diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 10086bf8123..70d236c0ddd 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -25,9 +25,9 @@ import { UpdatePreferenceSelectorComponent } from '../components/dialogs/update- import { DEFAULT_TUI_CONFIG, saveTuiConfig, type TuiConfig } from '../config'; import type { ThemeName } from '#/tui/theme'; import { currentTheme, isBuiltInTheme, lightColors, loadCustomThemeMerged } from '#/tui/theme'; -import { NO_ACTIVE_SESSION_MESSAGE, UNCONFIRMED_FILE_CHANGES_WARNING } from '../constant/kimi-tui'; +import { NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui'; import { formatErrorMessage } from '../utils/event-payload'; -import { PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; +import { PERMISSION_MODE_DESCRIPTIONS, PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; import { thinkingEffortToConfig } from '../utils/thinking-config'; import { showUsage } from './info'; import { setExperimentalFeatures } from './experimental-flags'; @@ -129,102 +129,6 @@ async function applyPlanMode(host: SlashCommandHost, session: Session, enabled: } } -export async function handleYoloCommand(host: SlashCommandHost, args: string): Promise { - const session = host.session; - if (session === undefined && !host.engineV2) { - host.showError(NO_ACTIVE_SESSION_MESSAGE); - return; - } - // v2 session-less: the chosen mode is recorded in appState and passed to the - // lazy-created session; apply the runtime permission only when one exists. - - const subcmd = args.trim().toLowerCase(); - const currentMode = host.state.appState.permissionMode; - - if (subcmd === 'on') { - if (currentMode === 'yolo') { - host.showNotice('Ask When Needed mode is already on'); - return; - } - await session?.setPermission('yolo'); - host.setAppState({ permissionMode: 'yolo' }); - host.showNotice('Ask When Needed mode: ON', 'Routine edits and commands run automatically; risky actions, questions, and plans still ask.'); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); - return; - } - - if (subcmd === 'off') { - if (currentMode !== 'yolo') { - host.showNotice('Ask When Needed mode is already off'); - return; - } - await session?.setPermission('manual'); - host.setAppState({ permissionMode: 'manual' }); - host.showNotice('Ask When Needed mode: OFF'); - return; - } - - // toggle - if (currentMode === 'yolo') { - await session?.setPermission('manual'); - host.setAppState({ permissionMode: 'manual' }); - host.showNotice('Ask When Needed mode: OFF'); - } else { - await session?.setPermission('yolo'); - host.setAppState({ permissionMode: 'yolo' }); - host.showNotice('Ask When Needed mode: ON', 'Routine edits and commands run automatically; risky actions, questions, and plans still ask.'); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); - } -} - -export async function handleAutoCommand(host: SlashCommandHost, args: string): Promise { - const session = host.session; - if (session === undefined && !host.engineV2) { - host.showError(NO_ACTIVE_SESSION_MESSAGE); - return; - } - // v2 session-less: the chosen mode is recorded in appState and passed to the - // lazy-created session; apply the runtime permission only when one exists. - - const subcmd = args.trim().toLowerCase(); - const currentMode = host.state.appState.permissionMode; - - if (subcmd === 'on') { - if (currentMode === 'auto') { - host.showNotice('Never Ask mode is already on'); - return; - } - await session?.setPermission('auto'); - host.setAppState({ permissionMode: 'auto' }); - host.showNotice('Never Ask mode: ON', 'Never interrupts you; everything runs and is decided automatically.'); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); - return; - } - - if (subcmd === 'off') { - if (currentMode !== 'auto') { - host.showNotice('Never Ask mode is already off'); - return; - } - await session?.setPermission('manual'); - host.setAppState({ permissionMode: 'manual' }); - host.showNotice('Never Ask mode: OFF'); - return; - } - - // toggle - if (currentMode === 'auto') { - await session?.setPermission('manual'); - host.setAppState({ permissionMode: 'manual' }); - host.showNotice('Never Ask mode: OFF'); - } else { - await session?.setPermission('auto'); - host.setAppState({ permissionMode: 'auto' }); - host.showNotice('Never Ask mode: ON', 'Never interrupts you; everything runs and is decided automatically.'); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); - } -} - export async function handleCompactCommand(host: SlashCommandHost, args: string): Promise { const session = host.session; if (session === undefined) { @@ -742,10 +646,11 @@ async function applyThemeChoice(host: SlashCommandHost, theme: ThemeName): Promi host.showStatus(`Theme set to "${theme}"${detail}.`); } -export function showPermissionPicker(host: SlashCommandHost): void { +export function showPermissionPicker(host: SlashCommandHost, initialMode?: PermissionMode): void { host.mountEditorReplacement( new PermissionSelectorComponent({ currentValue: host.state.appState.permissionMode, + initialValue: initialMode, onSelect: (value) => { host.restoreEditor(); void applyPermissionChoice(host, value); @@ -915,7 +820,7 @@ async function applyPermissionChoice(host: SlashCommandHost, mode: PermissionMod host.setAppState({ permissionMode: mode }); host.showNotice(`Permission mode: ${PERMISSION_MODE_DISPLAY_NAMES[mode]}`); if (mode !== 'manual') { - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + host.showStatus(PERMISSION_MODE_DESCRIPTIONS[mode], 'warning'); } } diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index afe3b88be43..f6682ee43dd 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -28,7 +28,6 @@ import { handleLoginCommand, handleLogoutCommand } from './auth'; import { handleBtwCommand } from './btw'; import { handleCopyCommand } from './copy'; import { - handleAutoCommand, handleCompactCommand, handleEditorCommand, handleEffortCommand, @@ -36,7 +35,6 @@ import { handlePlanCommand, handleSecondaryModelCommand, handleThemeCommand, - handleYoloCommand, showExperimentsPanel, showModelPicker, showPermissionPicker, @@ -82,7 +80,6 @@ export { handleBtwCommand } from './btw'; export { handleCopyCommand } from './copy'; export { handleAddDirCommand } from './add-dir'; export { - handleAutoCommand, handleCompactCommand, handleEditorCommand, handleEffortCommand, @@ -90,7 +87,6 @@ export { handlePlanCommand, handleSecondaryModelCommand, handleThemeCommand, - handleYoloCommand, showModelPicker, showExperimentsPanel, showPermissionPicker, @@ -565,11 +561,11 @@ async function handleBuiltInSlashCommand( case 'title': await handleTitleCommand(host, args); return; - case 'ask-when-needed': - await handleYoloCommand(host, args); + case 'yolo': + showPermissionPicker(host, 'yolo'); return; - case 'never-ask': - await handleAutoCommand(host, args); + case 'auto': + showPermissionPicker(host, 'auto'); return; case 'plan': await handlePlanCommand(host, args); diff --git a/apps/kimi-code/src/tui/commands/goal.ts b/apps/kimi-code/src/tui/commands/goal.ts index c6f23c1f555..d33d7b64b52 100644 --- a/apps/kimi-code/src/tui/commands/goal.ts +++ b/apps/kimi-code/src/tui/commands/goal.ts @@ -15,7 +15,7 @@ import { GoalStatusMessageComponent, UpcomingGoalAddedMessageComponent, } from '../components/messages/goal-panel'; -import { LLM_NOT_SET_MESSAGE, UNCONFIRMED_FILE_CHANGES_WARNING } from '../constant/kimi-tui'; +import { LLM_NOT_SET_MESSAGE } from '../constant/kimi-tui'; import { appendGoalQueueItem, moveGoalQueueItem, @@ -25,7 +25,7 @@ import { type GoalQueueSnapshot, } from '../goal-queue-store'; import { formatErrorMessage } from '../utils/event-payload'; -import { PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; +import { PERMISSION_MODE_DESCRIPTIONS, PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; import { canRestoreSubmittedInput } from './resolve'; import type { SlashCommandHost } from './dispatch'; @@ -452,7 +452,7 @@ async function startGoalWithPermission( // transcript even though the rollback above restored the previous mode. if (switched) { host.showNotice(`Permission mode: ${PERMISSION_MODE_DISPLAY_NAMES[choice]}`); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + host.showStatus(PERMISSION_MODE_DESCRIPTIONS[choice], 'warning'); } } diff --git a/apps/kimi-code/src/tui/commands/index.ts b/apps/kimi-code/src/tui/commands/index.ts index 6bbffc67b53..cdafaac1e66 100644 --- a/apps/kimi-code/src/tui/commands/index.ts +++ b/apps/kimi-code/src/tui/commands/index.ts @@ -16,7 +16,6 @@ export { handleModelCommand, handlePlanCommand, handleThemeCommand, - handleYoloCommand, showExperimentsPanel, showModelPicker, showPermissionPicker, diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index 7888de62777..3448c6aefa5 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -146,16 +146,16 @@ function formatDirectoryCompletionValue(argumentPrefix: string, parentInput: str export const BUILTIN_SLASH_COMMANDS = [ { - name: 'ask-when-needed', - aliases: ['yolo', 'yes'], - description: 'Toggle Ask When Needed mode: routine edits and commands run automatically; risky actions, questions, and plans still ask.', + name: 'yolo', + aliases: ['yes'], + description: 'Ask When Needed mode: routine edits and commands run automatically; risky actions, questions, and plans still ask.', priority: 101, availability: 'always', }, { - name: 'never-ask', - aliases: ['auto'], - description: 'Toggle Never Ask mode: never interrupts you; everything runs and is decided automatically.', + name: 'auto', + aliases: [], + description: 'Never Ask mode: never interrupts you; everything runs and is decided automatically.', priority: 99, availability: 'always', }, diff --git a/apps/kimi-code/src/tui/commands/swarm.ts b/apps/kimi-code/src/tui/commands/swarm.ts index bed78566db5..daf5cecb1b2 100644 --- a/apps/kimi-code/src/tui/commands/swarm.ts +++ b/apps/kimi-code/src/tui/commands/swarm.ts @@ -8,9 +8,9 @@ import { SwarmModeMarkerComponent, type SwarmModeMarkerState, } from '../components/messages/swarm-markers'; -import { LLM_NOT_SET_MESSAGE, NO_ACTIVE_SESSION_MESSAGE, UNCONFIRMED_FILE_CHANGES_WARNING } from '../constant/kimi-tui'; +import { LLM_NOT_SET_MESSAGE, NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui'; import { formatErrorMessage } from '../utils/event-payload'; -import { PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; +import { PERMISSION_MODE_DESCRIPTIONS, PERMISSION_MODE_DISPLAY_NAMES } from '../utils/permission-mode'; import type { SlashCommandHost } from './dispatch'; export async function handleSwarmCommand(host: SlashCommandHost, args: string): Promise { @@ -87,7 +87,7 @@ async function setPermissionForSwarm(host: SlashCommandHost, mode: PermissionMod } host.setAppState({ permissionMode: mode }); host.showNotice(`Permission mode: ${PERMISSION_MODE_DISPLAY_NAMES[mode]}`); - host.showStatus(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + host.showStatus(PERMISSION_MODE_DESCRIPTIONS[mode], 'warning'); return true; } diff --git a/apps/kimi-code/src/tui/components/chrome/footer.ts b/apps/kimi-code/src/tui/components/chrome/footer.ts index ae39f40231e..fb41393badb 100644 --- a/apps/kimi-code/src/tui/components/chrome/footer.ts +++ b/apps/kimi-code/src/tui/components/chrome/footer.ts @@ -2,7 +2,7 @@ * Footer/status bar — multi-line status display at the bottom of the TUI. * * Layout: - * Line 1: [ask-when-needed] [plan] + * Line 1: [Ask When Needed] [plan] * Line 2: context: N% (tokens/max) */ diff --git a/apps/kimi-code/src/tui/components/dialogs/choice-picker.ts b/apps/kimi-code/src/tui/components/dialogs/choice-picker.ts index b6b74fe5b9f..4ad6c0654a1 100644 --- a/apps/kimi-code/src/tui/components/dialogs/choice-picker.ts +++ b/apps/kimi-code/src/tui/components/dialogs/choice-picker.ts @@ -44,6 +44,7 @@ export interface ChoicePickerOptions { readonly noticeTone?: 'success' | 'warning'; readonly options: readonly ChoiceOption[]; readonly currentValue?: string; + readonly initialValue?: string; /** When true, typed characters filter the list (fuzzy) and a search line is shown. */ readonly searchable?: boolean; /** Items per page. Lists longer than this paginate. */ @@ -86,12 +87,14 @@ export class ChoicePickerComponent extends Container implements Focusable { constructor(opts: ChoicePickerOptions) { super(); this.opts = opts; - const currentIdx = opts.options.findIndex((o) => o.value === opts.currentValue); + const initialIdx = opts.options.findIndex( + (o) => o.value === (opts.initialValue ?? opts.currentValue), + ); this.list = new SearchableList({ items: opts.options, toSearchText: (o) => `${o.label} ${o.description ?? ''}`, pageSize: opts.pageSize, - initialIndex: Math.max(currentIdx, 0), + initialIndex: Math.max(initialIdx, 0), searchable: opts.searchable === true, }); } diff --git a/apps/kimi-code/src/tui/components/dialogs/permission-selector.ts b/apps/kimi-code/src/tui/components/dialogs/permission-selector.ts index a212c4eafd6..0ffbec57569 100644 --- a/apps/kimi-code/src/tui/components/dialogs/permission-selector.ts +++ b/apps/kimi-code/src/tui/components/dialogs/permission-selector.ts @@ -1,6 +1,6 @@ import type { PermissionMode } from '@moonshot-ai/kimi-code-sdk'; -import { PERMISSION_MODE_DISPLAY_NAMES } from '#/tui/utils/permission-mode'; +import { PERMISSION_MODE_DESCRIPTIONS, PERMISSION_MODE_DISPLAY_NAMES } from '#/tui/utils/permission-mode'; import { ChoicePickerComponent, type ChoiceOption } from './choice-picker'; @@ -8,18 +8,17 @@ const PERMISSION_OPTIONS: readonly ChoiceOption[] = [ { value: 'manual', label: PERMISSION_MODE_DISPLAY_NAMES.manual, - description: 'Auto-read only; everything else needs your approval first.', + description: PERMISSION_MODE_DESCRIPTIONS.manual, }, { value: 'yolo', label: PERMISSION_MODE_DISPLAY_NAMES.yolo, - description: - 'Routine edits and commands run automatically; risky actions, questions, and plans still ask.', + description: PERMISSION_MODE_DESCRIPTIONS.yolo, }, { value: 'auto', label: PERMISSION_MODE_DISPLAY_NAMES.auto, - description: 'Never interrupts you; everything runs and is decided automatically.', + description: PERMISSION_MODE_DESCRIPTIONS.auto, }, ]; @@ -29,6 +28,7 @@ function isPermissionModeChoice(value: string): value is PermissionMode { export interface PermissionSelectorOptions { readonly currentValue: PermissionMode; + readonly initialValue?: PermissionMode; readonly onSelect: (mode: PermissionMode) => void; readonly onCancel: () => void; } @@ -39,6 +39,7 @@ export class PermissionSelectorComponent extends ChoicePickerComponent { title: 'Select permission mode', options: [...PERMISSION_OPTIONS], currentValue: opts.currentValue, + initialValue: opts.initialValue, onSelect: (value) => { if (isPermissionModeChoice(value)) opts.onSelect(value); }, diff --git a/apps/kimi-code/src/tui/constant/kimi-tui.ts b/apps/kimi-code/src/tui/constant/kimi-tui.ts index 9d9f0ba9479..45c77d177b8 100644 --- a/apps/kimi-code/src/tui/constant/kimi-tui.ts +++ b/apps/kimi-code/src/tui/constant/kimi-tui.ts @@ -4,8 +4,6 @@ export { DEFAULT_OAUTH_PROVIDER_NAME, OAUTH_LOGIN_REQUIRED_CODE, PRODUCT_NAME } export const LLM_NOT_SET_MESSAGE = 'LLM not set, send "/login" to login'; export const NO_ACTIVE_SESSION_MESSAGE = 'No active session. Send /login to login.'; -export const UNCONFIRMED_FILE_CHANGES_WARNING = - 'In this mode, Kimi Code can modify or delete files without your confirmation'; export const CTRL_D_HINT = 'Press Ctrl+D again to exit'; export const CTRL_C_HINT = 'Press Ctrl+C again to exit'; export const MAIN_AGENT_ID = 'main'; diff --git a/apps/kimi-code/src/tui/constant/tips.ts b/apps/kimi-code/src/tui/constant/tips.ts index 266eb10f581..e383e4b458d 100644 --- a/apps/kimi-code/src/tui/constant/tips.ts +++ b/apps/kimi-code/src/tui/constant/tips.ts @@ -38,8 +38,8 @@ export const ALL_TIPS: readonly ToolbarTip[] = [ { text: 'shift+enter: newline' }, { text: 'ctrl+c: cancel' }, { text: '/theme to switch the terminal UI theme' }, - { text: '/never-ask when you want Kimi to handle approvals and keep going unattended' }, - { text: '/ask-when-needed to skip most approvals for trusted batch work, only use it in repos you trust' }, + { text: '/auto when you want Kimi to handle approvals and keep going unattended' }, + { text: '/yolo to skip most approvals for trusted batch work, only use it in repos you trust' }, { text: '/help: show commands' }, { text: '/compact compresses context when it gets long', priority: 2 }, { text: 'ctrl-o to hide or reveal tool output switching between a clean chat view and full execution details', priority: 2 }, diff --git a/apps/kimi-code/src/tui/utils/permission-mode.ts b/apps/kimi-code/src/tui/utils/permission-mode.ts index 9adec263708..6df9eb1d094 100644 --- a/apps/kimi-code/src/tui/utils/permission-mode.ts +++ b/apps/kimi-code/src/tui/utils/permission-mode.ts @@ -5,3 +5,9 @@ export const PERMISSION_MODE_DISPLAY_NAMES: Readonly> = { + manual: 'Auto-read only; everything else needs your approval first.', + yolo: 'Routine edits and commands run automatically; risky actions, questions, and plans still ask.', + auto: 'Never interrupts you; everything runs and is decided automatically.', +}; diff --git a/apps/kimi-code/test/tui/commands/goal.test.ts b/apps/kimi-code/test/tui/commands/goal.test.ts index 50574b30993..f48f2acb325 100644 --- a/apps/kimi-code/test/tui/commands/goal.test.ts +++ b/apps/kimi-code/test/tui/commands/goal.test.ts @@ -18,7 +18,7 @@ import { } from '#/tui/goal-queue-store'; import type { SlashCommandHost } from '#/tui/commands/dispatch'; import { getBuiltInPalette } from '#/tui/theme'; -import { UNCONFIRMED_FILE_CHANGES_WARNING } from '#/tui/constant/kimi-tui'; +import { PERMISSION_MODE_DESCRIPTIONS } from '#/tui/utils/permission-mode'; vi.mock('#/tui/goal-queue-store', () => ({ appendGoalQueueItem: vi.fn(async () => ({ @@ -377,7 +377,7 @@ describe('handleGoalCommand', () => { expect(s.setPermission).toHaveBeenCalledWith('auto'); expect(manualHost.setAppState).toHaveBeenCalledWith({ permissionMode: 'auto' }); expect(manualHost.showNotice).toHaveBeenCalledWith('Permission mode: Never Ask'); - expect(manualHost.showStatus).toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(manualHost.showStatus).toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.auto, 'warning'); expect(manualHost.sendNormalUserInput).toHaveBeenCalledWith('Ship feature X'); }); @@ -397,7 +397,7 @@ describe('handleGoalCommand', () => { }); expect(s.setPermission).not.toHaveBeenCalled(); expect(manualHost.showNotice).not.toHaveBeenCalled(); - expect(manualHost.showStatus).not.toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(manualHost.showStatus).not.toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.auto, 'warning'); expect(manualHost.sendNormalUserInput).toHaveBeenCalledWith('Ship feature X'); }); @@ -417,7 +417,7 @@ describe('handleGoalCommand', () => { expect(s.setPermission).toHaveBeenCalledWith('yolo'); expect(manualHost.setAppState).toHaveBeenCalledWith({ permissionMode: 'yolo' }); expect(manualHost.showNotice).toHaveBeenCalledWith('Permission mode: Ask When Needed'); - expect(manualHost.showStatus).toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(manualHost.showStatus).toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.yolo, 'warning'); }); it('restores the previous permission mode when the goal fails to start', async () => { @@ -440,7 +440,7 @@ describe('handleGoalCommand', () => { // The permissive-mode notice is deferred until the goal starts, so a failed // start leaves no stale notice behind. expect(manualHost.showNotice).not.toHaveBeenCalled(); - expect(manualHost.showStatus).not.toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(manualHost.showStatus).not.toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.yolo, 'warning'); }); it('returns the command to the input box when a Manual-mode goal start is cancelled', async () => { diff --git a/apps/kimi-code/test/tui/commands/registry.test.ts b/apps/kimi-code/test/tui/commands/registry.test.ts index dbf44ec5ef7..163e0acaf15 100644 --- a/apps/kimi-code/test/tui/commands/registry.test.ts +++ b/apps/kimi-code/test/tui/commands/registry.test.ts @@ -194,8 +194,8 @@ describe('built-in slash command registry', () => { 'undo', 'usage', 'version', - 'ask-when-needed', - 'never-ask', + 'yolo', + 'auto', ]), ); }); diff --git a/apps/kimi-code/test/tui/commands/swarm.test.ts b/apps/kimi-code/test/tui/commands/swarm.test.ts index 781f0ee8194..49407b14523 100644 --- a/apps/kimi-code/test/tui/commands/swarm.test.ts +++ b/apps/kimi-code/test/tui/commands/swarm.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest'; import { handleSwarmCommand } from '#/tui/commands/index'; import type { SlashCommandHost } from '#/tui/commands/dispatch'; import { currentTheme } from '#/tui/theme'; -import { UNCONFIRMED_FILE_CHANGES_WARNING } from '#/tui/constant/kimi-tui'; +import { PERMISSION_MODE_DESCRIPTIONS } from '#/tui/utils/permission-mode'; const ENTER = '\r'; const ESCAPE = '\u001B'; @@ -235,7 +235,7 @@ describe('handleSwarmCommand', () => { expect(host.setAppState).toHaveBeenCalledWith({ permissionMode: 'auto' }); expect(host.setAppState).toHaveBeenCalledWith({ swarmMode: true }); expect(host.showNotice).toHaveBeenCalledWith('Permission mode: Never Ask'); - expect(host.showStatus).toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(host.showStatus).toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.auto, 'warning'); expect(host.state.swarmModeEntry).toBe('task'); expectSwarmMarker(host, 'Swarm activated'); }); @@ -256,7 +256,7 @@ describe('handleSwarmCommand', () => { expect(session.setSwarmMode).toHaveBeenCalledWith(true, 'task'); expect(session.setSwarmMode).toHaveBeenCalledTimes(1); expect(host.showNotice).not.toHaveBeenCalled(); - expect(host.showStatus).not.toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(host.showStatus).not.toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.auto, 'warning'); expect(host.state.swarmModeEntry).toBe('task'); expectSwarmMarker(host, 'Swarm activated'); }); @@ -278,7 +278,7 @@ describe('handleSwarmCommand', () => { expect(host.setAppState).toHaveBeenCalledWith({ permissionMode: 'yolo' }); expect(host.setAppState).toHaveBeenCalledWith({ swarmMode: true }); expect(host.showNotice).toHaveBeenCalledWith('Permission mode: Ask When Needed'); - expect(host.showStatus).toHaveBeenCalledWith(UNCONFIRMED_FILE_CHANGES_WARNING, 'warning'); + expect(host.showStatus).toHaveBeenCalledWith(PERMISSION_MODE_DESCRIPTIONS.yolo, 'warning'); expect(host.state.swarmModeEntry).toBe('task'); expectSwarmMarker(host, 'Swarm activated'); }); diff --git a/apps/kimi-code/test/tui/components/dialogs/choice-picker.test.ts b/apps/kimi-code/test/tui/components/dialogs/choice-picker.test.ts index ac4e843c586..c0fac1c1ec1 100644 --- a/apps/kimi-code/test/tui/components/dialogs/choice-picker.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/choice-picker.test.ts @@ -92,10 +92,13 @@ describe('ChoicePickerComponent', () => { const permission = new PermissionSelectorComponent({ currentValue: 'manual', + initialValue: 'yolo', onSelect, onCancel, }); - expect(permission.render(120).map(strip)).toContain(' ❯ Always Ask ← current'); + const permissionOutput = permission.render(120).map(strip); + expect(permissionOutput).toContain(' ❯ Ask When Needed'); + expect(permissionOutput).toContain(' Always Ask ← current'); const settings = new SettingsSelectorComponent({ onSelect, diff --git a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts index aaa76f4302d..b462663a399 100644 --- a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts @@ -40,6 +40,7 @@ import { ThinkingComponent } from '#/tui/components/messages/thinking'; import { WelcomeComponent } from '#/tui/components/chrome/welcome'; import { ModelSelectorComponent } from '#/tui/components/dialogs/model-selector'; import { TabbedModelSelectorComponent } from '#/tui/components/dialogs/tabbed-model-selector'; +import { PermissionSelectorComponent } from '#/tui/components/dialogs/permission-selector'; import { UndoSelectorComponent } from '#/tui/components/dialogs/undo-selector'; import { PluginInstallTrustConfirmComponent, @@ -1885,7 +1886,7 @@ describe('KimiTUI message flow', () => { expect(driver.state.appState.sessionId).toBe(''); }); - it('applies /yolo on session-less and passes the mode to the lazy session (v2 engine)', async () => { + it('applies /yolo session-less via the permission picker and passes the mode to the lazy session (v2 engine)', async () => { const session = makeSession({ id: 'ses-lazy' }); const startupInput: KimiTUIStartupInput = { ...makeStartupInput(), @@ -1894,7 +1895,12 @@ describe('KimiTUI message flow', () => { }; const { driver, harness } = await makeDriver(session, {}, startupInput); - driver.handleUserInput('/yolo on'); + driver.handleUserInput('/yolo'); + + await vi.waitFor(() => { + expect(driver.state.editorContainer.children[0]).toBeInstanceOf(PermissionSelectorComponent); + }); + (driver.state.editorContainer.children[0] as PermissionSelectorComponent).handleInput('\r'); await vi.waitFor(() => { expect(driver.state.appState.permissionMode).toBe('yolo'); @@ -2536,7 +2542,12 @@ command = "vim" const { driver, session, harness } = await makeDriver(); harness.track.mockClear(); - driver.handleUserInput('/yolo on'); + driver.handleUserInput('/yolo'); + + await vi.waitFor(() => { + expect(driver.state.editorContainer.children[0]).toBeInstanceOf(PermissionSelectorComponent); + }); + (driver.state.editorContainer.children[0] as PermissionSelectorComponent).handleInput('\r'); await vi.waitFor(() => { expect(session.setPermission).toHaveBeenCalledWith('yolo'); @@ -2545,9 +2556,9 @@ command = "vim" permissionMode: 'yolo', }); expect(stripSgr(renderTranscript(driver))).toContain( - 'In this mode, Kimi Code can modify or delete files without your confirmation', + 'Routine edits and commands run automatically; risky actions, questions, and plans still ask.', ); - expect(harness.track).toHaveBeenCalledWith('input_command', { command: 'ask-when-needed' }); + expect(harness.track).toHaveBeenCalledWith('input_command', { command: 'yolo' }); expect(harness.track).not.toHaveBeenCalledWith('yolo_toggle', expect.anything()); }); @@ -2766,10 +2777,15 @@ command = "vim" driver.handleUserInput('hello'); driver.state.appState.streamingPhase = 'idle'; - driver.handleUserInput('/auto on'); + driver.handleUserInput('/auto'); + + await vi.waitFor(() => { + expect(driver.state.editorContainer.children[0]).toBeInstanceOf(PermissionSelectorComponent); + }); + (driver.state.editorContainer.children[0] as PermissionSelectorComponent).handleInput('\r'); await vi.waitFor(() => { - expect(stripSgr(renderTranscript(driver))).toContain('Never Ask mode: ON'); + expect(stripSgr(renderTranscript(driver))).toContain('Permission mode: Never Ask'); }); driver.handleUserInput('/undo 10'); @@ -2789,9 +2805,9 @@ command = "vim" const transcript = stripSgr(renderTranscript(driver)); expect(transcript).not.toContain('hello'); expect(transcript).not.toContain('Cannot undo 10 prompts'); - expect(transcript).toContain('Never Ask mode: ON'); + expect(transcript).toContain('Permission mode: Never Ask'); expect(transcript).toContain( - 'In this mode, Kimi Code can modify or delete files without your confirmation', + 'Never interrupts you; everything runs and is decided automatically.', ); expect(driver.state.appState.permissionMode).toBe('auto'); }); diff --git a/docs/en/guides/interaction.md b/docs/en/guides/interaction.md index 1f6bf581b96..cd17ae92f16 100644 --- a/docs/en/guides/interaction.md +++ b/docs/en/guides/interaction.md @@ -33,7 +33,7 @@ Type `/` to open the completion menu — it filters as you type, `Esc` closes it | `/undo` | Undo recent prompts | | `/model` | Switch the model used in the current session | | `/plan` | Toggle Plan mode (plan first, then execute) | -| `/ask-when-needed` | Toggle Ask When Needed mode (routine edits and commands run automatically) | +| `/yolo` | Open the permission mode list with Ask When Needed preselected (routine edits and commands run automatically) | | `/goal` | Start or manage goal mode | | `/help` | Show all commands | @@ -64,9 +64,9 @@ Approvals are not triggered for regular tool calls in Ask When Needed mode, nor **Always Ask mode** (formerly Manual) is the default: read-only operations run automatically, while every other action — editing files, running commands — asks for your confirmation one by one. Use it when you want full control over every change. -**Ask When Needed mode** (formerly YOLO), toggled with `/ask-when-needed`, auto-approves regular tool calls, making it suitable for batch tasks you know are safe. It still asks before sensitive actions — accessing sensitive files such as `.env` or SSH keys, running dangerous commands such as `shutdown` or `rm -rf`, or exiting Plan mode — and the agent can still ask you questions. +**Ask When Needed mode** (formerly YOLO), enabled with `/yolo`, auto-approves regular tool calls, making it suitable for batch tasks you know are safe. It still asks before sensitive actions — accessing sensitive files such as `.env` or SSH keys, running dangerous commands such as `shutdown` or `rm -rf`, or exiting Plan mode — and the agent can still ask you questions. -**Never Ask mode** (formerly Auto), toggled with `/never-ask`, is the fully unattended mode: every tool approval is handled automatically, including sensitive files and plan exits, and the agent never asks you questions — it decides everything on its own. The built-in dangerous-command guard asks for your confirmation before commands such as `shutdown`, `reboot`, or `rm -rf` in Always Ask and Ask When Needed mode; in Never Ask mode these commands run without interruption. +**Never Ask mode** (formerly Auto), enabled with `/auto`, is the fully unattended mode: every tool approval is handled automatically, including sensitive files and plan exits, and the agent never asks you questions — it decides everything on its own. The built-in dangerous-command guard asks for your confirmation before commands such as `shutdown`, `reboot`, or `rm -rf` in Always Ask and Ask When Needed mode; in Never Ask mode these commands run without interruption. ## Mode switching diff --git a/docs/en/guides/use-cases.md b/docs/en/guides/use-cases.md index 13ed05d6398..adf5154a7f0 100644 --- a/docs/en/guides/use-cases.md +++ b/docs/en/guides/use-cases.md @@ -99,7 +99,7 @@ Analyze the access logs in logs/ from the past 7 days. For each API path, comput Research the main dependency injection options for TypeScript (tsyringe, inversify, awilix). Compare them across three dimensions: API style, decorator requirements, and runtime overhead. Give me a recommendation that fits on one page. ``` -For batch tasks you know are safe, use `--yolo` or `/ask-when-needed` to skip approval prompts, or add pre-approved allowlist rules for specific tools in [Configuration files](../configuration/config-files.md#permission). +For batch tasks you know are safe, use `--yolo` or `/yolo` to skip approval prompts, or add pre-approved allowlist rules for specific tools in [Configuration files](../configuration/config-files.md#permission). ## Scheduled tasks and reminders diff --git a/docs/en/reference/slash-commands.md b/docs/en/reference/slash-commands.md index 726578b4aa5..cde6312780a 100644 --- a/docs/en/reference/slash-commands.md +++ b/docs/en/reference/slash-commands.md @@ -47,8 +47,8 @@ Some commands are only available in the idle state. Executing these commands whi | Command | Alias | Description | Always available | | --- | --- | --- | --- | -| `/ask-when-needed [on\|off]` | `/yolo`, `/yes` | Toggle Ask When Needed mode. Without arguments, flips the current state; explicitly passing `on`/`off` forces the setting. When enabled, routine edits and commands run automatically; risky actions, questions, and plans still ask | Yes | -| `/never-ask [on\|off]` | `/auto` | Toggle Never Ask mode. When enabled, it never interrupts you; everything runs and is decided automatically | Yes | +| `/yolo` | `/yes` | Open the permission mode list with Ask When Needed preselected; press `Enter` to confirm. In this mode, routine edits and commands run automatically; risky actions, questions, and plans still ask | Yes | +| `/auto` | — | Open the permission mode list with Never Ask preselected; press `Enter` to confirm. In this mode, Kimi never interrupts you; everything runs and is decided automatically | Yes | | `/plan [on\|off]` | — | Toggle Plan mode. Without arguments, flips the current state; explicitly passing `on`/`off` forces the setting. Simply toggling does not create an empty plan file | Yes | | `/plan clear` | — | Clear the current plan | No | | `/swarm on\|off` | — | Turn swarm mode on or off without sending a prompt. | Yes | @@ -56,7 +56,7 @@ Some commands are only available in the idle state. Executing these commands whi | `/goal [...]` | — | Start or manage an autonomous goal | See below | ::: warning -`/ask-when-needed` skips approval for regular tool calls. Please make sure you understand the potential risks before enabling it. Plan mode exit approval is not bypassed by `/ask-when-needed`; `Bash` inside Plan mode is still subject to the regular `/ask-when-needed` allow rules. +`/yolo` skips approval for regular tool calls. Please make sure you understand the potential risks before enabling it. Plan mode exit approval is not bypassed by `/yolo`; `Bash` inside Plan mode is still subject to the regular `/yolo` allow rules. ::: ## Autonomous Goal diff --git a/docs/zh/guides/interaction.md b/docs/zh/guides/interaction.md index 4d577d9c54d..75b56d3026a 100644 --- a/docs/zh/guides/interaction.md +++ b/docs/zh/guides/interaction.md @@ -33,7 +33,7 @@ Kimi Code CLI 支持在输入框中直接粘贴图片和视频,让 AI 结合 | `/undo` | 撤销最近的提示词 | | `/model` | 切换当前会话使用的模型 | | `/plan` | 切换 Plan 模式(先出计划再动手) | -| `/ask-when-needed` | 切换 "Ask When Needed" 模式(常规修改和命令自动完成) | +| `/yolo` | 打开权限模式列表并预选 "Ask When Needed"(常规修改和命令自动完成) | | `/goal` | 开始或管理目标模式 | | `/help` | 查看全部命令 | @@ -64,9 +64,9 @@ Agent 调用会产生副作用的工具(修改文件、执行命令等)时 **"Always Ask"**(始终询问,原 Manual)是默认模式:只读操作自动放行,修改文件、执行命令等其余操作都会逐一向你确认,适合需要全程掌控每个改动的场景。 -**"Ask When Needed"**(必要时询问,原 YOLO)用 `/ask-when-needed` 切换,自动批准普通工具调用,适合已知安全的批处理任务。敏感操作仍会询问——例如访问 `.env`、SSH 私钥等敏感文件、执行 `shutdown`、`rm -rf` 这类危险命令,或退出 Plan 模式——Agent 也仍可能向你提问。 +**"Ask When Needed"**(必要时询问,原 YOLO)用 `/yolo` 开启,自动批准普通工具调用,适合已知安全的批处理任务。敏感操作仍会询问——例如访问 `.env`、SSH 私钥等敏感文件、执行 `shutdown`、`rm -rf` 这类危险命令,或退出 Plan 模式——Agent 也仍可能向你提问。 -**"Never Ask"**(完全自动,原 Auto)用 `/never-ask` 切换,是完全无人值守模式:所有工具审批自动处理,包括敏感文件和计划退出,且 Agent 不会向你提问,完全由它自己做决定。内置的危险命令拦截会在 "Always Ask" 和 "Ask When Needed" 模式下要求你确认 `shutdown`、`reboot`、`rm -rf` 这类命令;在 "Never Ask" 模式下这些命令直接执行,不再拦截。 +**"Never Ask"**(完全自动,原 Auto)用 `/auto` 开启,是完全无人值守模式:所有工具审批自动处理,包括敏感文件和计划退出,且 Agent 不会向你提问,完全由它自己做决定。内置的危险命令拦截会在 "Always Ask" 和 "Ask When Needed" 模式下要求你确认 `shutdown`、`reboot`、`rm -rf` 这类命令;在 "Never Ask" 模式下这些命令直接执行,不再拦截。 ## 模式切换 diff --git a/docs/zh/guides/use-cases.md b/docs/zh/guides/use-cases.md index cb8a3f7b3ad..9318b94fdee 100644 --- a/docs/zh/guides/use-cases.md +++ b/docs/zh/guides/use-cases.md @@ -99,7 +99,7 @@ src/parser/markdown.ts 目前几乎没有测试。请补一组单元测试,覆 帮我调研一下 TypeScript 里几种主流的依赖注入方案(tsyringe、inversify、awilix),从 API 风格、装饰器依赖、运行时开销三个维度对比,给一份不超过一页的建议。 ``` -对于确定安全的批处理任务,可以用 `--yolo` 或 `/ask-when-needed` 跳过审批,也可以在[配置文件](../configuration/config-files.md#permission)里给特定工具预置白名单规则。 +对于确定安全的批处理任务,可以用 `--yolo` 或 `/yolo` 跳过审批,也可以在[配置文件](../configuration/config-files.md#permission)里给特定工具预置白名单规则。 ## 定时任务与提醒 diff --git a/docs/zh/reference/slash-commands.md b/docs/zh/reference/slash-commands.md index 12226bca60a..887a47251d4 100644 --- a/docs/zh/reference/slash-commands.md +++ b/docs/zh/reference/slash-commands.md @@ -45,8 +45,8 @@ | 命令 | 别名 | 说明 | 随时可用 | | --- | --- | --- | --- | -| `/ask-when-needed [on\|off]` | `/yolo`、`/yes` | 切换 "Ask When Needed" 模式。不带参数时翻转;显式传 `on`/`off` 时强制设置。开启后常规修改和命令自动完成;高危操作、提问和计划仍会问你 | 是 | -| `/never-ask [on\|off]` | `/auto` | 切换 "Never Ask" 模式。开启后完全不打断,所有操作和判断自动完成 | 是 | +| `/yolo` | `/yes` | 打开权限模式列表并预选 "Ask When Needed",按 `Enter` 确认开启。该模式下常规修改和命令自动完成;高危操作、提问和计划仍会问你 | 是 | +| `/auto` | — | 打开权限模式列表并预选 "Never Ask",按 `Enter` 确认开启。该模式下完全不打断,所有操作和判断自动完成 | 是 | | `/plan [on\|off]` | — | 切换 Plan 模式。不带参数时翻转;显式传 `on`/`off` 时强制设置。单纯切换不会创建空计划文件 | 是 | | `/plan clear` | — | 清除当前 plan 方案 | 否 | | `/swarm on\|off` | — | 开启或关闭 swarm mode,但不发送提示词。 | 是 | @@ -54,7 +54,7 @@ | `/goal [...]` | — | 开始或管理目标模式 | 见下文 | ::: warning 注意 -`/ask-when-needed` 会跳过普通工具调用的审批确认,使用前请确保了解可能的风险。Plan 模式的退出审批不会被 `/ask-when-needed` 跳过;Plan 模式下的 `Bash` 也按 `/ask-when-needed` 的普通放行规则处理。 +`/yolo` 会跳过普通工具调用的审批确认,使用前请确保了解可能的风险。Plan 模式的退出审批不会被 `/yolo` 跳过;Plan 模式下的 `Bash` 也按 `/yolo` 的普通放行规则处理。 ::: ## 目标模式