| 239 | const DETACH_HINT_DISPLAY_MS = 4_000; |
| 240 | |
| 241 | export class KimiTUI { |
| 242 | readonly harness: KimiHarness; |
| 243 | readonly options: KimiTUIOptions; |
| 244 | session: Session | undefined; |
| 245 | state: TUIState; |
| 246 | private readonly approvalController = new ApprovalController(); |
| 247 | private readonly questionController = new QuestionController(); |
| 248 | private readonly reverseRpcDisposers: Array<() => void> = []; |
| 249 | private skillCommands: readonly KimiSlashCommand[] = []; |
| 250 | readonly skillCommandMap = new Map<string, string>(); |
| 251 | private pluginCommands: readonly KimiSlashCommand[] = []; |
| 252 | readonly pluginCommandMap = new Map<string, string>(); |
| 253 | private readonly imageStore = new ImageAttachmentStore(); |
| 254 | private fdPath: string | null = detectFdPath(); |
| 255 | private fdDownloadStarted = false; |
| 256 | sessionEventUnsubscribe: (() => void) | undefined; |
| 257 | cancelInFlight: (() => void) | undefined; |
| 258 | deferUserMessages = false; |
| 259 | aborted = false; |
| 260 | private terminalFocusTrackingDispose: (() => void) | undefined; |
| 261 | private terminalThemeTrackingDispose: (() => void) | undefined; |
| 262 | private clipboardImageHintController: ClipboardImageHintController | undefined; |
| 263 | private uninstallRainbowDance: () => void; |
| 264 | private signalCleanupHandlers: Array<() => void> = []; |
| 265 | private isShuttingDown = false; |
| 266 | private readonly migrationPlan: MigrationPlan | null; |
| 267 | private readonly migrateOnly: boolean; |
| 268 | private startupNotice: string | undefined; |
| 269 | private lastActivityMode: string | undefined; |
| 270 | private currentLoadingTip: { kind: LoadingTipKind; tip: string | undefined } | undefined = |
| 271 | undefined; |
| 272 | private lastHistoryContent: string | undefined; |
| 273 | // Live `!` shell output entries, keyed by commandId so concurrent commands |
| 274 | // each update their own card and stale events are dropped. Mutated in place |
| 275 | // as `shell.output` events arrive; removed when the command completes. |
| 276 | // `taskId` (from `shell.started`) lets ctrl+b detach the exact task. |
| 277 | private readonly shellOutputStreams = new Map< |
| 278 | string, |
| 279 | { entry: TranscriptEntry; component: ShellRunComponent; taskId?: string } |
| 280 | >(); |
| 281 | readonly streamingUI: StreamingUIController; |
| 282 | readonly authFlow: AuthFlowController; |
| 283 | readonly btwPanelController: BtwPanelController; |
| 284 | readonly sessionEventHandler: SessionEventHandler; |
| 285 | readonly sessionReplay: SessionReplayRenderer; |
| 286 | readonly tasksBrowserController: TasksBrowserController; |
| 287 | readonly editorKeyboard: EditorKeyboardController; |
| 288 | |
| 289 | /** Timer that auto-clears the one-shot "moved to background" footer hint. */ |
| 290 | private detachHintClearTimer: ReturnType<typeof setTimeout> | undefined; |
| 291 | |
| 292 | // The currently-mounted approval panel, if any. Kept so the full-screen |
| 293 | // preview viewer can restore focus to the exact same instance (and its |
| 294 | // selection / feedback state) when it closes. |
| 295 | private activeApprovalPanel: ApprovalPanelComponent | undefined; |
| 296 | // Active full-screen approval preview. While set, the root UI's normal |
| 297 | // children are stashed in `savedChildren`; closing restores them. |
| 298 | private approvalPreview: |
nothing calls this directly
no test coverage detected