Skip to content
Open
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
27dded5
feat: add agent swarm tui support
kermanx Jun 3, 2026
d711cf0
fix: redraw agent swarm progress updates
kermanx Jun 3, 2026
45a74b1
fix: refine agent swarm progress rendering
kermanx Jun 3, 2026
1cd720a
upd
kermanx Jun 3, 2026
6f89819
fix
kermanx Jun 3, 2026
9e58f5c
update
kermanx Jun 4, 2026
d3f92ef
fix
kermanx Jun 4, 2026
8bc6b5f
update
kermanx Jun 4, 2026
329238b
upd
kermanx Jun 4, 2026
0c7d35f
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 4, 2026
9db2cd2
fix
kermanx Jun 4, 2026
4d76bf0
update
kermanx Jun 4, 2026
c1b5ba7
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 4, 2026
4797e90
fix
kermanx Jun 4, 2026
0a93675
fix
kermanx Jun 4, 2026
c0bfa73
fix: emit suspended subagent events
kermanx Jun 4, 2026
5729fbc
fix: show suspended swarm progress
kermanx Jun 4, 2026
42ff90b
fix: normalize suspended swarm status
kermanx Jun 4, 2026
a116227
fix
kermanx Jun 4, 2026
68ec665
fix
kermanx Jun 4, 2026
f5841ae
fix
kermanx Jun 4, 2026
ce6a471
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 5, 2026
99adec6
fix
kermanx Jun 5, 2026
b418a04
fix
kermanx Jun 5, 2026
765f108
fix
kermanx Jun 5, 2026
75e00b8
update
kermanx Jun 5, 2026
b922dbe
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 5, 2026
108f635
fix
kermanx Jun 5, 2026
263c968
fix
kermanx Jun 5, 2026
206183e
fix
kermanx Jun 5, 2026
65454eb
fix
kermanx Jun 5, 2026
ef7521a
fix
kermanx Jun 5, 2026
a952c70
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 5, 2026
96a081a
fix
kermanx Jun 5, 2026
ef4a584
fix
kermanx Jun 5, 2026
95936ff
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 5, 2026
61c3a03
fix
kermanx Jun 5, 2026
7122b52
fix
kermanx Jun 5, 2026
e175ddf
swarmItem
kermanx Jun 5, 2026
71d85df
fix
kermanx Jun 5, 2026
43c708f
fix: restore export markdown internal filtering
kermanx Jun 5, 2026
fe4aa16
upd
kermanx Jun 5, 2026
dbc99be
Merge remote-tracking branch 'origin/main' into xtr/agent-swarm-progr…
kermanx Jun 6, 2026
2c471c8
fix
kermanx Jun 6, 2026
e7a07aa
wip
kermanx Jun 6, 2026
d72e279
wip
kermanx Jun 6, 2026
82d3831
remove swarm demo
kermanx Jun 6, 2026
615256d
fix
kermanx Jun 6, 2026
bc82e59
wip
kermanx Jun 6, 2026
2f965c0
wip
kermanx Jun 6, 2026
3e79efa
wip
kermanx Jun 6, 2026
0991872
wip
kermanx Jun 6, 2026
fa64e52
upd
kermanx Jun 6, 2026
a0cd20c
wip
kermanx Jun 6, 2026
e8f24eb
fix
kermanx Jun 6, 2026
896dfde
update
kermanx Jun 6, 2026
d529b75
fix
kermanx Jun 6, 2026
5cf861b
fix
kermanx Jun 6, 2026
dc029d5
fix
kermanx Jun 6, 2026
6e87a7b
update
kermanx Jun 6, 2026
536d27c
fix
kermanx Jun 6, 2026
f52f253
fix
kermanx Jun 6, 2026
29de45b
fix exit
kermanx Jun 6, 2026
06d452f
fix
kermanx Jun 6, 2026
b9dc203
fix
kermanx Jun 6, 2026
4a2eede
timeout
kermanx Jun 6, 2026
42b1fb9
fix
kermanx Jun 6, 2026
548ada8
fix
kermanx Jun 6, 2026
0d50b54
refactor merge panels
kermanx Jun 6, 2026
7c9e5dd
fix
kermanx Jun 6, 2026
1dc2a84
fix
kermanx Jun 6, 2026
c5b416c
chore: update agent swarm changeset
kermanx Jun 6, 2026
67b8d35
update
kermanx Jun 6, 2026
4a64533
update
kermanx Jun 6, 2026
0da717c
fix
kermanx Jun 6, 2026
5c22fa9
fix
kermanx Jun 6, 2026
a42e6a7
fix
kermanx Jun 6, 2026
b380ceb
ok
kermanx Jun 6, 2026
31fa83d
gated under flag
kermanx Jun 6, 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
7 changes: 7 additions & 0 deletions .changeset/template-agent-swarm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@moonshot-ai/agent-core": minor
"@moonshot-ai/kimi-code-sdk": minor
"@moonshot-ai/kimi-code": minor
---

