kimi-plugin-cc 2.4.5 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +103 -29
  5. package/dist/commands/setup.js +78 -51
  6. package/dist/dsh/companion.js +177 -0
  7. package/dist/dsh/index.js +169 -0
  8. package/dist/hooks/install-paths.js +2 -2
  9. package/dist/hooks/managed-block.js +86 -20
  10. package/dist/version.js +1 -1
  11. package/docs/migration.md +4 -2
  12. package/docs/models.md +5 -1
  13. package/docs/registry-releases.md +84 -20
  14. package/docs/safety.md +25 -10
  15. package/docs/upstream-2.4.1-certification.md +6 -0
  16. package/package.json +75 -8
  17. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  18. package/plugins/kimi-codex/dist/commands/setup.js +78 -51
  19. package/plugins/kimi-codex/dist/dsh/companion.js +177 -0
  20. package/plugins/kimi-codex/dist/dsh/index.js +169 -0
  21. package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
  22. package/plugins/kimi-codex/dist/hooks/managed-block.js +86 -20
  23. package/plugins/kimi-codex/dist/version.js +1 -1
  24. package/plugins/kimi-dsh/LICENSE +201 -0
  25. package/plugins/kimi-dsh/README.md +83 -0
  26. package/plugins/kimi-dsh/cordis.patch.yml +45 -0
  27. package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
  28. package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
  29. package/plugins/kimi-dsh/dist/cli-client.js +876 -0
  30. package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
  31. package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
  32. package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
  33. package/plugins/kimi-dsh/dist/commands/models.js +140 -0
  34. package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
  35. package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
  36. package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
  37. package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
  38. package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
  39. package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
  40. package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
  41. package/plugins/kimi-dsh/dist/commands/result.js +66 -0
  42. package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
  43. package/plugins/kimi-dsh/dist/commands/review.js +220 -0
  44. package/plugins/kimi-dsh/dist/commands/setup.js +1074 -0
  45. package/plugins/kimi-dsh/dist/commands/status.js +27 -0
  46. package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
  47. package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
  48. package/plugins/kimi-dsh/dist/companion.js +97 -0
  49. package/plugins/kimi-dsh/dist/config.js +22 -0
  50. package/plugins/kimi-dsh/dist/dsh/companion.js +177 -0
  51. package/plugins/kimi-dsh/dist/dsh/index.js +169 -0
  52. package/plugins/kimi-dsh/dist/errors.js +40 -0
  53. package/plugins/kimi-dsh/dist/git.js +224 -0
  54. package/plugins/kimi-dsh/dist/help.js +43 -0
  55. package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
  56. package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
  57. package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
  58. package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
  59. package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
  60. package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
  61. package/plugins/kimi-dsh/dist/hooks/managed-block.js +685 -0
  62. package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
  63. package/plugins/kimi-dsh/dist/job-id.js +12 -0
  64. package/plugins/kimi-dsh/dist/job-store.js +659 -0
  65. package/plugins/kimi-dsh/dist/jobs.js +217 -0
  66. package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
  67. package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
  68. package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
  69. package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
  70. package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
  71. package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
  72. package/plugins/kimi-dsh/dist/logging.js +22 -0
  73. package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
  74. package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
  75. package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
  76. package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
  77. package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
  78. package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
  79. package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
  80. package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
  81. package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
  82. package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
  83. package/plugins/kimi-dsh/dist/model-command.js +32 -0
  84. package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
  85. package/plugins/kimi-dsh/dist/package.json +1 -0
  86. package/plugins/kimi-dsh/dist/parsing.js +620 -0
  87. package/plugins/kimi-dsh/dist/paths.js +27 -0
  88. package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
  89. package/plugins/kimi-dsh/dist/process-tree.js +479 -0
  90. package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
  91. package/plugins/kimi-dsh/dist/render.js +177 -0
  92. package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
  93. package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
  94. package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
  95. package/plugins/kimi-dsh/dist/session-title.js +293 -0
  96. package/plugins/kimi-dsh/dist/stream-json.js +346 -0
  97. package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
  98. package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
  99. package/plugins/kimi-dsh/dist/types.js +1 -0
  100. package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
  101. package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
  102. package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
  103. package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
  104. package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
  105. package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
  106. package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
  107. package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
  108. package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
  109. package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
  110. package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
  111. package/plugins/kimi-dsh/dist/version.js +7 -0
  112. package/plugins/kimi-dsh/package.json +78 -0
  113. package/plugins/kimi-dsh/scripts/companion.sh +37 -0
  114. package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,177 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { brandString } from "@deepseek-ai/dsh-brand";
