From 86a128efea7c0136557fb75ef4b5bf2605518d2e Mon Sep 17 00:00:00 2001 From: KimiSwitch Dev Date: Thu, 3 Sep 2026 10:10:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=20kimi-code=200.40.1?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20flag=20=E6=B8=85=E5=8D=95/=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=E8=AF=AD=E4=B9=89/=E5=8D=B1=E9=99=A9?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=AE=88=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flag 清单 9→10:新增 file_history、search_worker;secondary-model 0.40.1 起默认开启(defaultEnabled) - env 探测补齐为 12 项元组(10 flag + master + legacy),修正此前 tower/subagent_fork/wait_for/auto_session_title 四项漏探 - 优先级语义跟随上游 flagService:单 flag env > [experimental] 显式值 > master env(仅强制开)> 默认。master env 不再锁定 UI;setExperimentalFlag 增加 explicitFalse 路径,关闭默认开/master 强开的 flag 时写字面 false - 权限设置新增 dangerous_command_guard 开关(默认开,写 [permission] 蛇形键;env KIMI_CODE_DANGEROUS_COMMAND_GUARD 运行时覆盖 config) - 新增 11 个测试(注册表/写语义/守卫读写) --- src-tauri/src/commands.rs | 40 +++++++++---- src/components/AgentSettingsPanel.tsx | 19 ++++++ src/components/SubagentSettingsPage.tsx | 66 +++++++++++++++------ src/i18n/en.ts | 11 +++- src/i18n/zh.ts | 11 +++- src/lib/agent-settings.test.ts | 72 +++++++++++++++++++++++ src/lib/agent-settings.ts | 44 +++++++++++--- src/lib/subagent-settings.test.ts | 77 +++++++++++++++++++++++++ src/lib/subagent-settings.ts | 36 ++++++++++-- src/types/index.ts | 10 +++- 10 files changed, 339 insertions(+), 47 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index fcd4598..59f09da 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1339,26 +1339,42 @@ pub async fn kimi_oauth_poll( // --------------------------------------------------------------------------- /// Read the Kimi Code environment variables that are currently set (non-empty) -/// in this process's environment. The experimental-feature vars are used by the -/// frontend to mark config.toml `[experimental]` toggles as locked: env vars -/// outrank the config file in Kimi Code's flag resolution. `KIMI_CODE_LEGACY_FLAG` -/// is probed too — the AgentSettingsPanel uses it to decide whether to +/// in this process's environment. Every `[experimental]` feature env var is +/// probed — the frontend uses a per-feature env var to mark the matching +/// config.toml `[experimental]` toggle as locked, since a single-feature env +/// var outranks the config file in Kimi Code's flag resolution. The master +/// `KIMI_CODE_EXPERIMENTAL_FLAG` (0.40.1+: only force-ONs flags without an +/// explicit config entry; never locks a toggle) and `KIMI_CODE_LEGACY_FLAG` +/// are probed too — the latter lets AgentSettingsPanel decide whether to /// dual-write the v1 engine's loop_control keys. /// +/// The flag id → env var mapping mirrors `EXPERIMENTAL_FLAGS` in +/// src/lib/subagent-settings.ts; keep both lists in sync. +/// /// Truthy values per the CLI: `1` / `true` / `yes` / `on` (case-insensitive); /// the raw values are returned and the truthy check happens on the frontend. #[tauri::command] pub fn get_experimental_env_status() -> HashMap { - const VARS: [&str; 6] = [ - "KIMI_CODE_EXPERIMENTAL_FLAG", - "KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL", - "KIMI_CODE_EXPERIMENTAL_TOOL_SELECT", - "KIMI_CODE_EXPERIMENTAL_PERSISTENCE_MINIDB_READMODEL", - "KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL", - "KIMI_CODE_LEGACY_FLAG", + const VARS: [(&str, &str); 12] = [ + ("secondary-model", "KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL"), + ("tool-select", "KIMI_CODE_EXPERIMENTAL_TOOL_SELECT"), + ( + "persistence_minidb_readmodel", + "KIMI_CODE_EXPERIMENTAL_PERSISTENCE_MINIDB_READMODEL", + ), + ("tower", "KIMI_CODE_EXPERIMENTAL_TOWER"), + ("subagent_fork", "KIMI_CODE_EXPERIMENTAL_SUBAGENT_FORK"), + ("wait_for", "KIMI_CODE_EXPERIMENTAL_WAIT_FOR"), + ("auto_session_title", "KIMI_CODE_EXPERIMENTAL_AUTO_SESSION_TITLE"), + ("remote-control", "KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL"), + ("search_worker", "KIMI_CODE_EXPERIMENTAL_SEARCH_WORKER"), + ("file_history", "KIMI_CODE_EXPERIMENTAL_FILE_HISTORY"), + // Non-flag probes consumed by the frontend: + ("master", "KIMI_CODE_EXPERIMENTAL_FLAG"), + ("legacy", "KIMI_CODE_LEGACY_FLAG"), ]; let mut out = HashMap::new(); - for name in VARS { + for (_, name) in VARS { if let Ok(value) = std::env::var(name) { if !value.trim().is_empty() { out.insert(name.to_string(), value); diff --git a/src/components/AgentSettingsPanel.tsx b/src/components/AgentSettingsPanel.tsx index ed108ee..d5f2363 100644 --- a/src/components/AgentSettingsPanel.tsx +++ b/src/components/AgentSettingsPanel.tsx @@ -157,6 +157,25 @@ export function AgentSettingsPanel({ rawOther, onChange }: AgentSettingsPanelPro + {/* kimi-code 0.40.1 `[permission] dangerous_command_guard`. The env + var KIMI_CODE_DANGEROUS_COMMAND_GUARD (literal "true"/"false") + outranks this config at runtime; no env-lock UI here (deliberate + minimal scope). Absent key = upstream default on. */} + + update({ + permission: { + ...settings.permission, + dangerous_command_guard: checked, + }, + }) + } + /> +

+ {t("permissionDangerousGuardDesc")} +

{(settings.permission?.rules ?? []).map((rule, idx) => (
diff --git a/src/components/SubagentSettingsPage.tsx b/src/components/SubagentSettingsPage.tsx index 8b11a6f..ea76d1a 100644 --- a/src/components/SubagentSettingsPage.tsx +++ b/src/components/SubagentSettingsPage.tsx @@ -57,6 +57,8 @@ const FLAG_LABELS: Record explicit [experimental] value > + * master env (force-on only) > upstream default. The master env no + * longer locks the toggle — only the flag's own env var does. */ + const secondaryEnabled = isFlagLockedByEnv(env, secondaryFlag) + ? forcedEnvValue(env, secondaryFlag) + : isExperimentalFlagSet(rawOther, secondaryFlag.id) + ? flags["secondary-model"] === true + : masterOn + ? true + : (secondaryFlag.defaultEnabled ?? false); + const secondaryLocked = isFlagLockedByEnv(env, secondaryFlag); const aliasList = Object.keys(models).sort(); @@ -302,17 +309,24 @@ export function SubagentSettingsPage({ const renderFlagRow = (flag: ExperimentalFlagDef) => { const labels = FLAG_LABELS[flag.id]; - const locked = masterOn || isFlagLockedByEnv(env, flag); - // An explicitly written flag (true or false) wins; an absent flag falls - // back to the upstream default (`defaultEnabled`). + // Only the flag's own env var locks the toggle. The master env force-ons + // undefined flags but an explicit config entry outranks it, so the user + // can still flip values here while it is set. + const locked = isFlagLockedByEnv(env, flag); + // Resolution order mirrors kimi-code 0.40.1 flagService: + // single-flag env > explicit [experimental] value (true *or* false) > + // master env (on-only) > upstream default (`defaultEnabled`). const explicitlySet = isExperimentalFlagSet(rawOther, flag.id); - const on = masterOn - ? true - : isFlagLockedByEnv(env, flag) - ? forcedEnvValue(env, flag) - : explicitlySet - ? flags[flag.id] === true + const on = locked + ? forcedEnvValue(env, flag) + : explicitlySet + ? flags[flag.id] === true + : masterOn + ? true : (flag.defaultEnabled ?? false); + // Turning the flag off only sticks via an explicit `false` when the + // fallback (master env or the upstream default) would otherwise keep it on. + const explicitFalse = masterOn || flag.defaultEnabled === true; return (
@@ -339,7 +353,9 @@ export function SubagentSettingsPage({ disabled={locked} ariaLabel={t(labels.name)} onChange={(checked) => - onChange(setExperimentalFlag(rawOther, flag.id, checked)) + onChange( + setExperimentalFlag(rawOther, flag.id, checked, { explicitFalse }) + ) } />
@@ -560,6 +576,16 @@ export function SubagentSettingsPage({
+
+ {t(FLAG_LABELS[secondaryFlag.id].name)} + {!secondaryLocked && + !isExperimentalFlagSet(rawOther, secondaryFlag.id) && + secondaryFlag.defaultEnabled && ( + + {t("flagDefaultOn")} + + )} +
{t(FLAG_LABELS[secondaryFlag.id].desc)} {secondaryFlag.envVar} @@ -575,7 +601,11 @@ export function SubagentSettingsPage({ disabled={secondaryLocked} ariaLabel={t(FLAG_LABELS[secondaryFlag.id].name)} onChange={(checked) => - onChange(setExperimentalFlag(rawOther, secondaryFlag.id, checked)) + onChange( + setExperimentalFlag(rawOther, secondaryFlag.id, checked, { + explicitFalse: masterOn || secondaryFlag.defaultEnabled === true, + }) + ) } />
diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 1861483..aab8bd6 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -151,6 +151,9 @@ export const enTranslations: Record = { permissionAllow: "Allow", permissionDeny: "Deny", permissionAsk: "Ask", + permissionDangerousGuard: "Dangerous Command Guard", + permissionDangerousGuardDesc: + "When on (default): Auto mode refuses dangerous commands (rm -rf, shutdown, dd of=, ...) and other modes always prompt; turning it off restores the previous behavior. Env KIMI_CODE_DANGEROUS_COMMAND_GUARD overrides this config", addRule: "+ Add rule", addCommonRules: "Add common rules", hooks: "Lifecycle Hooks", @@ -477,9 +480,15 @@ export const enTranslations: Record = { flagAutoSessionTitleDesc: "Generate session titles automatically via the managed chat_title tool", flagRemoteControl: "Remote Control", flagRemoteControlDesc: "Enable the experimental remote control feature", + flagSearchWorker: "Search Worker", + flagSearchWorkerDesc: "Run the kap-server global search backend in a background worker host", + flagFileHistory: "File History", + flagFileHistoryDesc: + "Snapshot edited files before/after each turn (last 5 turns across 30 sessions, stored in the session data dir) for real whole-file diffs", flagDefaultOn: "On by default", lockedByEnv: "Locked by env var", - masterEnvOnHint: "KIMI_CODE_EXPERIMENTAL_FLAG is set: all experimental features are forced on by the env var", + masterEnvOnHint: + "KIMI_CODE_EXPERIMENTAL_FLAG is set: flags without an explicit [experimental] entry are forced on; explicit entries in config.toml still win", secondaryModelSection: "Subagent Model (Secondary Model)", secondaryModelDisabledHint: "The experimental flag \"Subagent Secondary Model\" is off and this config will not take effect — enable the toggle above first", diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index a50dbdc..351cbab 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -149,6 +149,9 @@ export const zhTranslations = { permissionAllow: "允许", permissionDeny: "拒绝", permissionAsk: "询问", + permissionDangerousGuard: "危险命令防护", + permissionDangerousGuardDesc: + "开启(默认)时:Auto 模式直接拒绝危险命令(rm -rf、shutdown、dd of= 等),其它模式强制询问;关闭后回归旧行为。环境变量 KIMI_CODE_DANGEROUS_COMMAND_GUARD 会覆盖本配置", addRule: "+ 添加规则", addCommonRules: "添加常用规则", hooks: "生命周期钩子", @@ -472,9 +475,15 @@ export const zhTranslations = { flagAutoSessionTitleDesc: "通过托管 chat_title 工具自动生成会话标题", flagRemoteControl: "远程控制", flagRemoteControlDesc: "启用实验性远程控制功能", + flagSearchWorker: "搜索 Worker", + flagSearchWorkerDesc: "kap-server 全局搜索的后台 worker 宿主服务", + flagFileHistory: "文件历史", + flagFileHistoryDesc: + "记录每轮编辑文件的前后快照(最近 30 个会话×最近 5 轮,存于会话数据目录),用于真实的整文件 diff", flagDefaultOn: "默认开启", lockedByEnv: "被环境变量锁定", - masterEnvOnHint: "KIMI_CODE_EXPERIMENTAL_FLAG 已设置:全部实验功能被环境变量强制开启", + masterEnvOnHint: + "KIMI_CODE_EXPERIMENTAL_FLAG 已设置:未显式配置的实验项将被强制开启;config.toml 中的显式配置优先生效", secondaryModelSection: "子代理模型(次主力模型)", secondaryModelDisabledHint: "实验开关「子代理次主力模型」未启用,本配置不会生效——请先在上方打开对应开关", diff --git a/src/lib/agent-settings.test.ts b/src/lib/agent-settings.test.ts index c111f03..21e9ee9 100644 --- a/src/lib/agent-settings.test.ts +++ b/src/lib/agent-settings.test.ts @@ -131,4 +131,76 @@ describe("loop_control — v1/v2 key handling", () => { expect(s.loop_control?.max_attempts_per_step).toBe(4); expect(s.loop_control?.max_retries_per_step).toBe(4); }); +}); + +// --------------------------------------------------------------------------- +// [permission] dangerous_command_guard (kimi-code 0.40.1) — absent key means +// upstream default ON; an explicit false must survive even with no rules +// --------------------------------------------------------------------------- + +describe("permission.dangerous_command_guard", () => { + it("reads as undefined when the key is absent (default on)", () => { + expect( + getAgentSettings({}).permission?.dangerous_command_guard + ).toBeUndefined(); + expect( + getAgentSettings({ permission: { rules: [] } }).permission + ?.dangerous_command_guard + ).toBeUndefined(); + }); + + it("reads an explicit true / false", () => { + expect( + getAgentSettings({ permission: { dangerous_command_guard: false } }) + .permission?.dangerous_command_guard + ).toBe(false); + expect( + getAgentSettings({ permission: { dangerous_command_guard: true } }) + .permission?.dangerous_command_guard + ).toBe(true); + }); + + it("writes guard=false even with no rules (section kept)", () => { + const next = setAgentSettings( + {}, + { permission: { rules: [], dangerous_command_guard: false } } + ) as { permission?: Record }; + expect(next.permission).toEqual({ dangerous_command_guard: false }); + }); + + it("writes guard together with rules", () => { + const rules = [{ decision: "allow" as const, pattern: "Read" }]; + const next = setAgentSettings( + {}, + { permission: { rules, dangerous_command_guard: true } } + ) as { permission?: Record }; + expect(next.permission).toEqual({ + rules, + dangerous_command_guard: true, + }); + }); + + it("a rules-only update carries the existing guard through", () => { + const raw = { permission: { dangerous_command_guard: false } }; + const next = setAgentSettings(raw, { + permission: { rules: [{ decision: "deny" as const, pattern: "Bash" }] }, + }) as { permission?: Record }; + expect(next.permission?.dangerous_command_guard).toBe(false); + expect(next.permission?.rules).toHaveLength(1); + }); + + it("omits an empty rules array; keeps an explicit guard on plain saves", () => { + const raw = { permission: { dangerous_command_guard: true } }; + const off = setAgentSettings(raw, { + permission: { rules: [], dangerous_command_guard: false }, + }) as { permission?: Record }; + expect(off.permission).toEqual({ dangerous_command_guard: false }); + expect(off.permission).not.toHaveProperty("rules"); + // An explicit true is materialized on plain saves (harmless, and it + // documents the state the UI toggle shows). + const on = setAgentSettings(raw, {}) as { + permission?: Record; + }; + expect(on.permission).toEqual({ dangerous_command_guard: true }); + }); }); \ No newline at end of file diff --git a/src/lib/agent-settings.ts b/src/lib/agent-settings.ts index f45d40f..27b8688 100644 --- a/src/lib/agent-settings.ts +++ b/src/lib/agent-settings.ts @@ -65,6 +65,10 @@ export function getAgentSettings(rawOther: unknown): AgentSettings { if (thinking.effort === "max") { thinking.effort = "high"; } + const sectionPermission = getSection( + rawOther, + "permission" + ); return { thinking, loop_control: loop, @@ -73,9 +77,15 @@ export function getAgentSettings(rawOther: unknown): AgentSettings { ...getSection(rawOther, "background"), }, permission: { - rules: - getSection(rawOther, "permission")?.rules ?? - [], + rules: sectionPermission?.rules ?? [], + // `dangerous_command_guard` stays undefined unless the config carries + // an explicit boolean — upstream defaults it to ON (kimi-code 0.40.1), + // so an absent key means "on". The env var + // KIMI_CODE_DANGEROUS_COMMAND_GUARD (literal "true"/"false" only) + // outranks this config value at kimi-code runtime. + ...(typeof sectionPermission?.dangerous_command_guard === "boolean" + ? { dangerous_command_guard: sectionPermission.dangerous_command_guard } + : {}), }, hooks: getSection(rawOther, "hooks") ?? [], }; @@ -88,13 +98,20 @@ export function setAgentSettings( ): unknown { const root = { ...asRecord(rawOther) }; const current = getAgentSettings(rawOther); + // Explicit guard value wins over the carried-over one; undefined keeps the + // key absent (upstream default = on). + const guard = + patch.permission?.dangerous_command_guard ?? + current.permission?.dangerous_command_guard; + const permission: NonNullable = { + rules: patch.permission?.rules ?? current.permission?.rules ?? [], + ...(typeof guard === "boolean" ? { dangerous_command_guard: guard } : {}), + }; const next: AgentSettings = { thinking: { ...current.thinking, ...patch.thinking }, loop_control: { ...current.loop_control, ...patch.loop_control }, background: { ...current.background, ...patch.background }, - permission: { - rules: patch.permission?.rules ?? current.permission?.rules ?? [], - }, + permission, hooks: patch.hooks ?? current.hooks ?? [], }; @@ -115,8 +132,19 @@ export function setAgentSettings( if (next.thinking) root.thinking = next.thinking; if (next.loop_control) root.loop_control = next.loop_control; if (next.background) root.background = next.background; - if (next.permission?.rules && next.permission.rules.length > 0) { - root.permission = next.permission; + // Keep `[permission]` when it carries rules *or* an explicit + // dangerous_command_guard — dropping the section would silently lose a + // guard=false write (absent key = upstream default on). An empty rules + // array is omitted rather than written as `rules = []`. + const hasRules = !!permission.rules && permission.rules.length > 0; + const hasGuard = typeof permission.dangerous_command_guard === "boolean"; + if (hasRules || hasGuard) { + root.permission = { + ...(hasRules ? { rules: permission.rules } : {}), + ...(hasGuard + ? { dangerous_command_guard: permission.dangerous_command_guard } + : {}), + }; } else { delete root.permission; } diff --git a/src/lib/subagent-settings.test.ts b/src/lib/subagent-settings.test.ts index bbdd94b..8beb40b 100644 --- a/src/lib/subagent-settings.test.ts +++ b/src/lib/subagent-settings.test.ts @@ -1,8 +1,12 @@ import { describe, expect, it } from "vitest"; import { + EXPERIMENTAL_FLAGS, + getExperimentalFlags, getSecondaryModel, getSubagentModelPool, + isExperimentalFlagSet, removeSubagentPoolEntry, + setExperimentalFlag, setSecondaryModelOnly, setSubagentDefault, setSubagentForce, @@ -449,3 +453,76 @@ describe("validateSubagentPool", () => { expect(errors).toContainEqual({ key: "forceExcludesModels" }); }); }); + +// --------------------------------------------------------------------------- +// [experimental] — flag registry (kimi-code 0.40.1) + write semantics +// --------------------------------------------------------------------------- + +describe("experimental flag registry", () => { + it("mirrors the 0.40.1 v2 registry (10 flags incl. file_history)", () => { + expect(EXPERIMENTAL_FLAGS.map((f) => f.id)).toEqual([ + "secondary-model", + "tool-select", + "persistence_minidb_readmodel", + "tower", + "subagent_fork", + "wait_for", + "auto_session_title", + "remote-control", + "search_worker", + "file_history", + ]); + const fh = EXPERIMENTAL_FLAGS.find((f) => f.id === "file_history"); + expect(fh?.envVar).toBe("KIMI_CODE_EXPERIMENTAL_FILE_HISTORY"); + expect(fh?.defaultEnabled).toBeUndefined(); + }); + + it("secondary-model is on by default since 0.40.1", () => { + expect( + EXPERIMENTAL_FLAGS.find((f) => f.id === "secondary-model")?.defaultEnabled + ).toBe(true); + }); +}); + +describe("setExperimentalFlag", () => { + const rawExp = (section: Record) => ({ + experimental: section, + }); + + it("enabling writes true; disabling a default-off flag deletes the key", () => { + const on = setExperimentalFlag({}, "tool-select", true) as { + experimental: Record; + }; + expect(on.experimental["tool-select"]).toBe(true); + const off = setExperimentalFlag(on, "tool-select", false) as Record< + string, + unknown + >; + expect(off).not.toHaveProperty("experimental"); // empty section dropped + }); + + it("disabling a default-on flag with explicitFalse writes a literal false", () => { + const next = setExperimentalFlag( + rawExp({ wait_for: true }), + "wait_for", + false, + { explicitFalse: true } + ) as { experimental: Record }; + expect(next.experimental.wait_for).toBe(false); + expect(isExperimentalFlagSet(next, "wait_for")).toBe(true); + expect(getExperimentalFlags(next).wait_for).toBe(false); + }); + + it("an explicit false coexists with other flags and keeps the section", () => { + const next = setExperimentalFlag( + rawExp({ "secondary-model": false }), + "tower", + true, + { explicitFalse: true } + ) as { experimental: Record }; + expect(next.experimental).toEqual({ + "secondary-model": false, + tower: true, + }); + }); +}); diff --git a/src/lib/subagent-settings.ts b/src/lib/subagent-settings.ts index f20ce88..7406262 100644 --- a/src/lib/subagent-settings.ts +++ b/src/lib/subagent-settings.ts @@ -36,10 +36,16 @@ export interface ExperimentalFlagDef { /** Known experimental flags — mirrors the kimi-code v2 flag registry * (the per-feature flag.ts files under packages/agent-core-v2/src: * secondary-model, tool-select, persistence_minidb_readmodel, tower, - * subagent_fork, wait_for, auto_session_title, remote-control). `acp-v2` was - * removed upstream and is dropped here. */ + * subagent_fork, wait_for, auto_session_title, remote-control, + * search_worker, file_history). `acp-v2` was removed upstream and is + * dropped here. Keep the env-var list in sync with + * `get_experimental_env_status` in src-tauri/src/commands.rs. */ export const EXPERIMENTAL_FLAGS: ExperimentalFlagDef[] = [ - { id: "secondary-model", envVar: "KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL" }, + { + id: "secondary-model", + envVar: "KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL", + defaultEnabled: true, // on by default since kimi-code 0.40.1 + }, { id: "tool-select", envVar: "KIMI_CODE_EXPERIMENTAL_TOOL_SELECT" }, { id: "persistence_minidb_readmodel", @@ -58,11 +64,21 @@ export const EXPERIMENTAL_FLAGS: ExperimentalFlagDef[] = [ envVar: "KIMI_CODE_EXPERIMENTAL_AUTO_SESSION_TITLE", }, { id: "remote-control", envVar: "KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL" }, + { + id: "search_worker", + envVar: "KIMI_CODE_EXPERIMENTAL_SEARCH_WORKER", + defaultEnabled: true, + }, + { id: "file_history", envVar: "KIMI_CODE_EXPERIMENTAL_FILE_HISTORY" }, ]; export const EXPERIMENTAL_MASTER_ENV = "KIMI_CODE_EXPERIMENTAL_FLAG"; -/** Whether the master env var is set to a truthy value (locks every flag on). */ +/** Whether the master env var is set to a truthy value. Since kimi-code + * 0.40.1 it only force-ONs flags that have *no* explicit `[experimental]` + * entry (an explicit config value — true or false — outranks it, and the + * master env can never force a flag off). It locks no UI toggles; only a + * flag's own single-feature env var does. */ export function isMasterEnvOn(env: ExperimentalEnvStatus | null): boolean { return isTruthyEnv(env?.[EXPERIMENTAL_MASTER_ENV]); } @@ -105,16 +121,24 @@ export function isExperimentalFlagSet(rawOther: unknown, id: string): boolean { ); } -/** Set one flag in `[experimental]`. Removing all flags drops the section. */ +/** Set one flag in `[experimental]`. Removing all flags drops the section. + * Pass `explicitFalse` when deleting the key would NOT express "off" — + * i.e. the flag is on by upstream default (`defaultEnabled`) or the master + * env force-ons undefined flags — and write a literal `false` instead, + * which outranks both under the kimi-code 0.40.1 priority + * (single env > explicit config > master env > default). */ export function setExperimentalFlag( rawOther: unknown, id: string, - enabled: boolean + enabled: boolean, + opts?: { explicitFalse?: boolean } ): unknown { const root = { ...asRecord(rawOther) }; const flags = { ...getExperimentalFlags(rawOther) }; if (enabled) { flags[id] = true; + } else if (opts?.explicitFalse) { + flags[id] = false; } else { delete flags[id]; } diff --git a/src/types/index.ts b/src/types/index.ts index 3d3996f..844ca0f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -149,7 +149,15 @@ export interface AgentSettings { thinking?: ThinkingConfig; loop_control?: LoopControlConfig; background?: BackgroundConfig; - permission?: { rules?: PermissionRule[] }; + permission?: { + rules?: PermissionRule[]; + /** kimi-code 0.40.1 `[permission]` key. Default true when the key is + * absent: Auto mode refuses dangerous commands (rm -rf, shutdown, + * dd of=, …) and other modes always prompt; false restores the + * previous behavior. Env KIMI_CODE_DANGEROUS_COMMAND_GUARD (literal + * "true"/"false" only) outranks this config. */ + dangerous_command_guard?: boolean; + }; hooks?: Hook[]; }