diff --git a/.changeset/rich-status-card.md b/.changeset/rich-status-card.md new file mode 100644 index 000000000..814038bce --- /dev/null +++ b/.changeset/rich-status-card.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Enrich the `/status` panel with runtime context and colorized fields. diff --git a/apps/kimi-code/src/tui/commands/info.ts b/apps/kimi-code/src/tui/commands/info.ts index 73cc99824..b4c441790 100644 --- a/apps/kimi-code/src/tui/commands/info.ts +++ b/apps/kimi-code/src/tui/commands/info.ts @@ -116,14 +116,18 @@ export async function showStatusReport(host: SlashCommandHost): Promise { thinking: appState.thinking, permissionMode: appState.permissionMode, planMode: appState.planMode, + swarmMode: appState.swarmMode, contextUsage: appState.contextUsage, contextTokens: appState.contextTokens, maxContextTokens: appState.maxContextTokens, availableModels: appState.availableModels, + availableProviders: appState.availableProviders, status: runtimeStatus.status, statusError: runtimeStatus.error, managedUsage: managedUsage?.usage, managedUsageError: managedUsage?.error, + mcpServersSummary: appState.mcpServersSummary, + goal: appState.goal, }; const panel = new UsagePanelComponent(() => buildStatusReportLines(reportArgs), 'primary', ' Status '); host.state.transcriptContainer.addChild(panel); diff --git a/apps/kimi-code/src/tui/components/messages/status-panel.ts b/apps/kimi-code/src/tui/components/messages/status-panel.ts index 9007b8f97..0bd247d9c 100644 --- a/apps/kimi-code/src/tui/components/messages/status-panel.ts +++ b/apps/kimi-code/src/tui/components/messages/status-panel.ts @@ -5,7 +5,7 @@ * separate from the TUI orchestration layer. */ -import type { ModelAlias, PermissionMode, SessionStatus } from '@moonshot-ai/kimi-code-sdk'; +import type { GoalSnapshot, ModelAlias, PermissionMode, ProviderConfig, SessionStatus } from '@moonshot-ai/kimi-code-sdk'; import { PRODUCT_NAME } from '#/constant/app'; import { currentTheme } from '#/tui/theme'; @@ -33,14 +33,18 @@ export interface StatusReportOptions { readonly thinking: boolean; readonly permissionMode: PermissionMode; readonly planMode: boolean; + readonly swarmMode: boolean; readonly contextUsage: number; readonly contextTokens: number; readonly maxContextTokens: number; readonly availableModels: Record; + readonly availableProviders: Record; readonly status?: SessionStatus; readonly statusError?: string; readonly managedUsage?: ManagedUsageReport; readonly managedUsageError?: string; + readonly mcpServersSummary?: string | null; + readonly goal?: GoalSnapshot | null; } type Colorize = (text: string) => string; @@ -60,6 +64,26 @@ function formatModelStatus(options: StatusReportOptions): string { return `${displayModelName(model, options.availableModels)} (thinking ${thinking})`; } +function formatPermissionMode(permission: PermissionMode, value: Colorize, errorStyle: Colorize): string { + if (permission === 'yolo') return errorStyle('yolo'); + if (permission === 'auto') return value('auto'); + return value(permission); +} + +function formatPlanMode(planMode: boolean, value: Colorize, muted: Colorize): string { + return planMode ? value('on') : muted('off'); +} + +function formatSwarmMode(swarmMode: boolean, value: Colorize, muted: Colorize): string { + return swarmMode ? value('on') : muted('off'); +} + +function formatGoalStatus(goal: GoalSnapshot | null | undefined): string | undefined { + if (goal === null || goal === undefined) return undefined; + const objective = goal.objective.length > 40 ? `${goal.objective.slice(0, 40)}…` : goal.objective; + return `${objective} · ${goal.status}`; +} + function addFieldRows( lines: string[], rows: readonly FieldRow[], @@ -97,15 +121,31 @@ export function buildStatusReportLines(options: StatusReportOptions): string[] { const permission = options.status?.permission ?? options.permissionMode; const planMode = options.status?.planMode ?? options.planMode; const sessionId = options.sessionId.trim().length > 0 ? options.sessionId : 'none'; + const modelCount = Object.keys(options.availableModels).length; + const providerCount = Object.keys(options.availableProviders).length; + const mcpSummary = options.mcpServersSummary?.trim(); + const goalStatus = formatGoalStatus(options.goal); + const rows: FieldRow[] = [ { label: 'Model', value: formatModelStatus(options) }, { label: 'Directory', value: options.workDir }, - { label: 'Permissions', value: permission }, - { label: 'Plan mode', value: planMode ? 'on' : 'off' }, + { label: 'Permissions', value: formatPermissionMode(permission, value, errorStyle) }, + { label: 'Plan mode', value: formatPlanMode(planMode, value, muted) }, + { label: 'Swarm', value: formatSwarmMode(options.swarmMode, value, muted) }, { label: 'Session', value: sessionId }, ]; const title = options.sessionTitle?.trim(); if (title !== undefined && title.length > 0) rows.push({ label: 'Title', value: title }); + if (goalStatus !== undefined) { + rows.push({ label: 'Goal', value: goalStatus }); + } + rows.push( + { label: 'Providers', value: `${providerCount}` }, + { label: 'Models', value: `${modelCount}` }, + ); + if (mcpSummary !== undefined && mcpSummary.length > 0) { + rows.push({ label: 'MCP servers', value: mcpSummary }); + } if (options.statusError !== undefined) { rows.push({ label: 'Warning', value: options.statusError, severity: 'error' }); } diff --git a/apps/kimi-code/test/tui/components/messages/status-panel.test.ts b/apps/kimi-code/test/tui/components/messages/status-panel.test.ts index ca67aded7..bf9d0b03f 100644 --- a/apps/kimi-code/test/tui/components/messages/status-panel.test.ts +++ b/apps/kimi-code/test/tui/components/messages/status-panel.test.ts @@ -17,6 +17,7 @@ describe('status panel report lines', () => { thinking: true, permissionMode: 'manual', planMode: false, + swarmMode: false, contextUsage: 0.25, contextTokens: 2500, maxContextTokens: 10000, @@ -28,6 +29,33 @@ describe('status panel report lines', () => { displayName: 'Kimi K2', }, }, + availableProviders: { + 'managed:kimi-code': { + type: 'kimi', + apiKey: 'sk-test', + }, + }, + mcpServersSummary: '2 connected', + goal: { + goalId: 'goal-1', + objective: 'Ship the status card', + status: 'active', + turnsUsed: 3, + tokensUsed: 1200, + wallClockMs: 45000, + budget: { + tokenBudget: null, + turnBudget: null, + wallClockBudgetMs: null, + remainingTokens: null, + remainingTurns: null, + remainingWallClockMs: null, + tokenBudgetReached: false, + turnBudgetReached: false, + wallClockBudgetReached: false, + overBudget: false, + }, + }, status: { model: 'k2', thinkingLevel: 'high', @@ -56,8 +84,13 @@ describe('status panel report lines', () => { expect(output).toContain('Directory /tmp/project'); expect(output).toContain('Permissions auto'); expect(output).toContain('Plan mode on'); + expect(output).toContain('Swarm off'); expect(output).toContain('Session ses-1'); expect(output).toContain('Title Implement status'); + expect(output).toContain('Goal Ship the status card · active'); + expect(output).toContain('Providers 1'); + expect(output).toContain('Models 1'); + expect(output).toContain('MCP servers 2 connected'); expect(output).toContain('Context window'); expect(output).toContain('25.0%'); expect(output).toContain('(3.0k / 12.0k)'); @@ -65,7 +98,6 @@ describe('status panel report lines', () => { expect(output).toContain('8% used'); expect(output).not.toContain('Account'); expect(output).not.toContain('AGENTS.md'); - expect(output).not.toContain('Runtime'); }); it('falls back to app state and shows status load errors as warnings', () => { @@ -78,10 +110,12 @@ describe('status panel report lines', () => { thinking: false, permissionMode: 'manual', planMode: false, + swarmMode: false, contextUsage: 0, contextTokens: 0, maxContextTokens: 0, availableModels: {}, + availableProviders: {}, statusError: 'No active session', }).map(strip);