3
+ import { assertUsableCwd, settleRunResult, subprocessRunHandle, } from "@deepseek-ai/dsh-subagent";
4
+ import { preferStableNodePath } from "../hooks/install-paths.js";
5
+ export const DSH_COMPANION_OPERATIONS = [
6
+ "ask",
7
+ "review",
8
+ "challenge",
9
+ "rescue",
10
+ "pursue",
11
+ "swarm",
12
+ "swarm-write",
13
+ ];
14
+ export function companionTextTask(prompt) {
15
+ if (prompt.length === 0 || prompt.some((block) => block.type !== "text")) {
16
+ throw new Error("kimi-plugin-cc: a delegation must contain only text blocks");
17
+ }
18
+ const text = prompt.map((block) => block.type === "text" ? block.text : "").join("\n\n");
19
+ if (text.trim().length === 0) {
20
+ throw new Error("kimi-plugin-cc: a delegation must not be empty");
21
+ }
22
+ return text;
23
+ }
24
+ export function companionArgv(operation, prompt, companionPath, nodeBin = preferStableNodePath(process.execPath), options = {}) {
25
+ const command = operation === "ask" || operation === "review"
26
+ ? [operation]
27
+ : operation === "swarm-write"
28
+ ? ["task", "swarm", "--write"]
29
+ : ["task", operation];
30
+ return [
31
+ nodeBin,
32
+ companionPath,
33
+ ...command,
34
+ ...(operation === "ask" || operation === "rescue" ? ["--fresh"] : []),
35
+ ...(options.model === undefined ? [] : ["-m", options.model]),
36
+ ...(options.thinkingEffort === undefined ? [] : ["--thinking", options.thinkingEffort]),
37
+ ...(options.budget === undefined ? [] : ["--budget", options.budget]),
38
+ ...((operation === "review" || operation === "challenge") && options.reviewBase !== undefined
39
+ ? ["--base", options.reviewBase]
40
+ : []),
41
+ "--",
42
+ prompt,
43
+ ];
44
+ }
45
+ export function companionEnvironment(dataRoot, cwd, env, nodeBin = preferStableNodePath(process.execPath)) {
46
+ return {
47
+ ...env,
48
+ KIMI_PLUGIN_CC_DATA: dataRoot,
49
+ KIMI_PLUGIN_CC_HOST_ID: "dsh",
50
+ KIMI_PLUGIN_CC_NODE_BIN: nodeBin,
51
+ KIMI_PLUGIN_CC_WORKSPACE_CWD: cwd,
52
+ CLAUDE_PLUGIN_DATA: undefined,
53
+ PLUGIN_DATA: undefined,
54
+ CLAUDE_PLUGIN_ROOT: undefined,
55
+ PLUGIN_ROOT: undefined,
56
+ KIMI_PLUGIN_CC_SHELL_LAUNCH: undefined,
57
+ KIMI_PLUGIN_CC_MCP_WORKSPACE: undefined,
58
+ KIMI_PLUGIN_CC_WORKSPACE_ROOT: undefined,
59
+ KIMI_PLUGIN_CC_HOOK_SCRIPT: undefined,
60
+ KIMI_PLUGIN_CC_SKIP_HOOK_CHECK: undefined,
61
+ KIMI_PLUGIN_CC_SKIP_VERSION_PROBE: undefined,
62
+ KIMI_CODE_TRUST_WORKSPACE: undefined,
63
+ };
64
+ }
65
+ export async function startCompanionRun(request, operation, spec) {
66
+ const prompt = companionTextTask(request.prompt);
67
+ if (request.signal.aborted) {
68
+ throw new Error("kimi-plugin-cc: delegation was aborted before companion startup");
69
+ }
70
+ const cwd = assertUsableCwd("kimi-plugin-cc", "child cwd", request.cwd ?? process.cwd());
71
+ const termination = new AbortController();
72
+ let cancelled = false;
73
+ const requestCancel = () => {
74
+ cancelled = true;
75
+ if (!termination.signal.aborted) {
76
+ termination.abort(new Error("kimi-plugin-cc: delegation cancelled"));
77
+ }
78
+ };
79
+ const onAbort = () => requestCancel();
80
+ request.signal.addEventListener("abort", onAbort, { once: true });
81
+ let child;
82
+ try {
83
+ const nodeBin = preferStableNodePath(process.execPath);
84
+ child = spec.spawn({
85
+ argv: companionArgv(operation, prompt, spec.companionPath, nodeBin, spec),
86
+ cwd,
87
+ stdio: {
88
+ stdin: "ignore",
89
+ stdout: { maxBytes: spec.maxOutputBytes },
90
+ stderr: "inherit",
91
+ },
92
+ graceMs: spec.disposeGraceMs,
93
+ signal: termination.signal,
94
+ env: companionEnvironment(spec.dataRoot, cwd, spec.env, nodeBin),
95
+ });
96
+ }
97
+ catch (error) {
98
+ request.signal.removeEventListener("abort", onAbort);
99
+ throw error;
100
+ }
101
+ let diagnostic;
102
+ const result = settleRunResult({
103
+ attempt: async () => {
104
+ let outcome;
105
+ try {
106
+ outcome = await child.done;
107
+ }
108
+ catch (error) {
109
+ diagnostic = `Kimi ${operation} companion failed before producing a terminal exit`;
110
+ throw error;
111
+ }
112
+ const collected = child.collected.stdout?.readFrom(0);
113
+ const text = collected?.text.trimEnd() ?? "";
114
+ if (!cancelled && outcome.exitCode === 0 && collected && !collected.lossy && text.trim().length > 0) {
115
+ return { output: [{ type: "text", text }], stopReason: "completed" };
116
+ }
117
+ const facts = [
118
+ `exit code: ${outcome.exitCode ?? "none"}`,
119
+ `signal: ${outcome.signal ?? "none"}`,
120
+ ...(collected?.lossy ? ["stdout truncated"] : []),
121
+ ...(text.trim().length === 0 ? ["stdout empty"] : []),
122
+ ];
123
+ diagnostic = `Kimi ${operation} companion failed (${facts.join("; ")})`;
124
+ return { output: [], diagnostic, stopReason: cancelled ? "aborted" : "error" };
125
+ },
126
+ collectOutput: () => [],
127
+ collectDiagnostic: () => diagnostic,
128
+ cancelled: () => cancelled,
129
+ signal: request.signal,
130
+ onAbort,
131
+ });
132
+ return subprocessRunHandle({
133
+ id: brandString(randomUUID()),
134
+ result,
135
+ signal: request.signal,
136
+ onAbort,
137
+ requestCancel,
138
+ teardown: async () => {
139
+ child.terminate();
140
+ await child.waitForExit();
141
+ await child.done.catch(() => { });
142
+ },
143
+ });
144
+ }
145
+ export async function runCompanionCommand(argv, cwd, spec, signal) {
146
+ const nodeBin = preferStableNodePath(process.execPath);
147
+ const child = spec.spawn({
148
+ argv: [nodeBin, spec.companionPath, ...argv],
149
+ cwd,
150
+ stdio: {
151
+ stdin: "ignore",
152
+ stdout: { maxBytes: spec.maxOutputBytes },
153
+ stderr: { maxBytes: spec.maxOutputBytes },
154
+ },
155
+ graceMs: spec.disposeGraceMs,
156
+ signal,
157
+ env: companionEnvironment(spec.dataRoot, cwd, spec.env, nodeBin),
158
+ });
159
+ try {
160
+ const outcome = await child.done;
161
+ await child.waitForExit();
162
+ const stdout = child.collected.stdout?.readFrom(0);
163
+ const stderr = child.collected.stderr?.readFrom(0);
164
+ return {
165
+ exitCode: outcome.exitCode,
166
+ signal: outcome.signal,
167
+ stdout: stdout?.text ?? "",
168
+ stderr: stderr?.text ?? "",
169
+ truncated: Boolean(stdout?.lossy || stderr?.lossy),
170
+ };
171
+ }
172
+ catch (error) {
173
+ child.terminate();
174
+ await child.waitForExit().catch(() => false);
175
+ throw error;
176
+ }
177
+ }
@@ -0,0 +1,169 @@
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { CommandDefinitionId } from "@deepseek-ai/dsh-commands/brand";
5
+ import z from "@deepseek-ai/schemastery";
6
+ import { NO_START_CAPABILITIES, } from "@deepseek-ai/dsh-subagent";
7
+ import { DSH_COMPANION_OPERATIONS, runCompanionCommand, startCompanionRun, } from "./companion.js";
8
+ import { parseDurationMs } from "../parsing.js";
9
+ export const name = "kimi-plugin-cc";
10
+ export const inject = ["subagents", "subprocess"];
11
+ export const DSH_TOOL_GUIDANCE = [
12
+ "Kimi delegation tools have different write boundaries:",
13
+ "kimi_ask, kimi_review, and kimi_challenge are read-only; kimi_swarm is a read-only parallel review.",
14
+ "kimi_rescue and kimi_pursue can edit the selected workspace; kimi_pursue continues toward a bounded goal.",
15
+ "kimi_swarm_write edits a temporary worktree and returns a patch without applying it.",
16
+ ].join(" ");
17
+ const DEFAULT_DISPOSE_GRACE_MS = 6_000;
18
+ const DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
19
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
20
+ const COMPANION_PATH = fileURLToPath(new URL("../../dist/companion.js", import.meta.url));
21
+ export const Config = z.object({
22
+ dataRoot: z.string().min(1),
23
+ env: z.dict(z.string()).default({}),
24
+ disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
25
+ maxOutputBytes: z.number().default(DEFAULT_MAX_OUTPUT_BYTES),
26
+ model: z.string().min(1),
27
+ thinkingEffort: z.string().min(1),
28
+ budget: z.string().min(1),
29
+ reviewBase: z.string().min(1),
30
+ });
31
+ export function defaultDshDataRoot(env = process.env) {
32
+ const explicit = env.KIMI_PLUGIN_CC_DATA;
33
+ if (explicit !== undefined) {
34
+ if (!path.isAbsolute(explicit)) {
35
+ throw new Error("kimi-plugin-cc: KIMI_PLUGIN_CC_DATA must be absolute");
36
+ }
37
+ return path.normalize(explicit);
38
+ }
39
+ const dshHome = env.DSH_HOME ?? path.join(env.HOME || homedir(), ".dsh");
40
+ if (!path.isAbsolute(dshHome)) {
41
+ throw new Error("kimi-plugin-cc: DSH_HOME must be absolute");
42
+ }
43
+ return path.join(dshHome, "plugin-data", "kimi-plugin-cc");
44
+ }
45
+ function resolveConfig(config) {
46
+ const resolved = {
47
+ dataRoot: config.dataRoot ?? defaultDshDataRoot(),
48
+ env: config.env ?? {},
49
+ disposeGraceMs: config.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS,
50
+ maxOutputBytes: config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
51
+ model: config.model,
52
+ thinkingEffort: config.thinkingEffort,
53
+ budget: config.budget,
54
+ reviewBase: config.reviewBase,
55
+ };
56
+ if (!path.isAbsolute(resolved.dataRoot)) {
57
+ throw new Error("kimi-plugin-cc: dataRoot must be absolute");
58
+ }
59
+ if (!Number.isFinite(resolved.disposeGraceMs) || resolved.disposeGraceMs < DEFAULT_DISPOSE_GRACE_MS || resolved.disposeGraceMs > MAX_TIMER_DELAY_MS) {
60
+ throw new Error(`kimi-plugin-cc: disposeGraceMs must be at least ${DEFAULT_DISPOSE_GRACE_MS} and no greater than ${MAX_TIMER_DELAY_MS}`);
61
+ }
62
+ if (!Number.isSafeInteger(resolved.maxOutputBytes) || resolved.maxOutputBytes <= 0) {
63
+ throw new Error("kimi-plugin-cc: maxOutputBytes must be a positive safe integer");
64
+ }
65
+ for (const [field, value] of [["model", resolved.model], ["thinkingEffort", resolved.thinkingEffort], ["reviewBase", resolved.reviewBase]]) {
66
+ if (value?.startsWith("-"))
67
+ throw new Error(`kimi-plugin-cc: ${field} must not start with '-'`);
68
+ }
69
+ if (resolved.budget !== undefined)
70
+ parseDurationMs(resolved.budget, "--budget", "dsh.config", "safety");
71
+ return resolved;
72
+ }
73
+ class KimiCompanionProvider {
74
+ name;
75
+ operation;
76
+ spec;
77
+ capabilities = NO_START_CAPABILITIES;
78
+ inheritsParentContext = false;
79
+ constructor(name, operation, spec) {
80
+ this.name = name;
81
+ this.operation = operation;
82
+ this.spec = spec;
83
+ }
84
+ start(request) {
85
+ return startCompanionRun(request, this.operation, this.spec);
86
+ }
87
+ }
88
+ function commandArguments(rawInput) {
89
+ const trimmed = rawInput.trim();
90
+ return trimmed.length === 0 ? [] : trimmed.split(/\s+/);
91
+ }
92
+ function commandText(result) {
93
+ return [result.stdout.trimEnd(), result.stderr.trimEnd()]
94
+ .filter((text) => text.length > 0)
95
+ .join("\n");
96
+ }
97
+ export function apply(ctx, config) {
98
+ const resolved = resolveConfig(config);
99
+ const spec = {
100
+ companionPath: COMPANION_PATH,
101
+ dataRoot: resolved.dataRoot,
102
+ env: resolved.env,
103
+ disposeGraceMs: resolved.disposeGraceMs,
104
+ maxOutputBytes: resolved.maxOutputBytes,
105
+ model: resolved.model,
106
+ thinkingEffort: resolved.thinkingEffort,
107
+ budget: resolved.budget,
108
+ reviewBase: resolved.reviewBase,
109
+ spawn: (spawnSpec) => ctx.subprocess.spawn(spawnSpec),
110
+ };
111
+ for (const operation of DSH_COMPANION_OPERATIONS) {
112
+ ctx.subagents.registerProvider(new KimiCompanionProvider(`kimi-${operation}`, operation, spec));
113
+ }
114
+ ctx.inject(["systemPrompt"], (promptCtx) => {
115
+ promptCtx.systemPrompt.section({
116
+ name: "kimi-plugin-cc:tool-boundaries",
117
+ order: promptCtx.systemPrompt.getSectionOrder("TOOL_SUBAGENT"),
118
+ text: () => DSH_TOOL_GUIDANCE,
119
+ });
120
+ });
121
+ ctx.inject(["commands"], (commandCtx) => {
122
+ const execute = async (invocation, argv) => {
123
+ const directories = commandCtx.get("workingDirectory");
124
+ if (directories === undefined) {
125
+ return { kind: "error", text: "Kimi commands require the DSH working-directory service." };
126
+ }
127
+ const cwd = await directories.ensure(invocation.agent, invocation.signal);
128
+ const result = await runCompanionCommand(argv, cwd, spec, invocation.signal);
129
+ const text = commandText(result);
130
+ if (result.truncated) {
131
+ return { kind: "error", text: `${text}\nKimi command output exceeded the configured byte limit.`.trim() };
132
+ }
133
+ if (result.exitCode !== 0) {
134
+ return {
135
+ kind: "error",
136
+ text: `${text}\nKimi command failed (exit code: ${result.exitCode ?? "none"}; signal: ${result.signal ?? "none"}).`.trim(),
137
+ };
138
+ }
139
+ return { kind: "success", text: text.length > 0 ? text : "Kimi command completed." };
140
+ };
141
+ commandCtx.commands.register({
142
+ definitionId: CommandDefinitionId("kimi-plugin-cc/dsh/setup"),
143
+ name: "kimi-setup",
144
+ description: "Install, check, or remove the DSH-owned Kimi PreToolUse hook; list configured models",
145
+ input: { hint: "[--check|--uninstall|--models [--json]]" },
146
+ recordInput: false,
147
+ handler: (invocation) => {
148
+ const args = commandArguments(invocation.rawInput);
149
+ const valid = args.length === 0
150
+ || (args.length === 1 && ["--check", "--uninstall", "--models"].includes(args[0]))
151
+ || (args.length === 2 && args[0] === "--models" && args[1] === "--json");
152
+ if (!valid) {
153
+ return { kind: "error", text: "Usage: /kimi-setup [--check|--uninstall|--models [--json]]" };
154
+ }
155
+ return execute(invocation, ["setup", ...args]);
156
+ },
157
+ });
158
+ for (const command of ["status", "result", "cancel"]) {
159
+ commandCtx.commands.register({
160
+ definitionId: CommandDefinitionId(`kimi-plugin-cc/dsh/${command}`),
161
+ name: `kimi-${command}`,
162
+ description: `${command === "cancel" ? "Cancel" : command === "status" ? "Inspect" : "Return"} a companion Kimi job`,
163
+ input: { hint: "[job-id]" },
164
+ recordInput: false,
165
+ handler: (invocation) => execute(invocation, [command, ...commandArguments(invocation.rawInput)]),
166
+ });
167
+ }
168
+ });
169
+ }
@@ -410,7 +410,7 @@ export function hostIdFromHookCommand(command) {
410
410
  }
