Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
27e189c
feat(agent-core-v2): turn /tower into a mode parallel to plan mode
wbxl2000 Aug 19, 2026
5329388
feat(agent-core-v2): align /tower command semantics with the original…
wbxl2000 Aug 19, 2026
6c068f3
Merge remote-tracking branch 'origin/main' into feat/tower-mode
wbxl2000 Aug 19, 2026
63b9a43
fix(tui): harden the /tower command against mid-turn objectives, lega…
wbxl2000 Aug 19, 2026
7db8b6e
fix(agent-core-v2): keep restored tower state inert while the feature…
wbxl2000 Aug 19, 2026
b748da3
fix(agent-core-v2): include TowerInit in the mode tool overlay and re…
wbxl2000 Aug 19, 2026
99166f8
feat(agent-core-v2): expose tower control tools statically and make t…
wbxl2000 Aug 19, 2026
3380bfe
fix(tui): warn that tower mode needs a restart after a live flag flip…
wbxl2000 Aug 19, 2026
642ab6d
Merge remote-tracking branch 'origin/main' into feat/tower-mode
wbxl2000 Aug 20, 2026
9a4691b
fix(agent-core-v2): let tower mode exit clear persisted state while t…
wbxl2000 Aug 20, 2026
04e8038
fix(agent-core-v2): emit the tower exit reminder through a disabled f…
wbxl2000 Aug 20, 2026
35eecd1
fix(tui): show the Tower mode status row only when the experiment is …
wbxl2000 Aug 20, 2026
f34465e
fix(agent-core-v2): confine tower mode to the main agent and always r…
wbxl2000 Aug 20, 2026
1f357fb
feat(kap-server): project tower mode into transcript modes and reasse…
wbxl2000 Aug 20, 2026
afdeeed
fix(agent-core-v2): fold the main-agent invariant into the effective …
wbxl2000 Aug 20, 2026
0a1b95e
fix(kap-server): gate the cold tower mode badge behind the experiment…
wbxl2000 Aug 20, 2026
6ccca00
fix(agent-core-v2): reapply the tower tool overlay after a profile bi…
wbxl2000 Aug 20, 2026
b6020b4
fix(protocol): mirror towerMode and tower_mode in the shared zod schemas
wbxl2000 Aug 20, 2026
488859c
Merge remote-tracking branch 'origin/main' into feat/tower-mode
wbxl2000 Aug 20, 2026
05eec92
test(agent-core-v2): adapt tower tests to the agent lifecycle context…
wbxl2000 Aug 20, 2026
ee613f1
chore(agent-core-v2): regenerate the wire manifest; test(node-sdk): l…
wbxl2000 Aug 20, 2026
efbc73a
fix(agent-core-v2): exit a replayed tower mode when the workspace bel…
wbxl2000 Aug 20, 2026
85f5b20
fix(agent-core-v2): carry tower ownership in the enter record so fork…
wbxl2000 Aug 20, 2026
f7954a8
fix(agent-core-v2): apply the tower overlay before dispatching enter …
wbxl2000 Aug 20, 2026
a7e153c
fix(agent-core-v2): validate store ownership before entering tower mode
wbxl2000 Aug 20, 2026
831ffb5
fix(agent-core-v2): claim the repository tower owner at enter; fix(tu…
wbxl2000 Aug 20, 2026
8ada07d
fix(agent-core-v2): make the first tower claim exclusive and refuse a…
wbxl2000 Aug 20, 2026
f4bba85
feat(agent-core-v2): guard tower adoption and teardown with a cross-p…
wbxl2000 Aug 20, 2026
1063dfc
revert(agent-core-v2): drop the cross-process lease and enter-time cl…
wbxl2000 Aug 20, 2026
5f30610
fix(agent-core): hide the v2-only tower flag from the legacy experime…
wbxl2000 Aug 20, 2026
937aeb1
merge: resolve origin/main (#3007) into feat/tower-mode
wbxl2000 Aug 20, 2026
aaefbb4
fix(agent-core-v2): keep tower mode inert until the tower feature is …
wbxl2000 Aug 20, 2026
e443785
fix(kap-server): gate the cold tower badge on tower feature assembly
wbxl2000 Aug 20, 2026
86aab5d
fix(agent-core-v2): liveness-aware tower entry and per-App assembly s…
wbxl2000 Aug 20, 2026
9797030
fix(agent-core-v2): keep stale-owner adoption across resume; reject t…
wbxl2000 Aug 20, 2026
c1764fb
fix(agent-core-v2): clear the tower assembly marker when the feature …
wbxl2000 Aug 20, 2026
145c777
fix(agent-core-v2): publish tower deactivation on gate loss; reject r…
wbxl2000 Aug 20, 2026
345454e
fix(agent-core-v2): reconcile tower projection on config changes and …
wbxl2000 Aug 20, 2026
96cdbc9
fix(agent-core-v2): reconcile the tower projection in both directions
wbxl2000 Aug 20, 2026
a0c63b5
fix(agent-core-v2): validate forked tower ownership even while the fl…
wbxl2000 Aug 20, 2026
780a360
fix(agent-core-v2): veto tower tools while the tower experiment is off
wbxl2000 Aug 20, 2026
45b4a01
fix(agent-core-v2): keep tower worker write isolation when the flag t…
wbxl2000 Aug 20, 2026
b837752
test(agent-core-v2): make tower tests hermetic for CI
wbxl2000 Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tower-mode-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add tower mode as a mode parallel to plan mode: run /tower to toggle it, or /tower <objective> to start multi-agent orchestration on an objective. Enable it with KIMI_CODE_EXPERIMENTAL_TOWER=1.
5 changes: 5 additions & 0 deletions apps/kimi-code/src/tui/commands/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
handleTitleCommand,
} from './session';
import { handleSwarmCommand } from './swarm';
import { handleTowerCommand } from './tower';
import { handleUndoCommand } from './undo';
import { handleWebCommand } from './web';

Expand Down Expand Up @@ -96,6 +97,7 @@ export {
showSettingsSelector,
} from './config';
export { handleSwarmCommand } from './swarm';
export { handleTowerCommand } from './tower';
export { handleFeedbackCommand, showMcpServers, showStatusReport, showUsage } from './info';
export { handlePluginsCommand } from './plugins';
export { handleReloadCommand, handleReloadTuiCommand } from './reload';
Expand Down Expand Up @@ -573,6 +575,9 @@ async function handleBuiltInSlashCommand(
case 'swarm':
await handleSwarmCommand(host, args);
return;
case 'tower':
await handleTowerCommand(host, args);
return;
case 'compact':
await handleCompactCommand(host, args);
return;
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
showSettingsSelector,
} from './config';
export { handleSwarmCommand } from './swarm';
export { handleTowerCommand } from './tower';
export { handleFeedbackCommand, showMcpServers, showStatusReport, showUsage } from './info';
export { handlePluginsCommand } from './plugins';
export { handleReloadCommand, handleReloadTuiCommand } from './reload';
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export async function showStatusReport(host: SlashCommandHost): Promise<void> {
thinkingEffort: appState.thinkingEffort,
permissionMode: appState.permissionMode,
planMode: appState.planMode,
towerMode: appState.towerMode,
contextUsage: appState.contextUsage,
contextTokens: appState.contextTokens,
maxContextTokens: appState.maxContextTokens,
Expand Down
9 changes: 9 additions & 0 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ export const BUILTIN_SLASH_COMMANDS = [
completeArgs: swarmArgumentCompletions,
availability: 'idle-only',
},
{
name: 'tower',
aliases: [],
description: 'Toggle tower mode or set the tower objective',
priority: 100,
argumentHint: '[on|off] | <objective>',
availability: 'always',
Comment thread
wbxl2000 marked this conversation as resolved.
Outdated
experimentalFlag: 'tower',
},
{
name: 'model',
aliases: [],
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/commands/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function resolveSlashCommandInput(options: ResolveSlashCommandInput): Sla
// Skill activations are never blocked by a busy session: the TUI queues
// them behind the running turn exactly like normal messages (see
// sendSkillActivation), and Ctrl-S steers them as real activations, so
// commands like /tower can be issued any time.
// skill commands can be issued any time.
return {
kind: 'skill',
commandName: parsed.name,
Expand Down
66 changes: 66 additions & 0 deletions apps/kimi-code/src/tui/commands/tower.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui';
import { formatErrorMessage } from '../utils/event-payload';
import type { SlashCommandHost } from './dispatch';

export async function handleTowerCommand(host: SlashCommandHost, args: string): Promise<void> {
if (host.session === undefined) {
if (!host.engineV2) {
host.showError(NO_ACTIVE_SESSION_MESSAGE);
return;
}
// v2 session-less: lazy-create the session, then toggle — the same path
// the first prompt takes.
const session = await host.ensureSession();
if (session === undefined) return;
}

const input = args.trim().toLowerCase();
if (input === 'on') {
await applyTowerMode(host, true);
return;
}
if (input === 'off') {
await applyTowerMode(host, false);
return;
}
if (input.length === 0) {
await applyTowerMode(host, !host.state.appState.towerMode);
return;
}

await startTowerObjective(host, args.trim());
}

async function startTowerObjective(host: SlashCommandHost, objective: string): Promise<void> {
if (!host.state.appState.towerMode) {
if (!(await setTowerMode(host, true))) return;
Comment thread
wbxl2000 marked this conversation as resolved.
Outdated
host.showNotice('Tower mode: ON');
}
host.sendNormalUserInput(objective);
Comment thread
wbxl2000 marked this conversation as resolved.
Comment thread
wbxl2000 marked this conversation as resolved.
}

async function applyTowerMode(host: SlashCommandHost, enabled: boolean): Promise<void> {
if (enabled && host.state.appState.towerMode) {
host.showStatus('Tower mode is already on.');
return;
}
if (!enabled && !host.state.appState.towerMode) {
Comment thread
wbxl2000 marked this conversation as resolved.
Outdated
host.showStatus('Tower mode is already off.');
return;
}
if (!(await setTowerMode(host, enabled))) return;
host.showNotice(enabled ? 'Tower mode: ON' : 'Tower mode: OFF');
}

async function setTowerMode(host: SlashCommandHost, enabled: boolean): Promise<boolean> {
try {
await host.requireSession().setTowerMode(enabled);
} catch (error) {
host.showError(
`Failed to ${enabled ? 'enable' : 'disable'} tower mode: ${formatErrorMessage(error)}`,
);
return false;
}
host.setAppState({ towerMode: enabled });
Comment thread
wbxl2000 marked this conversation as resolved.
return true;
}
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/components/chrome/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export class FooterComponent implements Component {
if (state.permissionMode === 'yolo') modes.push(chalk.hex(colors.warning).bold('yolo'));
if (state.planMode) modes.push(chalk.hex(colors.primary).bold('plan'));
if (state.swarmMode) modes.push(chalk.hex(colors.accent).bold('swarm'));
if (state.towerMode) modes.push(chalk.hex(colors.accent).bold('tower'));
if (modes.length > 0) slots['mode'] = [modes.join(' ')];

const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
Expand Down
2 changes: 2 additions & 0 deletions apps/kimi-code/src/tui/components/messages/status-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface StatusReportOptions {
readonly thinkingEffort: ThinkingEffort;
readonly permissionMode: PermissionMode;
readonly planMode: boolean;
readonly towerMode: boolean;
readonly contextUsage: number;
readonly contextTokens: number;
readonly maxContextTokens: number;
Expand Down Expand Up @@ -112,6 +113,7 @@ export function buildStatusReportLines(options: StatusReportOptions): string[] {
{ label: 'Directory', value: options.workDir },
{ label: 'Permissions', value: permission },
{ label: 'Plan mode', value: planMode ? 'on' : 'off' },
{ label: 'Tower mode', value: options.towerMode ? 'on' : 'off' },
Comment thread
wbxl2000 marked this conversation as resolved.
Outdated
{ label: 'Session', value: sessionId },
];
const title = options.sessionTitle?.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ export class SessionEventHandler {
if (event.maxContextTokens !== undefined) patch.maxContextTokens = event.maxContextTokens;
if (event.planMode !== undefined) patch.planMode = event.planMode;
if (event.swarmMode !== undefined) patch.swarmMode = event.swarmMode;
if (event.towerMode !== undefined) patch.towerMode = event.towerMode;
if (event.permission !== undefined) {
patch.permissionMode = event.permission;
}
Expand Down
2 changes: 2 additions & 0 deletions apps/kimi-code/src/tui/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ function createInitialAppState(input: KimiTUIStartupInput): AppState {
planMode: input.cliOptions.plan,
inputMode: 'prompt',
swarmMode: false,
towerMode: false,
thinkingEffort: 'off',
contextUsage: 0,
contextTokens: 0,
Expand Down Expand Up @@ -2314,6 +2315,7 @@ export class KimiTUI {
permissionMode: status.permission,
planMode: status.planMode,
swarmMode: status.swarmMode ?? false,
towerMode: status.towerMode ?? false,
contextTokens: status.contextTokens,
maxContextTokens: status.maxContextTokens,
contextUsage: status.contextUsage,
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface AppState {
/** 'bash' when the editor is in `!` shell-command mode. */
inputMode: 'prompt' | 'bash';
swarmMode: boolean;
towerMode: boolean;
/** Live thinking effort of the active session (e.g. 'off', 'on', 'high');
* mirrors the runtime. The single source of truth for the thinking state in
* the TUI. */
Expand Down
10 changes: 10 additions & 0 deletions apps/kimi-code/test/tui/commands/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ describe('built-in slash command registry', () => {
'status',
'theme',
'title',
'tower',
'undo',
'usage',
'version',
Expand All @@ -197,4 +198,13 @@ describe('built-in slash command registry', () => {
expect((command as KimiSlashCommand).experimentalFlag).toBe('secondary-model');
expect(resolveSlashCommandAvailability(command!, '')).toBe('always');
});

it('gates tower behind the tower experiment, always available', () => {
const command = findBuiltInSlashCommand('tower');
expect(command).toBeDefined();
expect((command as KimiSlashCommand).experimentalFlag).toBe('tower');
expect(resolveSlashCommandAvailability(command!, '')).toBe('always');
expect(resolveSlashCommandAvailability(command!, 'on')).toBe('always');
expect(resolveSlashCommandAvailability(command!, 'Ship feature X')).toBe('always');
});
});
16 changes: 16 additions & 0 deletions apps/kimi-code/test/tui/commands/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,22 @@ describe('resolveSlashCommandInput', () => {
});
});

it('resolves /tower to the builtin command when the tower flag is enabled', () => {
setExperimentalFeatures([{ id: 'tower', enabled: true }]);

expect(resolve('/tower Ship feature X')).toMatchObject({
kind: 'builtin',
name: 'tower',
args: 'Ship feature X',
});
});

it('does not resolve /tower as a builtin when the tower flag is disabled', () => {
expect(resolve('/tower Ship feature X')).toEqual({
kind: 'message',
input: '/tower Ship feature X',
});
});
});

describe('goal command resolution', () => {
Expand Down
Loading