Add template-based AgentSwarm launches with live TUI progress.
2 changes: 2 additions & 0 deletions apps/kimi-code/src/cli/run-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ function runPromptTurn(
case 'subagent.completed':
case 'subagent.failed':
case 'subagent.spawned':
case 'subagent.started':
case 'subagent.suspended':
case 'tool.list.updated':
case 'turn.started':
case 'turn.step.completed':
Expand Down
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 @@ -42,6 +42,7 @@ import { handleProviderCommand } from './provider';
import { handleFeedbackCommand, showMcpServers, showStatusReport, showUsage } from './info';
import { handlePluginsCommand } from './plugins';
import { handleReloadCommand, handleReloadTuiCommand } from './reload';
import { handleSwarmCommand } from './swarm';
import {
handleExportDebugZipCommand,
handleExportMdCommand,
Expand Down Expand Up @@ -73,6 +74,7 @@ export {
showPermissionPicker,
showSettingsSelector,
} from './config';
export { handleSwarmCommand } from './swarm';
export {
handleFeedbackCommand,
showMcpServers,
Expand Down Expand Up @@ -300,6 +302,9 @@ async function handleBuiltInSlashCommand(
case 'plan':
await handlePlanCommand(host, args);
return;
case 'swarm':
await handleSwarmCommand(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 {
showPermissionPicker,
showSettingsSelector,
} from './config';
export { handleSwarmCommand } from './swarm';
export {
handleFeedbackCommand,
showMcpServers,
Expand Down
18 changes: 18 additions & 0 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const GOAL_NEXT_ARG_COMPLETIONS: readonly ArgCompletionSpec[] = [
{ value: 'manage', description: 'Manage upcoming goals' },
];

const SWARM_ARG_COMPLETIONS: readonly ArgCompletionSpec[] = [
{ value: 'on', description: 'Turn swarm mode on' },
{ value: 'off', description: 'Turn swarm mode off' },
];

/** Argument autocompletion for the `/goal` command (subcommands). */
export function goalArgumentCompletions(argumentPrefix: string): AutocompleteItem[] | null {
const nextMatch = argumentPrefix.match(/^next\s+(\S*)$/i);
Expand All @@ -31,6 +36,11 @@ export function goalArgumentCompletions(argumentPrefix: string): AutocompleteIte
return completeLeadingArg(GOAL_ARG_COMPLETIONS, argumentPrefix);
}

/** Argument autocompletion for the `/swarm` command (subcommands). */
export function swarmArgumentCompletions(argumentPrefix: string): AutocompleteItem[] | null {
return completeLeadingArg(SWARM_ARG_COMPLETIONS, argumentPrefix);
}

export const BUILTIN_SLASH_COMMANDS = [
{
name: 'yolo',
Expand Down Expand Up @@ -67,6 +77,14 @@ export const BUILTIN_SLASH_COMMANDS = [
priority: 100,
availability: (args) => (args.trim().toLowerCase() === 'clear' ? 'idle-only' : 'always'),
},
{
name: 'swarm',
aliases: [],
description: 'Toggle swarm mode or run one task in swarm mode',
priority: 100,
completeArgs: swarmArgumentCompletions,
availability: 'idle-only',
},
{
name: 'model',
aliases: [],
Expand Down
139 changes: 139 additions & 0 deletions apps/kimi-code/src/tui/commands/swarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import type { PermissionMode } from '@moonshot-ai/kimi-code-sdk';

import {
SwarmStartPermissionPromptComponent,
type SwarmStartPermissionChoice,
} from '../components/dialogs/swarm-start-permission-prompt';
import { SwarmModeMarkerComponent } from '../components/messages/swarm-markers';
import { LLM_NOT_SET_MESSAGE, NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui';
import { formatErrorMessage } from '../utils/event-payload';
import type { SlashCommandHost } from './dispatch';

export async function handleSwarmCommand(host: SlashCommandHost, args: string): Promise<void> {
if (host.session === undefined) {
host.showError(NO_ACTIVE_SESSION_MESSAGE);
return;
}

const prompt = args.trim();
const mode = swarmModeSubcommand(prompt);
if (mode !== undefined) {
await applySwarmMode(host, mode);
return;
}

if (prompt.length === 0) {
await applySwarmMode(host, !host.state.appState.swarmMode);
return;
}

if (host.state.appState.model.trim().length === 0) {
host.showError(LLM_NOT_SET_MESSAGE);
return;
}

if (host.state.appState.permissionMode === 'manual') {
if (!(await activateSwarmForTask(host))) return;
showSwarmStartPermissionPrompt(host, prompt);
Comment thread
kermanx marked this conversation as resolved.
Outdated
Comment thread
kermanx marked this conversation as resolved.
Outdated
return;
}

await startSwarmTask(host, prompt);
}

function showSwarmStartPermissionPrompt(host: SlashCommandHost, prompt: string): void {
const commandText = `/swarm ${prompt}`;
const cancelStart = (): void => {
host.restoreInputText(commandText);
host.showStatus('Swarm task not started.');
};
host.mountEditorReplacement(
new SwarmStartPermissionPromptComponent({
colors: host.state.theme.colors,
onSelect: (choice) => {
host.restoreEditor();
void startSwarmWithPermission(host, prompt, choice);
},
onCancel: cancelStart,
}),
);
}

async function startSwarmWithPermission(
host: SlashCommandHost,
prompt: string,
choice: SwarmStartPermissionChoice,
): Promise<void> {
if (choice === 'auto') {
if (!(await setPermissionForSwarm(host, choice))) return;
}
host.sendNormalUserInput(prompt);
}

async function setPermissionForSwarm(host: SlashCommandHost, mode: PermissionMode): Promise<boolean> {
try {
await host.requireSession().setPermission(mode);
} catch (error) {
host.showError(`Failed to set permission mode: ${formatErrorMessage(error)}`);
return false;
}
host.setAppState({ permissionMode: mode });
return true;
}

async function startSwarmTask(host: SlashCommandHost, prompt: string): Promise<void> {
if (!(await activateSwarmForTask(host))) return;
host.sendNormalUserInput(prompt);
}

async function activateSwarmForTask(host: SlashCommandHost): Promise<boolean> {
if (!host.state.appState.swarmMode && !(await setSwarmMode(host, true, 'task'))) {
return false;
}
renderSwarmModeMarker(host, true);
return true;
}

async function applySwarmMode(host: SlashCommandHost, enabled: boolean): Promise<void> {
if (enabled && host.state.appState.swarmMode) {
host.showStatus('Swarm mode is already on.');
return;
}
if (!enabled && !host.state.appState.swarmMode) {
host.showStatus('Swarm mode is already off.');
return;
}
if (!(await setSwarmMode(host, enabled, 'manual'))) return;
renderSwarmModeMarker(host, enabled);
}

async function setSwarmMode(
host: SlashCommandHost,
enabled: boolean,
trigger: 'manual' | 'task',
): Promise<boolean> {
try {
await host.requireSession().setSwarmMode(enabled, trigger);
} catch (error) {
host.showError(
`Failed to ${enabled ? 'enable' : 'disable'} swarm mode: ${formatErrorMessage(error)}`,
);
return false;
}
host.setAppState({ swarmMode: enabled });
return true;
}

function swarmModeSubcommand(input: string): boolean | undefined {
const command = input.toLowerCase();
if (command === 'on') return true;
if (command === 'off') return false;
return undefined;
}

function renderSwarmModeMarker(host: SlashCommandHost, active: boolean): void {
host.state.transcriptContainer.addChild(
new SwarmModeMarkerComponent(active, host.state.theme.colors),
);
host.state.ui.requestRender();
}
2 changes: 2 additions & 0 deletions apps/kimi-code/src/tui/commands/undo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Component } from '@earendil-works/pi-tui';

import { WelcomeComponent } from '../components/chrome/welcome';
import { AgentGroupComponent } from '../components/messages/agent-group';
import { AgentSwarmProgressComponent } from '../components/messages/agent-swarm-progress';
import { AssistantMessageComponent } from '../components/messages/assistant-message';
import { BackgroundAgentStatusComponent } from '../components/messages/background-agent-status';
import { CronMessageComponent } from '../components/messages/cron-message';
Expand Down Expand Up @@ -167,6 +168,7 @@ function isUndoContextComponent(child: Component): boolean {
child instanceof ThinkingComponent ||
child instanceof ToolCallComponent ||
child instanceof AgentGroupComponent ||
child instanceof AgentSwarmProgressComponent ||
Comment thread
kermanx marked this conversation as resolved.
child instanceof ReadGroupComponent ||
child instanceof SkillActivationComponent ||
child instanceof BackgroundAgentStatusComponent ||
Expand Down
9 changes: 6 additions & 3 deletions apps/kimi-code/src/tui/components/chrome/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,12 @@ export class FooterComponent implements Component {

// ── Line 1: mode badges + model + [N task(s) running] + [N agent(s) running] + cwd + git + hints ──
const left: string[] = [];
if (state.permissionMode === 'auto') left.push(chalk.hex(colors.warning).bold('auto'));
if (state.permissionMode === 'yolo') left.push(chalk.hex(colors.warning).bold('yolo'));
if (state.planMode) left.push(chalk.hex(colors.primary).bold('plan'));
const modes: string[] = [];
if (state.permissionMode === 'auto') modes.push(chalk.hex(colors.warning).bold('auto'));
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.success).bold('swarm'));
if (modes.length > 0) left.push(modes.join(' '));

const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
if (goalBadge !== null) left.push(goalBadge);
Expand Down
8 changes: 7 additions & 1 deletion apps/kimi-code/src/tui/components/chrome/moon-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class MoonLoader extends Text {
private interval: number;
private colorFn?: (s: string) => string;
private label: string;
private displayText = '';

constructor(
ui: TUI,
Expand Down Expand Up @@ -59,10 +60,15 @@ export class MoonLoader extends Text {
this.updateDisplay();
}

renderInline(): string {
return this.displayText;
}

private updateDisplay(): void {
const frame = this.frames[this.currentFrame]!;
const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
this.displayText = this.label ? `${coloredFrame} ${this.label}` : coloredFrame;
this.setText(this.displayText);
this.ui.requestRender();
}
}
Loading
Loading