411
411
  /**
412
412
  * True only for a canonical two-single-quoted-token command whose script is
413
- * `approval-hook.js` under a kimi-plugin-cc/kimi-marketplace install tree.
413
+ * `approval-hook.js` under a kimi-plugin-cc/dsh-kimi-plugin-cc/kimi-marketplace install tree.
414
414
  * This strict ownership test protects hand-rolled and non-canonical user hooks
415
415
  * from orphan pruning.
416
416
  */
@@ -424,5 +424,5 @@ export function isOurApprovalHookCommand(decodedCommand) {
424
424
  const normalized = parsed.hookScript.replace(/\\/g, "/");
425
425
  // Require a real path segment, not an arbitrary substring, so a user hook in
426
426
  // a similarly named wrapper directory is never misclassified and pruned.
427
- return normalized.includes("/kimi-plugin-cc/") || normalized.includes("/kimi-marketplace/");
427
+ return normalized.includes("/kimi-plugin-cc/") || normalized.includes("/dsh-kimi-plugin-cc/") || normalized.includes("/kimi-marketplace/");
428
428
  }
@@ -45,10 +45,80 @@ const MATCHER_LINE_RE = /^matcher\s*=/;
45
45
  /** `timeout = <int>` — the only other key our managed [[hooks]] table emits. */
