Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
105 changes: 5 additions & 100 deletions apps/kimi-code/src/tui/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -129,102 +129,6 @@ async function applyPlanMode(host: SlashCommandHost, session: Session, enabled:
}
}

export async function handleYoloCommand(host: SlashCommandHost, args: string): Promise<void> {
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<void> {
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<void> {
const session = host.session;
if (session === undefined) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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');
}
}

Expand Down
12 changes: 4 additions & 8 deletions apps/kimi-code/src/tui/commands/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,13 @@ import { handleLoginCommand, handleLogoutCommand } from './auth';
import { handleBtwCommand } from './btw';
import { handleCopyCommand } from './copy';
import {
handleAutoCommand,
handleCompactCommand,
handleEditorCommand,
handleEffortCommand,
handleModelCommand,
handlePlanCommand,
handleSecondaryModelCommand,
handleThemeCommand,
handleYoloCommand,
showExperimentsPanel,
showModelPicker,
showPermissionPicker,
Expand Down Expand Up @@ -82,15 +80,13 @@ export { handleBtwCommand } from './btw';
export { handleCopyCommand } from './copy';
export { handleAddDirCommand } from './add-dir';
export {
handleAutoCommand,
handleCompactCommand,
handleEditorCommand,
handleEffortCommand,
handleModelCommand,
handlePlanCommand,
handleSecondaryModelCommand,
handleThemeCommand,
handleYoloCommand,
showModelPicker,
showExperimentsPanel,
showPermissionPicker,
Expand Down Expand Up @@ -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');
Comment on lines +564 to +568

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add a changeset for the new permission-command behavior

This changes shipped CLI behavior by replacing immediate permission toggles and their arguments with a selector, but the commit adds no changeset describing that user-visible interaction. The existing permission-mode-file-warning.md only covers the earlier warning change, so this behavior will be absent from the generated release notes and versioning metadata unless a dedicated @moonshot-ai/kimi-code changeset is added.

AGENTS.md reference: AGENTS.md:L85-L86

Useful? React with 👍 / 👎.

return;
case 'plan':
await handlePlanCommand(host, args);
Expand Down
6 changes: 3 additions & 3 deletions apps/kimi-code/src/tui/commands/goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -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');
}
}

Expand Down
1 change: 0 additions & 1 deletion apps/kimi-code/src/tui/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export {
handleModelCommand,
handlePlanCommand,
handleThemeCommand,
handleYoloCommand,
showExperimentsPanel,
showModelPicker,
showPermissionPicker,
Expand Down
12 changes: 6 additions & 6 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Comment on lines +149 to +150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the former permission-command aliases

After upgrading, users who enter the previously documented /ask-when-needed or /never-ask commands no longer match a built-in command; resolveSlashCommandInput consequently classifies the input as a normal message and sends it to the agent instead of changing permissions. Retain these names as aliases unless this is released as an explicitly approved major breaking change.

AGENTS.md reference: AGENTS.md:L62-L62

Useful? React with 👍 / 👎.

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',
},
Expand Down
6 changes: 3 additions & 3 deletions apps/kimi-code/src/tui/commands/swarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/components/chrome/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Footer/status bar — multi-line status display at the bottom of the TUI.
*
* Layout:
* Line 1: [ask-when-needed] [plan] <model> <cwd> <git-badge> <shortcut hints>
* Line 1: [Ask When Needed] [plan] <model> <cwd> <git-badge> <shortcut hints>
* Line 2: context: N% (tokens/max)
*/

Expand Down
7 changes: 5 additions & 2 deletions apps/kimi-code/src/tui/components/dialogs/choice-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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,
});
}
Expand Down
11 changes: 6 additions & 5 deletions apps/kimi-code/src/tui/components/dialogs/permission-selector.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
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';

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,
},
];

Expand All @@ -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;
}
Expand All @@ -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);
},
Expand Down
2 changes: 0 additions & 2 deletions apps/kimi-code/src/tui/constant/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/src/tui/constant/tips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
6 changes: 6 additions & 0 deletions apps/kimi-code/src/tui/utils/permission-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ export const PERMISSION_MODE_DISPLAY_NAMES: Readonly<Record<PermissionMode, stri
yolo: 'Ask When Needed',
auto: 'Never Ask',
};

export const PERMISSION_MODE_DESCRIPTIONS: Readonly<Record<PermissionMode, string>> = {
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.',
};
Loading
Loading