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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +103 -29
- package/dist/commands/setup.js +78 -51
- package/dist/dsh/companion.js +177 -0
- package/dist/dsh/index.js +169 -0
- package/dist/hooks/install-paths.js +2 -2
- package/dist/hooks/managed-block.js +86 -20
- package/dist/version.js +1 -1
- package/docs/migration.md +4 -2
- package/docs/models.md +5 -1
- package/docs/registry-releases.md +84 -20
- package/docs/safety.md +25 -10
- package/docs/upstream-2.4.1-certification.md +6 -0
- package/package.json +75 -8
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/commands/setup.js +78 -51
- package/plugins/kimi-codex/dist/dsh/companion.js +177 -0
- package/plugins/kimi-codex/dist/dsh/index.js +169 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
- package/plugins/kimi-codex/dist/hooks/managed-block.js +86 -20
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/LICENSE +201 -0
- package/plugins/kimi-dsh/README.md +83 -0
- package/plugins/kimi-dsh/cordis.patch.yml +45 -0
- package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
- package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
- package/plugins/kimi-dsh/dist/cli-client.js +876 -0
- package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
- package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
- package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
- package/plugins/kimi-dsh/dist/commands/models.js +140 -0
- package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
- package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
- package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
- package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
- package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
- package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
- package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
- package/plugins/kimi-dsh/dist/commands/result.js +66 -0
- package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
- package/plugins/kimi-dsh/dist/commands/review.js +220 -0
- package/plugins/kimi-dsh/dist/commands/setup.js +1074 -0
- package/plugins/kimi-dsh/dist/commands/status.js +27 -0
- package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
- package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
- package/plugins/kimi-dsh/dist/companion.js +97 -0
- package/plugins/kimi-dsh/dist/config.js +22 -0
- package/plugins/kimi-dsh/dist/dsh/companion.js +177 -0
- package/plugins/kimi-dsh/dist/dsh/index.js +169 -0
- package/plugins/kimi-dsh/dist/errors.js +40 -0
- package/plugins/kimi-dsh/dist/git.js +224 -0
- package/plugins/kimi-dsh/dist/help.js +43 -0
- package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
- package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
- package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
- package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
- package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
- package/plugins/kimi-dsh/dist/hooks/managed-block.js +685 -0
- package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
- package/plugins/kimi-dsh/dist/job-id.js +12 -0
- package/plugins/kimi-dsh/dist/job-store.js +659 -0
- package/plugins/kimi-dsh/dist/jobs.js +217 -0
- package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
- package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
- package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
- package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
- package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
- package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
- package/plugins/kimi-dsh/dist/logging.js +22 -0
- package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
- package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
- package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
- package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-dsh/dist/model-command.js +32 -0
- package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
- package/plugins/kimi-dsh/dist/package.json +1 -0
- package/plugins/kimi-dsh/dist/parsing.js +620 -0
- package/plugins/kimi-dsh/dist/paths.js +27 -0
- package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
- package/plugins/kimi-dsh/dist/process-tree.js +479 -0
- package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
- package/plugins/kimi-dsh/dist/render.js +177 -0
- package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
- package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
- package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
- package/plugins/kimi-dsh/dist/session-title.js +293 -0
- package/plugins/kimi-dsh/dist/stream-json.js +346 -0
- package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
- package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
- package/plugins/kimi-dsh/dist/types.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
- package/plugins/kimi-dsh/dist/version.js +7 -0
- package/plugins/kimi-dsh/package.json +78 -0
- package/plugins/kimi-dsh/scripts/companion.sh +37 -0
- 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);
|
|
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
|
|
440
|
-
*
|
|
441
|
-
*
|
|
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
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
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
|
-
|
|
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 (
|
|
518
|
-
|
|
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
|
-
!
|
|
596
|
+
!fieldBeyondBody) {
|
|
531
597
|
ranges.push({ start: i, end: j, command: commandDecoded });
|
|
532
598
|
}
|
|
533
599
|
}
|