46
46
  const TIMEOUT_LINE_RE = /^timeout\s*=\s*\d+\s*$/;
47
47
  const HOOKS_TABLE_RE = /^\[\[hooks\]\]\s*$/;
48
+ // Recognize comment lines lexically, not text inside TOML string values.
49
+ // Whole-document parsing remains the authority for TOML validity.
50
+ function scanTomlLines(contents) {
51
+ const markers = new Set();
52
+ const tables = new Set();
53
+ let state = "plain";
54
+ let line = 0;
55
+ let blankPrefix = true;
56
+ for (let i = 0; i < contents.length; i += 1) {
57
+ const c = contents[i];
58
+ if (c === "\n") {
59
+ line += 1;
60
+ blankPrefix = true;
61
+ continue;
62
+ }
63
+ const onlyWhitespaceBefore = blankPrefix;
64
+ if (c !== " " && c !== "\t" && c !== "\r")
65
+ blankPrefix = false;
66
+ if (state === "plain") {
67
+ if (c === "[" && onlyWhitespaceBefore)
68
+ tables.add(line);
69
+ if (c === "#") {
70
+ if (onlyWhitespaceBefore) {
71
+ const end = contents.indexOf("\n", i);
72
+ const comment = contents.slice(i, end < 0 ? contents.length : end).trim();
73
+ if (BEGIN_LINE_RE.test(comment) || END_LINE_RE.test(comment))
74
+ markers.add(line);
75
+ }
76
+ const end = contents.indexOf("\n", i);
77
+ if (end < 0)
78
+ break;
79
+ i = end - 1;
80
+ }
81
+ else if (c === '"' || c === "'") {
82
+ const triple = contents.slice(i, i + 3) === c.repeat(3);
83
+ state = c === '"' ? (triple ? "multibasic" : "basic") : (triple ? "multiliteral" : "literal");
84
+ if (triple)
85
+ i += 2;
86
+ }
87
+ }
88
+ else {
89
+ const basic = state === "basic" || state === "multibasic";
90
+ const quote = basic ? '"' : "'";
91
+ if (basic && c === "\\") {
92
+ if (contents[i + 1] === "\n") {
93
+ line += 1;
94
+ blankPrefix = true;
95
+ }
96
+ i += 1;
97
+ continue;
98
+ }
99
+ if (c !== quote)
100
+ continue;
101
+ if (state === "basic" || state === "literal")
102
+ state = "plain";
103
+ else if (contents.slice(i, i + 3) === quote.repeat(3)) {
104
+ while (contents[i + 1] === quote)
105
+ i += 1;
106
+ state = "plain";
107
+ }
108
+ }
109
+ }
110
+ return { markers, tables };
111
+ }
112
+ export function markerCommentLines(contents) {
113
+ return scanTomlLines(contents).markers;
114
+ }
48
115
  /** Locate every BEGIN/END marker line, in order, with its parsed host id. */
