kimi-plugin-cc 2.4.5 → 2.5.1
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 +14 -0
- package/README.md +42 -26
- package/dist/dsh/companion.js +174 -0
- package/dist/dsh/index.js +156 -0
- package/dist/hooks/install-paths.js +2 -2
- package/dist/version.js +1 -1
- package/docs/models.md +5 -1
- package/docs/registry-releases.md +20 -17
- package/docs/safety.md +22 -9
- package/docs/upstream-2.4.1-certification.md +6 -0
- package/package.json +40 -6
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
- package/plugins/kimi-codex/dist/dsh/index.js +156 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/LICENSE +201 -0
- package/plugins/kimi-dsh/README.md +80 -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 +1047 -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 +174 -0
- package/plugins/kimi-dsh/dist/dsh/index.js +156 -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 +619 -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 +48 -0
- package/plugins/kimi-dsh/scripts/companion.sh +37 -0
- package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { executeRescueJob } from "./rescue.js";
|
|
3
|
+
import { executeAskJob } from "./ask.js";
|
|
4
|
+
export async function runWorker(argv, context) {
|
|
5
|
+
const [kind, jobId] = argv;
|
|
6
|
+
if (!jobId || (kind !== "rescue" && kind !== "ask")) {
|
|
7
|
+
throw new RuntimeError("INVALID_COMMAND", "worker expects the form: worker rescue <job-id> or worker ask <job-id>.", "worker");
|
|
8
|
+
}
|
|
9
|
+
if (kind === "rescue") {
|
|
10
|
+
const encodedPrompt = context.env.KIMI_PLUGIN_CC_RESCUE_PROMPT_B64;
|
|
11
|
+
if (!encodedPrompt) {
|
|
12
|
+
throw new RuntimeError("MISSING_RESCUE_PROMPT", "Background rescue prompt is missing.", "worker");
|
|
13
|
+
}
|
|
14
|
+
const prompt = Buffer.from(encodedPrompt, "base64").toString("utf8");
|
|
15
|
+
const reusedSession = context.env.KIMI_PLUGIN_CC_RESCUE_REUSED_SESSION === "1";
|
|
16
|
+
await executeRescueJob(jobId, prompt, context, { workerPid: process.pid, reusedSession });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const encodedPrompt = context.env.KIMI_PLUGIN_CC_ASK_PROMPT_B64;
|
|
20
|
+
if (!encodedPrompt) {
|
|
21
|
+
throw new RuntimeError("MISSING_ASK_PROMPT", "Background ask prompt is missing.", "worker");
|
|
22
|
+
}
|
|
23
|
+
const prompt = Buffer.from(encodedPrompt, "base64").toString("utf8");
|
|
24
|
+
await executeAskJob(jobId, prompt, context, { workerPid: process.pid });
|
|
25
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { runRecoverSwarm } from "./commands/recover-swarm.js";
|
|
2
|
+
import { companionHelp } from "./help.js";
|
|
3
|
+
import { runModelInventory } from "./commands/models.js";
|
|
4
|
+
import { runCancel } from "./commands/cancel.js";
|
|
5
|
+
import { runReplay } from "./commands/replay.js";
|
|
6
|
+
import { runRepairSessions } from "./commands/repair-sessions.js";
|
|
7
|
+
import { runResult } from "./commands/result.js";
|
|
8
|
+
import { renderSetupResult, runSetup } from "./commands/setup.js";
|
|
9
|
+
import { runStatus } from "./commands/status.js";
|
|
10
|
+
import { runWorker } from "./commands/worker.js";
|
|
11
|
+
import { formatError, RuntimeError } from "./errors.js";
|
|
12
|
+
import { resolveKimiHome } from "./kimi-home.js";
|
|
13
|
+
import { runModelCommand } from "./model-command.js";
|
|
14
|
+
import { runMcpChild } from "./mcp/supervised-child.js";
|
|
15
|
+
import { runMcpServer } from "./mcp/server.js";
|
|
16
|
+
async function main(argv) {
|
|
17
|
+
const help = companionHelp(argv);
|
|
18
|
+
if (help !== undefined) {
|
|
19
|
+
process.stdout.write(`${help}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const [command, ...rest] = argv;
|
|
23
|
+
const cwd = process.env.KIMI_PLUGIN_CC_WORKSPACE_CWD || process.cwd();
|
|
24
|
+
const context = {
|
|
25
|
+
cwd,
|
|
26
|
+
env: {
|
|
27
|
+
...process.env,
|
|
28
|
+
KIMI_CODE_HOME: command === "mcp" ? process.env.KIMI_CODE_HOME : resolveKimiHome(process.env, cwd),
|
|
29
|
+
},
|
|
30
|
+
stdout: process.stdout,
|
|
31
|
+
stderr: process.stderr,
|
|
32
|
+
};
|
|
33
|
+
const modelResult = await runModelCommand(argv, context);
|
|
34
|
+
if (modelResult !== undefined) {
|
|
35
|
+
context.stdout.write(modelResult);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
switch (command) {
|
|
39
|
+
case "setup": {
|
|
40
|
+
if (rest.includes("--models")) {
|
|
41
|
+
const args = [...rest];
|
|
42
|
+
args.splice(args.indexOf("--models"), 1);
|
|
43
|
+
context.stdout.write(`${await runModelInventory(args, context)}\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const result = await runSetup(rest, context);
|
|
47
|
+
context.stdout.write(`${renderSetupResult(result)}\n`);
|
|
48
|
+
// A failed probe means the hook did not actually deny when exercised —
|
|
49
|
+
// enforcement is NOT in place, whichever action produced it. This must be
|
|
50
|
+
// nonzero for `install` too, not just `check`: since v1.9.0 an agent may
|
|
51
|
+
// run setup and then RETRY the refused command, and it keys that decision
|
|
52
|
+
// on the exit code. Exiting 0 after a failed probe would walk it straight
|
|
53
|
+
// past a proven-dead hook (a byte-exact command + a readable-but-broken
|
|
54
|
+
// script passes `verifyHookInstalled`, since only the probe can tell that
|
|
55
|
+
// the script cannot deny — e.g. a truncated `approval-hook.js`, which has
|
|
56
|
+
// happened here via iCloud sync, or a half-extracted plugin cache).
|
|
57
|
+
// `probe: "skipped"` (uninstall) is deliberately untouched.
|
|
58
|
+
if (result.probe === "failed") {
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case "mcp":
|
|
64
|
+
await runMcpServer(context);
|
|
65
|
+
return;
|
|
66
|
+
case "mcp-child":
|
|
67
|
+
context.stdout.write(await runMcpChild(rest, context));
|
|
68
|
+
return;
|
|
69
|
+
case "status":
|
|
70
|
+
context.stdout.write(await runStatus(rest, context));
|
|
71
|
+
return;
|
|
72
|
+
case "result":
|
|
73
|
+
context.stdout.write(await runResult(rest, context));
|
|
74
|
+
return;
|
|
75
|
+
case "cancel":
|
|
76
|
+
context.stdout.write(await runCancel(rest, context));
|
|
77
|
+
return;
|
|
78
|
+
case "replay":
|
|
79
|
+
context.stdout.write(await runReplay(rest, context));
|
|
80
|
+
return;
|
|
81
|
+
case "recover-swarm":
|
|
82
|
+
context.stdout.write(await runRecoverSwarm(rest, context));
|
|
83
|
+
return;
|
|
84
|
+
case "repair-sessions":
|
|
85
|
+
context.stdout.write(await runRepairSessions(rest, context));
|
|
86
|
+
return;
|
|
87
|
+
case "worker":
|
|
88
|
+
await runWorker(rest, context);
|
|
89
|
+
return;
|
|
90
|
+
default:
|
|
91
|
+
throw new RuntimeError("INVALID_COMMAND", `Unknown or missing companion subcommand: ${command ?? "<none>"}. Expected one of setup, review, task, ask, status, result, cancel, replay, repair-sessions, recover-swarm.`, "companion");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
95
|
+
process.stderr.write(`${formatError(error)}\n`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
const DEFAULT_CONFIG = {
|
|
3
|
+
reviewGateEnabled: false,
|
|
4
|
+
};
|
|
5
|
+
export async function readPluginConfig(paths) {
|
|
6
|
+
try {
|
|
7
|
+
const contents = await readFile(paths.configPath, "utf8");
|
|
8
|
+
const parsed = JSON.parse(contents);
|
|
9
|
+
return {
|
|
10
|
+
reviewGateEnabled: parsed.reviewGateEnabled ?? DEFAULT_CONFIG.reviewGateEnabled,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code === "ENOENT") {
|
|
15
|
+
return DEFAULT_CONFIG;
|
|
16
|
+
}
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function writePluginConfig(paths, config) {
|
|
21
|
+
await writeFile(paths.configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
22
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
export const DSH_COMPANION_OPERATIONS = [
|
|
5
|
+
"ask",
|
|
6
|
+
"review",
|
|
7
|
+
"challenge",
|
|
8
|
+
"rescue",
|
|
9
|
+
"pursue",
|
|
10
|
+
"swarm",
|
|
11
|
+
"swarm-write",
|
|
12
|
+
];
|
|
13
|
+
export function companionTextTask(prompt) {
|
|
14
|
+
if (prompt.length === 0 || prompt.some((block) => block.type !== "text")) {
|
|
15
|
+
throw new Error("kimi-plugin-cc: a delegation must contain only text blocks");
|
|
16
|
+
}
|
|
17
|
+
const text = prompt.map((block) => block.type === "text" ? block.text : "").join("\n\n");
|
|
18
|
+
if (text.trim().length === 0) {
|
|
19
|
+
throw new Error("kimi-plugin-cc: a delegation must not be empty");
|
|
20
|
+
}
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
export function companionArgv(operation, prompt, companionPath, nodeBin = process.execPath, options = {}) {
|
|
24
|
+
const command = operation === "ask" || operation === "review"
|
|
25
|
+
? [operation]
|
|
26
|
+
: operation === "swarm-write"
|
|
27
|
+
? ["task", "swarm", "--write"]
|
|
28
|
+
: ["task", operation];
|
|
29
|
+
return [
|
|
30
|
+
nodeBin,
|
|
31
|
+
companionPath,
|
|
32
|
+
...command,
|
|
33
|
+
...(operation === "ask" || operation === "rescue" ? ["--fresh"] : []),
|
|
34
|
+
...(options.model === undefined ? [] : ["-m", options.model]),
|
|
35
|
+
...(options.thinkingEffort === undefined ? [] : ["--thinking", options.thinkingEffort]),
|
|
36
|
+
...(options.budget === undefined ? [] : ["--budget", options.budget]),
|
|
37
|
+
...((operation === "review" || operation === "challenge") && options.reviewBase !== undefined
|
|
38
|
+
? ["--base", options.reviewBase]
|
|
39
|
+
: []),
|
|
40
|
+
"--",
|
|
41
|
+
prompt,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
export function companionEnvironment(dataRoot, cwd, env, nodeBin = process.execPath) {
|
|
45
|
+
return {
|
|
46
|
+
...env,
|
|
47
|
+
KIMI_PLUGIN_CC_DATA: dataRoot,
|
|
48
|
+
KIMI_PLUGIN_CC_HOST_ID: "dsh",
|
|
49
|
+
KIMI_PLUGIN_CC_NODE_BIN: nodeBin,
|
|
50
|
+
KIMI_PLUGIN_CC_WORKSPACE_CWD: cwd,
|
|
51
|
+
CLAUDE_PLUGIN_DATA: undefined,
|
|
52
|
+
PLUGIN_DATA: undefined,
|
|
53
|
+
CLAUDE_PLUGIN_ROOT: undefined,
|
|
54
|
+
PLUGIN_ROOT: undefined,
|
|
55
|
+
KIMI_PLUGIN_CC_SHELL_LAUNCH: undefined,
|
|
56
|
+
KIMI_PLUGIN_CC_MCP_WORKSPACE: undefined,
|
|
57
|
+
KIMI_PLUGIN_CC_WORKSPACE_ROOT: undefined,
|
|
58
|
+
KIMI_PLUGIN_CC_HOOK_SCRIPT: undefined,
|
|
59
|
+
KIMI_PLUGIN_CC_SKIP_HOOK_CHECK: undefined,
|
|
60
|
+
KIMI_PLUGIN_CC_SKIP_VERSION_PROBE: undefined,
|
|
61
|
+
KIMI_CODE_TRUST_WORKSPACE: undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function startCompanionRun(request, operation, spec) {
|
|
65
|
+
const prompt = companionTextTask(request.prompt);
|
|
66
|
+
if (request.signal.aborted) {
|
|
67
|
+
throw new Error("kimi-plugin-cc: delegation was aborted before companion startup");
|
|
68
|
+
}
|
|
69
|
+
const cwd = assertUsableCwd("kimi-plugin-cc", "child cwd", request.cwd ?? process.cwd());
|
|
70
|
+
const termination = new AbortController();
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
const requestCancel = () => {
|
|
73
|
+
cancelled = true;
|
|
74
|
+
if (!termination.signal.aborted) {
|
|
75
|
+
termination.abort(new Error("kimi-plugin-cc: delegation cancelled"));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const onAbort = () => requestCancel();
|
|
79
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
80
|
+
let child;
|
|
81
|
+
try {
|
|
82
|
+
child = spec.spawn({
|
|
83
|
+
argv: companionArgv(operation, prompt, spec.companionPath, process.execPath, spec),
|
|
84
|
+
cwd,
|
|
85
|
+
stdio: {
|
|
86
|
+
stdin: "ignore",
|
|
87
|
+
stdout: { maxBytes: spec.maxOutputBytes },
|
|
88
|
+
stderr: "inherit",
|
|
89
|
+
},
|
|
90
|
+
graceMs: spec.disposeGraceMs,
|
|
91
|
+
signal: termination.signal,
|
|
92
|
+
env: companionEnvironment(spec.dataRoot, cwd, spec.env),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
let diagnostic;
|
|
100
|
+
const result = settleRunResult({
|
|
101
|
+
attempt: async () => {
|
|
102
|
+
let outcome;
|
|
103
|
+
try {
|
|
104
|
+
outcome = await child.done;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
diagnostic = `Kimi ${operation} companion failed before producing a terminal exit`;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
const collected = child.collected.stdout?.readFrom(0);
|
|
111
|
+
const text = collected?.text.trimEnd() ?? "";
|
|
112
|
+
if (!cancelled && outcome.exitCode === 0 && collected && !collected.lossy && text.trim().length > 0) {
|
|
113
|
+
return { output: [{ type: "text", text }], stopReason: "completed" };
|
|
114
|
+
}
|
|
115
|
+
const facts = [
|
|
116
|
+
`exit code: ${outcome.exitCode ?? "none"}`,
|
|
117
|
+
`signal: ${outcome.signal ?? "none"}`,
|
|
118
|
+
...(collected?.lossy ? ["stdout truncated"] : []),
|
|
119
|
+
...(text.trim().length === 0 ? ["stdout empty"] : []),
|
|
120
|
+
];
|
|
121
|
+
diagnostic = `Kimi ${operation} companion failed (${facts.join("; ")})`;
|
|
122
|
+
return { output: [], diagnostic, stopReason: cancelled ? "aborted" : "error" };
|
|
123
|
+
},
|
|
124
|
+
collectOutput: () => [],
|
|
125
|
+
collectDiagnostic: () => diagnostic,
|
|
126
|
+
cancelled: () => cancelled,
|
|
127
|
+
signal: request.signal,
|
|
128
|
+
onAbort,
|
|
129
|
+
});
|
|
130
|
+
return subprocessRunHandle({
|
|
131
|
+
id: brandString(randomUUID()),
|
|
132
|
+
result,
|
|
133
|
+
signal: request.signal,
|
|
134
|
+
onAbort,
|
|
135
|
+
requestCancel,
|
|
136
|
+
teardown: async () => {
|
|
137
|
+
child.terminate();
|
|
138
|
+
await child.waitForExit();
|
|
139
|
+
await child.done.catch(() => { });
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export async function runCompanionCommand(argv, cwd, spec, signal) {
|
|
144
|
+
const child = spec.spawn({
|
|
145
|
+
argv: [process.execPath, spec.companionPath, ...argv],
|
|
146
|
+
cwd,
|
|
147
|
+
stdio: {
|
|
148
|
+
stdin: "ignore",
|
|
149
|
+
stdout: { maxBytes: spec.maxOutputBytes },
|
|
150
|
+
stderr: { maxBytes: spec.maxOutputBytes },
|
|
151
|
+
},
|
|
152
|
+
graceMs: spec.disposeGraceMs,
|
|
153
|
+
signal,
|
|
154
|
+
env: companionEnvironment(spec.dataRoot, cwd, spec.env),
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
const outcome = await child.done;
|
|
158
|
+
await child.waitForExit();
|
|
159
|
+
const stdout = child.collected.stdout?.readFrom(0);
|
|
160
|
+
const stderr = child.collected.stderr?.readFrom(0);
|
|
161
|
+
return {
|
|
162
|
+
exitCode: outcome.exitCode,
|
|
163
|
+
signal: outcome.signal,
|
|
164
|
+
stdout: stdout?.text ?? "",
|
|
165
|
+
stderr: stderr?.text ?? "",
|
|
166
|
+
truncated: Boolean(stdout?.lossy || stderr?.lossy),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
child.terminate();
|
|
171
|
+
await child.waitForExit().catch(() => false);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
const DEFAULT_DISPOSE_GRACE_MS = 6_000;
|
|
12
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
13
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
14
|
+
const COMPANION_PATH = fileURLToPath(new URL("../../dist/companion.js", import.meta.url));
|
|
15
|
+
export const Config = z.object({
|
|
16
|
+
dataRoot: z.string().min(1),
|
|
17
|
+
env: z.dict(z.string()).default({}),
|
|
18
|
+
disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
|
|
19
|
+
maxOutputBytes: z.number().default(DEFAULT_MAX_OUTPUT_BYTES),
|
|
20
|
+
model: z.string().min(1),
|
|
21
|
+
thinkingEffort: z.string().min(1),
|
|
22
|
+
budget: z.string().min(1),
|
|
23
|
+
reviewBase: z.string().min(1),
|
|
24
|
+
});
|
|
25
|
+
export function defaultDshDataRoot(env = process.env) {
|
|
26
|
+
const explicit = env.KIMI_PLUGIN_CC_DATA;
|
|
27
|
+
if (explicit !== undefined) {
|
|
28
|
+
if (!path.isAbsolute(explicit)) {
|
|
29
|
+
throw new Error("kimi-plugin-cc: KIMI_PLUGIN_CC_DATA must be absolute");
|
|
30
|
+
}
|
|
31
|
+
return path.normalize(explicit);
|
|
32
|
+
}
|
|
33
|
+
const dshHome = env.DSH_HOME ?? path.join(env.HOME || homedir(), ".dsh");
|
|
34
|
+
if (!path.isAbsolute(dshHome)) {
|
|
35
|
+
throw new Error("kimi-plugin-cc: DSH_HOME must be absolute");
|
|
36
|
+
}
|
|
37
|
+
return path.join(dshHome, "plugin-data", "kimi-plugin-cc");
|
|
38
|
+
}
|
|
39
|
+
function resolveConfig(config) {
|
|
40
|
+
const resolved = {
|
|
41
|
+
dataRoot: config.dataRoot ?? defaultDshDataRoot(),
|
|
42
|
+
env: config.env ?? {},
|
|
43
|
+
disposeGraceMs: config.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS,
|
|
44
|
+
maxOutputBytes: config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
|
|
45
|
+
model: config.model,
|
|
46
|
+
thinkingEffort: config.thinkingEffort,
|
|
47
|
+
budget: config.budget,
|
|
48
|
+
reviewBase: config.reviewBase,
|
|
49
|
+
};
|
|
50
|
+
if (!path.isAbsolute(resolved.dataRoot)) {
|
|
51
|
+
throw new Error("kimi-plugin-cc: dataRoot must be absolute");
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isFinite(resolved.disposeGraceMs) || resolved.disposeGraceMs <= 0 || resolved.disposeGraceMs > MAX_TIMER_DELAY_MS) {
|
|
54
|
+
throw new Error(`kimi-plugin-cc: disposeGraceMs must be positive, finite, and no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isSafeInteger(resolved.maxOutputBytes) || resolved.maxOutputBytes <= 0) {
|
|
57
|
+
throw new Error("kimi-plugin-cc: maxOutputBytes must be a positive safe integer");
|
|
58
|
+
}
|
|
59
|
+
for (const [field, value] of [["model", resolved.model], ["thinkingEffort", resolved.thinkingEffort], ["reviewBase", resolved.reviewBase]]) {
|
|
60
|
+
if (value?.startsWith("-"))
|
|
61
|
+
throw new Error(`kimi-plugin-cc: ${field} must not start with '-'`);
|
|
62
|
+
}
|
|
63
|
+
if (resolved.budget !== undefined)
|
|
64
|
+
parseDurationMs(resolved.budget, "--budget", "dsh.config", "safety");
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
class KimiCompanionProvider {
|
|
68
|
+
name;
|
|
69
|
+
operation;
|
|
70
|
+
spec;
|
|
71
|
+
capabilities = NO_START_CAPABILITIES;
|
|
72
|
+
inheritsParentContext = false;
|
|
73
|
+
constructor(name, operation, spec) {
|
|
74
|
+
this.name = name;
|
|
75
|
+
this.operation = operation;
|
|
76
|
+
this.spec = spec;
|
|
77
|
+
}
|
|
78
|
+
start(request) {
|
|
79
|
+
return startCompanionRun(request, this.operation, this.spec);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function commandArguments(rawInput) {
|
|
83
|
+
const trimmed = rawInput.trim();
|
|
84
|
+
return trimmed.length === 0 ? [] : trimmed.split(/\s+/);
|
|
85
|
+
}
|
|
86
|
+
function commandText(result) {
|
|
87
|
+
return [result.stdout.trimEnd(), result.stderr.trimEnd()]
|
|
88
|
+
.filter((text) => text.length > 0)
|
|
89
|
+
.join("\n");
|
|
90
|
+
}
|
|
91
|
+
export function apply(ctx, config) {
|
|
92
|
+
const resolved = resolveConfig(config);
|
|
93
|
+
const spec = {
|
|
94
|
+
companionPath: COMPANION_PATH,
|
|
95
|
+
dataRoot: resolved.dataRoot,
|
|
96
|
+
env: resolved.env,
|
|
97
|
+
disposeGraceMs: resolved.disposeGraceMs,
|
|
98
|
+
maxOutputBytes: resolved.maxOutputBytes,
|
|
99
|
+
model: resolved.model,
|
|
100
|
+
thinkingEffort: resolved.thinkingEffort,
|
|
101
|
+
budget: resolved.budget,
|
|
102
|
+
reviewBase: resolved.reviewBase,
|
|
103
|
+
spawn: (spawnSpec) => ctx.subprocess.spawn(spawnSpec),
|
|
104
|
+
};
|
|
105
|
+
for (const operation of DSH_COMPANION_OPERATIONS) {
|
|
106
|
+
ctx.subagents.registerProvider(new KimiCompanionProvider(`kimi-${operation}`, operation, spec));
|
|
107
|
+
}
|
|
108
|
+
ctx.inject(["commands"], (commandCtx) => {
|
|
109
|
+
const execute = async (invocation, argv) => {
|
|
110
|
+
const directories = commandCtx.get("workingDirectory");
|
|
111
|
+
if (directories === undefined) {
|
|
112
|
+
return { kind: "error", text: "Kimi commands require the DSH working-directory service." };
|
|
113
|
+
}
|
|
114
|
+
const cwd = await directories.ensure(invocation.agent, invocation.signal);
|
|
115
|
+
const result = await runCompanionCommand(argv, cwd, spec, invocation.signal);
|
|
116
|
+
const text = commandText(result);
|
|
117
|
+
if (result.truncated) {
|
|
118
|
+
return { kind: "error", text: `${text}\nKimi command output exceeded the configured byte limit.`.trim() };
|
|
119
|
+
}
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "error",
|
|
123
|
+
text: `${text}\nKimi command failed (exit code: ${result.exitCode ?? "none"}; signal: ${result.signal ?? "none"}).`.trim(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { kind: "success", text: text.length > 0 ? text : "Kimi command completed." };
|
|
127
|
+
};
|
|
128
|
+
commandCtx.commands.register({
|
|
129
|
+
definitionId: CommandDefinitionId("kimi-plugin-cc/dsh/setup"),
|
|
130
|
+
name: "kimi-setup",
|
|
131
|
+
description: "Install, check, or remove the DSH-owned Kimi PreToolUse hook; list configured models",
|
|
132
|
+
input: { hint: "[--check|--uninstall|--models [--json]]" },
|
|
133
|
+
recordInput: false,
|
|
134
|
+
handler: (invocation) => {
|
|
135
|
+
const args = commandArguments(invocation.rawInput);
|
|
136
|
+
const valid = args.length === 0
|
|
137
|
+
|| (args.length === 1 && ["--check", "--uninstall", "--models"].includes(args[0]))
|
|
138
|
+
|| (args.length === 2 && args[0] === "--models" && args[1] === "--json");
|
|
139
|
+
if (!valid) {
|
|
140
|
+
return { kind: "error", text: "Usage: /kimi-setup [--check|--uninstall|--models [--json]]" };
|
|
141
|
+
}
|
|
142
|
+
return execute(invocation, ["setup", ...args]);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
for (const command of ["status", "result", "cancel"]) {
|
|
146
|
+
commandCtx.commands.register({
|
|
147
|
+
definitionId: CommandDefinitionId(`kimi-plugin-cc/dsh/${command}`),
|
|
148
|
+
name: `kimi-${command}`,
|
|
149
|
+
description: `${command === "cancel" ? "Cancel" : command === "status" ? "Inspect" : "Return"} a companion Kimi job`,
|
|
150
|
+
input: { hint: "[job-id]" },
|
|
151
|
+
recordInput: false,
|
|
152
|
+
handler: (invocation) => execute(invocation, [command, ...commandArguments(invocation.rawInput)]),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class RuntimeError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
stage;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, stage, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "RuntimeError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.stage = stage;
|
|
10
|
+
this.details = options?.details ?? {};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function formatError(error) {
|
|
14
|
+
if (error instanceof RuntimeError) {
|
|
15
|
+
const base = `[${error.code}] [${error.stage}] ${error.message}`;
|
|
16
|
+
// Serialize structured context so an LLM caller can act on it. Without
|
|
17
|
+
// this, `details` existed only in the SQLite job row — reachable via
|
|
18
|
+
// `result <jobId> --json` for BACKGROUND jobs, but simply lost for
|
|
19
|
+
// foreground ones (review/challenge/ask), whose only output is this
|
|
20
|
+
// string. v1.9.0 needs `drift_axis` to reach the caller on every path, so
|
|
21
|
+
// it is rendered here as one deterministic JSON line. (Kimi's pre-release
|
|
22
|
+
// review caught that the agents were told to branch on a field no channel
|
|
23
|
+
// actually carried.)
|
|
24
|
+
const details = error.details;
|
|
25
|
+
if (details !== undefined && Object.keys(details).length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
return `${base}\ndetails: ${JSON.stringify(details)}`;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// A non-serializable value must never suppress the error itself.
|
|
31
|
+
return base;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return base;
|
|
35
|
+
}
|
|
36
|
+
if (error instanceof Error) {
|
|
37
|
+
return error.message;
|
|
38
|
+
}
|
|
39
|
+
return String(error);
|
|
40
|
+
}
|