@@ -57,6 +57,7 @@ import { CompactionComponent } from './components/dialogs/compaction';
5757import { HelpPanelComponent } from './components/dialogs/help-panel' ;
5858import { QuestionDialogComponent } from './components/dialogs/question-dialog' ;
5959import { SessionPickerComponent } from './components/dialogs/session-picker' ;
60+ import { SwarmStartPermissionPromptComponent } from './components/dialogs/swarm-start-permission-prompt' ;
6061import {
6162 FileMentionProvider ,
6263 type SlashAutocompleteCommand ,
@@ -70,6 +71,7 @@ import {
7071 GoalSetMessageComponent ,
7172} from './components/messages/goal-panel' ;
7273import { SkillActivationComponent } from './components/messages/skill-activation' ;
74+ import { SwarmModeMarkerComponent } from './components/messages/swarm-markers' ;
7375import {
7476 NoticeMessageComponent ,
7577 StatusMessageComponent ,
@@ -150,6 +152,8 @@ export interface KimiTUIStartupInput {
150152 readonly migrationPlan ?: MigrationPlan | null ;
151153 /** When true, run only the migration screen, then exit (the `kimi migrate` command). */
152154 readonly migrateOnly ?: boolean ;
155+ /** Default swarm mode from config.toml; CLI flags override this. */
156+ readonly defaultSwarmMode ?: boolean ;
153157}
154158
155159type EffectiveActivityPaneMode = ActivityPaneMode | 'idle' | 'session' ;
@@ -160,13 +164,14 @@ function createInitialAppState(input: KimiTUIStartupInput): AppState {
160164 : input . cliOptions . yolo
161165 ? 'yolo'
162166 : 'manual' ;
167+ const startupSwarm = input . cliOptions . swarm ?? input . defaultSwarmMode ?? false ;
163168 return {
164169 model : '' ,
165170 workDir : input . workDir ,
166171 sessionId : '' ,
167172 permissionMode : startupPermission ,
168173 planMode : input . cliOptions . plan ,
169- swarmMode : false ,
174+ swarmMode : startupSwarm ,
170175 thinking : false ,
171176 contextUsage : 0 ,
172177 contextTokens : 0 ,
@@ -260,6 +265,7 @@ export class KimiTUI {
260265 yolo : startupInput . cliOptions . yolo ,
261266 auto : startupInput . cliOptions . auto ,
262267 plan : startupInput . cliOptions . plan ,
268+ swarm : startupInput . cliOptions . swarm ,
263269 model : startupInput . cliOptions . model ,
264270 startupNotice : startupInput . startupNotice ,
265271 } ,
@@ -514,6 +520,58 @@ export class KimiTUI {
514520 this . updateTerminalTitle ( ) ;
515521 }
516522 void this . refreshSkillCommands ( this . session ) ;
523+ if ( ! shouldReplayHistory ) {
524+ void this . promptForSwarmPermissionIfNeeded ( ) ;
525+ }
526+ }
527+
528+ private async promptForSwarmPermissionIfNeeded ( ) : Promise < void > {
529+ if ( ! this . state . appState . swarmMode || this . state . appState . permissionMode !== 'manual' ) {
530+ return ;
531+ }
532+ const session = this . session ;
533+ if ( session === undefined ) return ;
534+
535+ this . deferUserMessages = true ;
536+ const restore = ( ) : void => {
537+ this . deferUserMessages = false ;
538+ this . restoreEditor ( ) ;
539+ } ;
540+
541+ this . mountEditorReplacement (
542+ new SwarmStartPermissionPromptComponent ( {
543+ onSelect : ( choice ) => {
544+ restore ( ) ;
545+ if ( choice === 'auto' || choice === 'yolo' ) {
546+ void ( async ( ) => {
547+ try {
548+ await session . setPermission ( choice ) ;
549+ } catch ( error ) {
550+ this . showError ( `Failed to set permission mode: ${ formatErrorMessage ( error ) } ` ) ;
551+ await this . disableStartupSwarmMode ( session ) ;
552+ return ;
553+ }
554+ this . setAppState ( { permissionMode : choice } ) ;
555+ } ) ( ) ;
556+ }
557+ } ,
558+ onCancel : ( ) => {
559+ restore ( ) ;
560+ void this . disableStartupSwarmMode ( session ) ;
561+ } ,
562+ } ) ,
563+ ) ;
564+ }
565+
566+ private async disableStartupSwarmMode ( session : Session ) : Promise < void > {
567+ try {
568+ await session . setSwarmMode ( false , 'manual' ) ;
569+ } catch ( error ) {
570+ this . showError ( `Failed to disable swarm mode: ${ formatErrorMessage ( error ) } ` ) ;
571+ }
572+ this . setAppState ( { swarmMode : false } ) ;
573+ this . state . transcriptContainer . addChild ( new SwarmModeMarkerComponent ( 'inactive' ) ) ;
574+ this . state . ui . requestRender ( ) ;
517575 }
518576
519577 private async showTmuxKeyboardWarningIfNeeded ( ) : Promise < void > {
@@ -537,6 +595,7 @@ export class KimiTUI {
537595 model : startup . model ,
538596 permission : startup . auto ? 'auto' : startup . yolo ? 'yolo' : undefined ,
539597 planMode : startup . plan ? true : undefined ,
598+ swarmMode : this . state . appState . swarmMode ? true : undefined ,
540599 } ;
541600
542601 try {
@@ -1090,10 +1149,10 @@ export class KimiTUI {
10901149 } ) ;
10911150 }
10921151
1093- // Apply --auto/--yolo/--plan startup flags to a resumed session. The resumed
1094- // session may already be in plan mode from its persisted records, and
1095- // re-entering plan mode throws, so only enable it when it is not active yet.
1096- // setPermission is idempotent and needs no such guard.
1152+ // Apply --auto/--yolo/--plan/--swarm startup flags to a resumed session. The
1153+ // resumed session may already be in plan/swarm mode from its persisted
1154+ // records, and re-entering plan mode throws, so only enable it when it is not
1155+ // active yet. setPermission is idempotent and needs no such guard.
10971156 private async applyStartupModesToResumedSession ( session : Session ) : Promise < void > {
10981157 const { startup } = this . options ;
10991158 if ( startup . auto ) {
@@ -1107,6 +1166,12 @@ export class KimiTUI {
11071166 await session . setPlanMode ( true ) ;
11081167 }
11091168 }
1169+ if ( startup . swarm ) {
1170+ const status = await session . getStatus ( ) ;
1171+ if ( ! status . swarmMode ) {
1172+ await session . setSwarmMode ( true , 'manual' ) ;
1173+ }
1174+ }
11101175 }
11111176
11121177 // Re-apply startup flags that the user explicitly passed on the command line.
0 commit comments