49
116
  function findMarkers(lines) {
50
117
  const hits = [];
118
+ const comments = markerCommentLines(lines.join("\n"));
51
119
  for (let i = 0; i < lines.length; i += 1) {
120
+ if (!comments.has(i))
121
+ continue;
52
122
  const trimmed = lines[i].trim();
53
123
  const begin = BEGIN_LINE_RE.exec(trimmed);
54
124
  if (begin !== null) {
@@ -66,7 +136,7 @@ function findMarkers(lines) {
66
136
  }
67
137
  /**
68
138
  * Pair BEGIN/END markers into well-formed blocks. Return the first orphan
69
- * (BEGIN-without-END or END-without-BEGIN); either is a hard error for install.
139
+ * (BEGIN-without-END or END-without-BEGIN); verification fails closed on either.
70
140
  */
71
141
  function pairBlocks(lines) {
72
142
  const markers = findMarkers(lines);
@@ -83,6 +153,8 @@ function pairBlocks(lines) {
83
153
  // end
84
154
  if (open === null)
85
155
  return { orphan: "END-without-BEGIN" };
156
+ if (open.hostId !== marker.hostId)
157
+ return { orphan: "BEGIN-without-END" };
86
158
  const body = validateBlockBody(lines.slice(open.index + 1, marker.index));
87
159
  blocks.push({
88
160
  hostId: open.hostId,
@@ -436,13 +508,13 @@ export function evaluateInstalled(contents, expectedCommand, opts) {
436
508
  * The deletion range `[start, end)` is conservative: only the contiguous
437
509
  * non-blank body after `[[hooks]]`, stopping at a blank line, table header, or
438
510
  * managed marker so adjacent user content is preserved. A separate full-span
439
- * scan rejects matcher lines recognized by MATCHER_LINE_RE to avoid half-cutting
440
- * a table. That scan is deliberately heuristic and does not claim to recognize
441
- * every quoted or dotted TOML matcher spelling. `command` is decoded for the
442
- * caller's ownership checks.
511
+ * scan refuses any further TOML field before the next table, including quoted
512
+ * and dotted keys, so pruning cannot attach those fields to another table.
513
+ * `command` is decoded for the caller's ownership checks.
443
514
  */
444
515
  export function findBareApprovalHookTables(contents) {
445
516
  const lines = contents.split("\n").map((line) => line.replace(/\r$/, ""));
517
+ const tableLines = scanTomlLines(contents).tables;
446
518
  const managed = new Set();
447
519
  const paired = pairBlocks(lines);
448
520
  if ("blocks" in paired) {
@@ -455,7 +527,7 @@ export function findBareApprovalHookTables(contents) {
455
527
  for (let i = 0; i < lines.length; i += 1) {
456
528
  if (managed.has(i))
457
529
  continue;
458
- if (!HOOKS_TABLE_RE.test(lines[i].trim()))
530
+ if (!tableLines.has(i) || !HOOKS_TABLE_RE.test(lines[i].trim()))
459
531
  continue;
460
532
  // Collect the contiguous table body.
461
533
  let j = i + 1;
@@ -500,22 +572,16 @@ export function findBareApprovalHookTables(contents) {
500
572
  }
501
573
  j += 1;
502
574
  }
503
- // A blank line does NOT end a TOML table; membership continues to the next
504
- // header or EOF. The deletion range nevertheless stops at the blank to
505
- // preserve adjacent user content. Scan the remaining span for matcher lines
506
- // this scanner recognizes and reject the table rather than half-cutting it:
507
- // matcher is schema-valid but can disable the hook at runtime. Foreign keys
508
- // and other TOML spellings are handled by whole-file schema validation and
509
- // the parser-based installed checks, not by this prune heuristic.
510
- let matcherBeyondBody = false;
575
+ // Blank lines and marker comments do not end a TOML table. Never leave any
576
+ // field behind to be rebound to a preceding table after pruning, regardless
577
+ // of the field's spelling or whether it is valid for a hook.
578
+ let fieldBeyondBody = false;
511
579
  for (let k = j; k < lines.length; k += 1) {
512
580
  const t = lines[k].trim();
513
- // The current table's TOML span ends at the next table header or a
514
- // managed marker; a matcher past that point belongs to a different table.
515
- if (t.startsWith("[") || BEGIN_LINE_RE.test(t) || END_LINE_RE.test(t))
581
+ if (tableLines.has(k))
516
582
  break;
517
- if (MATCHER_LINE_RE.test(t)) {
518
- matcherBeyondBody = true;
583
+ if (t.length > 0 && !t.startsWith("#")) {
584
+ fieldBeyondBody = true;
519
585
  break;
520
586
  }
521
587
  }
@@ -527,7 +593,7 @@ export function findBareApprovalHookTables(contents) {
527
593
  commandDecoded.length > 0 &&
528
594
  hasPreToolUseEvent &&
529
595
  !hasMatcher &&
530
- !matcherBeyondBody) {
596
+ !fieldBeyondBody) {
531
597
  ranges.push({ start: i, end: j, command: commandDecoded });
532
598
  }
533
599
  }
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  // and AGENTS.md on every release. A future improvement would read this from
5
5
  // package.json at build time, but that adds a build-step dependency we
6
6
  // don't want yet.
7
- export const KIMI_PLUGIN_CC_VERSION = "2.4.5";
7
+ export const KIMI_PLUGIN_CC_VERSION = "2.5.2";
package/docs/migration.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Compatibility and upgrades
2
2
 
3
- Plugin 2.4.5 certifies Kimi Code CLI 2.4.1 for all eight operations, alongside exact native-v2 versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, `2.3.2`, `2.3.3`, and `2.4.0`. Later patches and minor releases are not accepted automatically. The npm distribution adds an installation channel; it does not change this compatibility table.
3
+ Plugin 2.5.1 certifies Kimi Code CLI 2.4.1 for all eight operations, alongside exact native-v2 versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, `2.3.2`, `2.3.3`, and `2.4.0`. Later patches and minor releases are not accepted automatically. The npm distribution adds an installation channel; it does not change this compatibility table.
4
+
5
+ Plugin 2.5.1 also adds DeepSeek Harness as a third host with all seven operations and a DSH-specific bundle, data root, and managed hook id. Existing Claude Code and Codex certification, job stores, and host-scoped hooks are unchanged; no migration is required for those hosts. New DSH installations follow the [README DSH section](../README.md#install-in-deepseek-harness), including `/kimi-setup` on that host.
4
6
 
5
7
  Explicitly pinned legacy-v1 binaries remain supported within `KIMI_TESTED_MINORS`, through 0.41.x, subject to each operation's minimum version. The [runtime table](../runtime/kimi-engine.ts) is the authority.
6
8
 
@@ -8,7 +10,7 @@ Use the [README installation and update steps](../README.md) for Claude Code or
8
10
 
9
11
  ## Plugin versions and upgrades
10
12
 
11
- Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.4.5 certifies exact CLI 2.4.1; the nearby numbers do not imply version alignment. Published versions and tags are never reused. Every upstream patch requires its own certification. The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
13
+ Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.5.1 certifies exact CLI 2.4.1; the nearby numbers do not imply version alignment. Published versions and tags are never reused. Every upstream patch requires its own certification. The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
12
14
 
13
15
  ## CLI 2.3.2, 2.3.3, 2.4.0 and 2.4.1 swarm activation
14
16
 
package/docs/models.md CHANGED
@@ -6,7 +6,7 @@ configured model for one task without changing that default.
6
6
  ## See configured models
7
7
 
8
8
  In Claude Code, run `/kimi:setup --models`. In Codex, ask `$kimi-setup` to list
9
- models. The underlying command is:
9
+ models. In DSH, run `/kimi-setup --models` (or `--models --json`). The underlying command is:
10
10
 
11
11
  ```sh
12
12
  "<plugin-root>/scripts/companion.sh" setup --models
@@ -35,6 +35,8 @@ several, and guide setup when there is none. They do not guess model IDs or
35
35
  silently substitute the default after an error. An explicit `-m` remains a
36
36
  pass-through alias, so upstream can resolve it or report an error.
37
37
 
38
+ DSH activations are one-shot and do not expose per-call model fields. Set `model` in the bundle's `kimi-plugin-cc` profile row to use one exact configured alias for every delegation from that profile; omit it to use the native Kimi default. This does not change the saved default or add fallback selection.
39
+
38
40
  Omitting `-m` uses the default for a **fresh** session. A resumed session keeps
39
41
  its previous model unless you supply `-m`. The optional review gate also uses
40
42
  the default unless `KIMI_PLUGIN_CC_REVIEW_GATE_MODEL` explicitly selects an alias.
@@ -87,6 +89,8 @@ including `kosongConfig/configSection.ts`, `envOverlay.ts`, and
87
89
 
88
90
  Use `--thinking off`, `--thinking high`, or `--thinking=<effort>` before the prompt. Values are lowercased and must match `/^[a-z][a-z0-9-]{0,31}$/` (a plugin-owned syntax bound; a future upstream token outside it is rejected before upstream sees it); upstream per-model `supportEfforts`, not a plugin enum, determines supported values. `--no-thinking` is an error: use `--thinking off`. In CLI 2.2.0 only the three Moonshot `kimi*` provider definitions are trait-driven. BYO providers ignore the request silently; an always-thinking model may remain thinking.
89
91
 
92
+ DSH forwards the profile's `thinkingEffort` value as `--thinking <effort>` on every delegation. Omission preserves the ambient operator value; the same provider and observability limits apply.
93
+
90
94
  Jobs persist nullable `thinking_effort`; status and `result --json` expose `thinking_effort_requested`, not observed effort. Detached ask/rescue workers replay the request. A resume without `--thinking` exports no new override (any ambient operator value remains). No config.toml write occurs.
91
95
 
92
96
  The review gate requests `off` by default. `KIMI_PLUGIN_CC_REVIEW_GATE_THINKING=inherit` exports no override; another non-empty value is lowercased and must match the same regex. Unset or empty means `off`; an invalid value makes the gate skip visibly. The gate remains fail-open on its 8 s timeout: a BYO provider or always-thinking model can ignore the request, and even an honoured request does not guarantee completion within the budget.