kimi-plugin-cc 2.5.1 → 2.5.3
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 +15 -1
- package/README.md +62 -4
- package/dist/cli-client.js +8 -0
- package/dist/commands/setup.js +78 -51
- package/dist/commands/swarm.js +2 -2
- package/dist/dsh/companion.js +9 -6
- package/dist/dsh/index.js +15 -2
- package/dist/hooks/config-safety.js +5 -2
- package/dist/hooks/managed-block.js +86 -20
- package/dist/kimi-command.js +1 -0
- package/dist/kimi-engine.js +3 -0
- package/dist/native-v2-preflight.js +138 -30
- package/dist/swarm-activation.js +276 -14
- package/dist/version.js +1 -1
- package/docs/migration.md +8 -6
- package/docs/registry-releases.md +66 -3
- package/docs/safety.md +14 -10
- package/docs/swarm-recovery.md +2 -2
- package/docs/upstream-2.5.0-certification.md +48 -0
- package/docs/upstream-2.5.1-certification.md +43 -0
- package/package.json +40 -5
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/cli-client.js +8 -0
- package/plugins/kimi-codex/dist/commands/setup.js +78 -51
- package/plugins/kimi-codex/dist/commands/swarm.js +2 -2
- package/plugins/kimi-codex/dist/dsh/companion.js +9 -6
- package/plugins/kimi-codex/dist/dsh/index.js +15 -2
- package/plugins/kimi-codex/dist/hooks/config-safety.js +5 -2
- package/plugins/kimi-codex/dist/hooks/managed-block.js +86 -20
- package/plugins/kimi-codex/dist/kimi-command.js +1 -0
- package/plugins/kimi-codex/dist/kimi-engine.js +3 -0
- package/plugins/kimi-codex/dist/native-v2-preflight.js +138 -30
- package/plugins/kimi-codex/dist/swarm-activation.js +276 -14
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/README.md +4 -1
- package/plugins/kimi-dsh/dist/cli-client.js +8 -0
- package/plugins/kimi-dsh/dist/commands/setup.js +78 -51
- package/plugins/kimi-dsh/dist/commands/swarm.js +2 -2
- package/plugins/kimi-dsh/dist/dsh/companion.js +9 -6
- package/plugins/kimi-dsh/dist/dsh/index.js +15 -2
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +5 -2
- package/plugins/kimi-dsh/dist/hooks/managed-block.js +86 -20
- package/plugins/kimi-dsh/dist/kimi-command.js +1 -0
- package/plugins/kimi-dsh/dist/kimi-engine.js +3 -0
- package/plugins/kimi-dsh/dist/native-v2-preflight.js +138 -30
- package/plugins/kimi-dsh/dist/swarm-activation.js +276 -14
- package/plugins/kimi-dsh/dist/version.js +1 -1
- package/plugins/kimi-dsh/package.json +33 -3
package/docs/swarm-recovery.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Normal stop: use `scripts/companion.sh cancel` for a shell-owned run, or the MCP job cancel tool for an MCP-owned run. A hard kill cannot execute cleanup. Cancellation still starts no fresh cleanup server.
|
|
4
4
|
|
|
5
|
-
For exact CLI 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
5
|
+
For exact CLI 2.3.2, 2.3.3, 2.4.0, 2.4.1 and 2.5.0/2.5.1 swarm runs created by this runtime, status includes a durable `swarm_recovery` record. It contains the resolved home/workspace, companion identity, launch intent, verified process identities and session identity. It never contains the web authentication token, credentials, prompt or config contents. Session identity is committed before the request enabling swarm mode.
|
|
6
6
|
|
|
7
7
|
## Preview, then recover
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ Preview does not signal processes, modify sessions, or capture patches. Apply:
|
|
|
17
17
|
|
|
18
18
|
1. Requires the original companion to be gone and acquires an identity-checked recovery claim. A second recovery cannot race the first; a dead recovery owner can be reclaimed by compare-and-swap.
|
|
19
19
|
2. Revalidates recorded processes and groups. It stops only verified individual identities, never a guessed PID or unproven group. An unknown surviving group member, changed group, incomplete probe or unrecorded launch refuses recovery.
|
|
20
|
-
3. If session cleanup is pending, requires the selected exact certified CLI and valid managed hook. It starts one bounded authenticated web lifecycle, disables the recorded session's swarm mode, verifies
|
|
20
|
+
3. If session cleanup is pending, requires the selected exact certified CLI and valid managed hook. It starts one bounded authenticated web lifecycle, disables the recorded session's swarm mode, verifies the version-appropriate state proof and exit journal, then verifies process quiescence. On 2.5.0/2.5.1 this includes the local environment binding and protocol-v2 WebSocket main-agent state rather than `/status`. It does not resume a prompt, generate model output, install hooks or retry failed cleanup automatically. New recovery web launches are themselves journaled.
|
|
21
21
|
4. Captures a binary patch from a retained write worktree. It does not apply the patch or delete the tree. The result names both paths for inspection.
|
|
22
22
|
|
|
23
23
|
The recovery attempt has a 30-second cancellation signal, with bounded identity probes and normal teardown. Interrupting recovery retains its journal. An already-recovered record is an idempotent no-op. Failed/cancelled/completed job outcomes are immutable; a previously running abandoned job becomes failed, never completed. Recovery status is separate from task success.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Kimi Code 2.5.0 certification evidence — 2026-11-05
|
|
2
|
+
|
|
3
|
+
## Status and exact source
|
|
4
|
+
|
|
5
|
+
**Exact CLI 2.5.0 is certified for all eight operations by the plugin 2.5.2 working tree.** The exact-source audit, non-vacuous plan and swarm controls, local-environment controls, candidate matrix, and production-table matrix passed. Repository and package validation results are recorded below as they complete. Commit, push, tag, publication, installed-host rollout, and operator CLI pin changes are outside this certification slice and were not performed.
|
|
6
|
+
|
|
7
|
+
Candidate: `@moonshot-ai/kimi-code@2.5.0`, release commit `41c341434ae24e45bfe0f2232ec11b80531ac717`, compared with certified 2.4.1 (`2c73f3f50af4fa7f2540ca0d2e33b07a1408e3a7`). The monitor recorded npm publication at 2026-11-04T11:59:13.163Z. A temporary isolated npm installation and its executable both report 2.5.0. The clean exact source checkout was verified at that commit; the seven scoped 2.4.1→2.5.0 diffs are retained under `.claude/kimi-code-research/daily-monitor/2026-11-05-2.5.0-diffs/`.
|
|
8
|
+
|
|
9
|
+
## Source and integration review
|
|
10
|
+
|
|
11
|
+
Four independent source lanes reviewed hooks/plan, CLI/bootstrap, tools/fan-out, and activation adversarially. The hook/plan lane returned COMPAT-PRESERVED. Other lanes identified the removed status route, remote execution defaults/bindings, and the workspace-trust bypass; those findings drove fail-closed repairs rather than certification by assertion. A separate runtime diff review returned APPROVE with no correctness defects.
|
|
12
|
+
|
|
13
|
+
- The expanded exact-tag scan passed **18 tests and 153 assertions across 40 pinned files**, including the SDK, remote-environment implementation, workspace trust/MCP, upgrade authentication, and WebSocket state surfaces. The sole final allow remains active-plan-gated, every model tool execution retains the managed before-execute path, and EnterPlanMode remains removed.
|
|
14
|
+
- Exact 2.5.0 activation uses authenticated protocol-v2 WebSocket main-agent state with unique request IDs, an accepted subscription, and the latest frame through intentional bounded close. Earlier certified versions retain `/status`. Focused activation passed **67 tests with 228 assertions**, including wrong identities, request IDs, optional fields, malformed/oversized frames, contradictory state, timeouts, and local-binding failures.
|
|
15
|
+
- Fresh 2.5.0 print selects `--environment local`; resume never receives that flag. Launcher environment selectors are refused. Remote defaults and disabled workspace trust refuse before spawn. Resume inspection covers persisted metadata and every agent journal, requiring local execution and the expected root, including omitted-metadata-`environmentId` and swarm-write worktrees. Activation and deactivation independently re-read the authenticated local binding before either profile mutation.
|
|
16
|
+
|
|
17
|
+
## Exact-binary controls
|
|
18
|
+
|
|
19
|
+
Controls used a task-authorized Agent Key in process memory/environment and empty isolated homes, except the forced removed-tool control, which used a loopback scripted provider and a dummy key. No personal config or OAuth store was read, copied, or symlinked. Controls ran sequentially; completed evidence homes had credential-bearing config removed afterward. Raw evidence is under `.claude/kimi-code-research/cert-2.5.0-live/`.
|
|
20
|
+
|
|
21
|
+
- **Plan OFF Write:** the exact 2.5.0 marker was first; Write reached the custom deny-all control hook and was denied on the same call ID. No target file or plan state was created.
|
|
22
|
+
- **Plan ON:** authenticated agent state independently proved `plan_mode:true`, auto permission, and the env-model alias before resume. Glob reached the deny hook; a **1,459-byte** plan file was written without a plan-Write hook invocation. The workspace remained empty.
|
|
23
|
+
- **Removed EnterPlanMode:** the offered tools included Write but not EnterPlanMode. A scripted provider forced `call_enter_forced`; the exact binary returned `Tool "EnterPlanMode" not found` on that ID before any hook call and returned the rejection to the provider. No plan state or files appeared.
|
|
24
|
+
- **Environment:** real activation and deactivation passed with the explicit local binding. A configured remote default refused before its command launcher ran, disabled workspace trust refused before spawn, and a resumed remote binding refused before spawn. The implementation also passes focused omitted-id and wrong-root checks.
|
|
25
|
+
- **Swarm OFF:** a real AgentSwarm call returned the inactive-mode result with zero child journals and no probe files.
|
|
26
|
+
- **Swarm ON:** preactivation produced `agent-0` and `agent-1`; each child attempted its assigned Write with a matching managed-denial result under the same toolCallId. No probes landed, and deactivation verified `swarm_mode.exit` with the idle-reclaimer override.
|
|
27
|
+
- **Out-of-root control:** an evidence-preserving run correlated two distinct child Writes with rescue denials on their assigned call IDs, created no files, and verified deactivation.
|
|
28
|
+
|
|
29
|
+
## Candidate and production matrices
|
|
30
|
+
|
|
31
|
+
- Expanded candidate matrix (`candidate-smoke-final.log`): **24 pass, 0 fail, 167 assertions, no skips**, 396.89 seconds. Real `runRescue` landed the confined sentinel and persisted operation/version/engine/session/artifact evidence. The absolute out-of-root Bash.cwd call received its exact same-call denial and created no file. Write-swarm produced a 278-byte child-authored patch, left the user tree clean, removed its worktree, and denied the out-of-root write.
|
|
32
|
+
- Expanded production-table matrix (`production-smoke.log`) with the candidate override explicitly unset: **24 pass, 0 fail, 167 assertions, no skips**, 326.30 seconds. The same confinement, provenance, resume, pursuit, fan-out, patch, and negative-write checks passed.
|
|
33
|
+
- Candidate and production goal controls produced same-call managed denials across distinct turns and left the workspace empty at settlement.
|
|
34
|
+
- The earlier candidate attempt passed 23 of 24 lanes but was rejected as incomplete. The fresh final candidate run above supersedes it. Invalid diagnostic fixtures using relative or symlink-alias cwd were also rejected as control evidence, not counted as product failures.
|
|
35
|
+
|
|
36
|
+
## Repository and package gates
|
|
37
|
+
|
|
38
|
+
Frozen dependency installation made no changes. The literal standard `bun run check` passed **1,404 tests with 33 expected opt-in skips, 0 failures, and 17,999 assertions across 82 files in 336.44 seconds**, including the generated-distribution drift gate. Package validation passed **329 files**, all three compiled distributions, isolated hook setup/check and version refusal, a patched DSH host/profile with seven live providers/tools, and Web boot. An isolated npm consumer-security audit also passed. The certification record is included in the archive. Independent documentation fidelity review identified nine documentation corrections and, after verifying the corrected files, returned **APPROVE** with no remaining must-fix findings. The fresh tag-scan log is retained at `.claude/kimi-code-research/cert-2.5.0-live/tag-scan-final.log`.
|
|
39
|
+
|
|
40
|
+
## Preserved boundaries
|
|
41
|
+
|
|
42
|
+
Certification remains exact and per-operation. Legacy-v1 ends at 0.41.x. Plan mode and unsafe experimental selectors remain refused. Local execution and workspace trust are mandatory, and resumed nonlocal or wrong-root bindings fail closed. Hook verification, trusted-root confinement, concurrency/defaults, one print runner, memory-only web tokens, finite budgets, process/session identity checks, deactivation, and recovery ownership are preserved. Existing Git trust and hook-process failure residuals remain documented; this release does not make the hook an OS sandbox. Publication does not install or re-pin either host.
|
|
43
|
+
|
|
44
|
+
## Release clean-close hardening
|
|
45
|
+
|
|
46
|
+
After the original certification, Linux release CI exposed a platform-specific early WebSocket close event that could be accepted as valid final agent state. The final runtime now requires `CloseEvent.wasClean` before accepting that state, and the mock performs a protocol-correct close handshake while retaining the timeout case. Focused activation validation passed **71 tests with 244 assertions**. On the exact 2.5.0 binary, direct OFF again produced no children, preactivated ON produced two children with correlated managed denials and verified deactivation, and the out-of-root control passed. The final full repository check passed **1,412 tests with 18,282 assertions and zero failures in 378.11 seconds**; 330-file package validation and isolated consumer security also passed. These results supplement the original certification evidence and cover the final release behavior.
|
|
47
|
+
|
|
48
|
+
A subsequent CI attempt showed that native WebSocket close scheduling and `wasClean` semantics vary by platform and cannot supply a deterministic no-close-event precondition. That timeout case now uses a controlled socket which completes the protocol exchange but never emits `close`, and asserts the exact `Kimi web agent-state verification timed out.` message. The refusal is therefore tested without relying on incidental host close timing; production behavior is unchanged by this test-only follow-up.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Kimi Code 2.5.1 certification evidence — 2026-11-06
|
|
2
|
+
|
|
3
|
+
## Status and exact source
|
|
4
|
+
|
|
5
|
+
**Exact CLI 2.5.1 is certified for all eight operations by the plugin 2.5.2 working tree.** The exact-source audit, non-vacuous plan/environment/swarm controls, candidate matrix, production-table matrix, frozen-install, literal full repository check, package validation, and isolated consumer-security audit passed. Independent read-only `claude -p` review returned **APPROVE with no must-fix findings**. Commit, push, tag, publication, installed-host rollout, and operator CLI pin changes are outside this certification slice and were not performed.
|
|
6
|
+
|
|
7
|
+
Candidate: `@moonshot-ai/kimi-code@2.5.1`, release commit `1b02030d8c2c6b00131b23ebb67a231614877827`, compared with certified 2.5.0 (`41c341434ae24e45bfe0f2232ec11b80531ac717`). The monitor recorded npm publication at 2026-11-05T09:58:53.967Z. A temporary isolated npm installation and its executable both report 2.5.1; its `dist/main.mjs` SHA-256 is `84156e7a0f500bb58f9734be57b26756c27eacefb9d884ef94db3af0d6b609dc`. The clean exact source checkout was verified at the release commit. The seven scoped 2.5.0→2.5.1 diffs are retained under `.claude/kimi-code-research/daily-monitor/2026-11-06-2.5.1-diffs/`.
|
|
8
|
+
|
|
9
|
+
## Source and integration review
|
|
10
|
+
|
|
11
|
+
- Scopes 01–06 are unchanged: print/CLI, before-execute and permission gates, external hooks, plan/restore, bootstrap/config/workspace, tools/fan-out/SDK. Scope 07 changes session-list visibility and web media projection only. The provider protocol omits empty system content on tool-bearing messages; the additional transcript change folds daemon media into web history. Neither changes headless stream records, execution, plan arming, hook ordering, environment binding, or WebSocket agent state.
|
|
12
|
+
- DI/scope creation, OS backends, session reload, wire restore/migrations, and the other human-review scopes are unchanged. Of the 40 pinned files, 39 are byte-identical to 2.5.0. Only `kap-server/src/routes/sessions.ts` changed, for deleted-workspace filtering in session lists; session creation and activation are unchanged.
|
|
13
|
+
- The expanded exact-tag scan passed **18 tests and 153 assertions**, with the print, environment/workspace-trust, and WebSocket checks actually executing on 2.5.1. The preserved 2.5.0 source scan also passed again with the same counts.
|
|
14
|
+
- Reviewed candidate branches cover fresh `--environment local`, exact hook-schema acceptance, preactivation, explicit local binding, and authenticated protocol-v2 WebSocket state. After the production row was added, focused activation/engine/CLI/setup tests passed **296 tests with 3,617 assertions**; preflight and launcher tests separately passed **79 tests with 186 assertions**. Typecheck, build, and generated-surface regeneration passed.
|
|
15
|
+
|
|
16
|
+
## Exact-binary controls
|
|
17
|
+
|
|
18
|
+
Controls used a task-authorized Agent Key resolved only into each process and empty absolute-path isolated homes, except the forced removed-tool control, which used a loopback scripted provider and a dummy key. No personal config or OAuth store was read, copied, or symlinked. Controls ran sequentially. Credential-bearing config was removed after the evidence runs. Raw evidence is under `.claude/kimi-code-research/cert-2.5.1-live/`.
|
|
19
|
+
|
|
20
|
+
- **Plan OFF Write:** the exact 2.5.1 marker was first; Write reached the custom deny-all hook and was denied on the same call ID. No target file or plan state was created.
|
|
21
|
+
- **Plan ON:** authenticated agent state independently proved `plan_mode:true`, auto permission, and the env-model alias before resume. Glob reached the deny hook; a **2,232-byte** plan file was written without a plan-Write hook invocation. The workspace remained empty.
|
|
22
|
+
- **Removed EnterPlanMode:** Write was offered but EnterPlanMode was absent. A scripted provider forced `call_enter_forced`; the exact binary returned `Tool "EnterPlanMode" not found` on that ID before any hook call and returned the rejection to the provider. No plan state or files appeared.
|
|
23
|
+
- **Environment:** real activation and deactivation passed with the explicit local binding. A configured remote default refused before its command launcher ran, disabled workspace trust refused before spawn, and a resumed remote binding refused before spawn.
|
|
24
|
+
- **Swarm OFF:** a real AgentSwarm call returned the inactive-mode result with zero child journals and no probe files.
|
|
25
|
+
- **Swarm ON:** preactivation produced `agent-0` and `agent-1`; each child attempted its assigned Write with a matching managed-denial result under the same toolCallId. No probes landed, and deactivation verified `swarm_mode.exit` with ambient idle reclamation set to 1 ms.
|
|
26
|
+
- **Out-of-root control:** two distinct child Writes were correlated with rescue denials on their assigned call IDs, created no files, and verified deactivation.
|
|
27
|
+
- An initial diagnostic Write attempt used a relative artifact root, causing the spawned CLI to resolve a different home without the control hook. Its Write landed only inside that disposable fixture. The run was rejected as control evidence and superseded by the valid absolute-path plan-OFF run above; it is not counted as a product failure or a hook bypass.
|
|
28
|
+
|
|
29
|
+
## Candidate matrix
|
|
30
|
+
|
|
31
|
+
The candidate matrix (`candidate-smoke.log`) passed **24 tests with 167 assertions and no skips in 358.88 seconds**. Real `runRescue` landed the confined sentinel and persisted operation/version/engine/session/artifact evidence. The absolute out-of-root Bash.cwd call received its exact same-call denial and created no file. Resume and plan-taint refusal passed. Thirteen goal turns each had a matching managed Write denial. Both pursue terminal outcomes passed. Write-swarm produced a **278-byte** child-authored patch, left the user tree clean, removed its worktree, and denied the out-of-root write.
|
|
32
|
+
|
|
33
|
+
## Production matrix and final gates
|
|
34
|
+
|
|
35
|
+
The production-table matrix (`production-smoke.log`) with the candidate override explicitly unset passed **24 tests with 167 assertions and no skips in 364.25 seconds**. It independently repeated the confined rescue and Bash.cwd evidence, resume/taint refusal, both pursue outcomes, both swarm modes, and out-of-root denial. Eleven goal turns each had a matching managed Write denial, and write-swarm again captured a **278-byte** child-authored patch with a clean user tree and removed worktree.
|
|
36
|
+
|
|
37
|
+
Frozen dependency installation checked 43 installs across 88 packages and made no changes. The literal standard `bun run check` passed **1,412 tests with 33 expected opt-in skips, 0 failures, and 18,282 assertions across 82 files in 362.99 seconds**, including the generated-distribution drift gate. Package validation passed **330 files**, all three compiled distributions, isolated hook setup/check and version refusal, a patched DSH host/profile with seven live providers/tools, and Web boot. The isolated npm consumer-security audit also passed. An independent read-only `claude -p` review returned **APPROVE with no must-fix findings** after inspecting the runtime, tests, documentation, diffs, and raw control artifacts. Its remaining notes were non-load-bearing evidence-retention limitations. Supporting logs are retained under `.claude/kimi-code-research/cert-2.5.1-live/`, including `check-standard.log`, `package-standard.log`, `consumer-security.log`, `claude-review.log`, `tag-scan-2.5.0-regression.log`, and `binary-sha256.txt`.
|
|
38
|
+
|
|
39
|
+
## Release clean-close hardening
|
|
40
|
+
|
|
41
|
+
After the original certification and independent review, Linux release CI exposed a platform-specific early WebSocket close event that could be accepted as valid final agent state. The final runtime now requires `CloseEvent.wasClean` before accepting that state, and the mock performs a protocol-correct close handshake while retaining the timeout case. Focused activation validation passed **71 tests with 244 assertions**. The full 2.5.1 production matrix was rerun against the final implementation and again passed **24 tests with 167 assertions and no skips**, this time in **336.76 seconds**, including nine denied goal turns and a 278-byte child-authored patch. Exact 2.5.0 swarm OFF/ON and out-of-root controls also passed. The final full repository check passed **1,412 tests with 18,282 assertions and zero failures in 378.11 seconds**; 330-file package validation and isolated consumer security also passed. These results supplement the original certification and review evidence and cover the final release behavior.
|
|
42
|
+
|
|
43
|
+
A subsequent CI attempt showed that native WebSocket close scheduling and `wasClean` semantics vary by platform and cannot supply a deterministic no-close-event precondition. That timeout case now uses a controlled socket which completes the protocol exchange but never emits `close`, and asserts the exact `Kimi web agent-state verification timed out.` message. The refusal is therefore tested without relying on incidental host close timing; production behavior is unchanged by this test-only follow-up.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi-plugin-cc",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Kimi for Claude Code, Codex, and DeepSeek Harness: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tools through the local Kimi Code CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/dsh/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"test": "bun test",
|
|
23
23
|
"smoke:real": "KIMI_PLUGIN_CC_SMOKE=1 bun test tests/runtime/real-binary-smoke.test.ts --path-ignore-patterns '**/worktrees/**'",
|
|
24
24
|
"check:package": "node scripts/check-package.mjs && node scripts/check-dsh-package.mjs",
|
|
25
|
+
"check:consumer-security": "node scripts/check-consumer-security.mjs",
|
|
25
26
|
"check:contracts": "node scripts/check-contract-size.mjs"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
@@ -31,16 +32,17 @@
|
|
|
31
32
|
"@deepseek-ai/dsh-session": "0.2.1-rc.5",
|
|
32
33
|
"@deepseek-ai/dsh-subagent": "0.2.1-rc.5",
|
|
33
34
|
"@deepseek-ai/dsh-subprocess": "0.2.1-rc.5",
|
|
35
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.5",
|
|
36
|
+
"@deepseek-ai/dsh-tool-subagent": "0.2.1-rc.5",
|
|
34
37
|
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.5",
|
|
35
|
-
"@types/bun": "^1.4.
|
|
36
|
-
"@types/node": "^26.
|
|
38
|
+
"@types/bun": "^1.4.4",
|
|
39
|
+
"@types/node": "^26.6.6",
|
|
37
40
|
"smol-toml": "1.9.1",
|
|
38
41
|
"tsx": "^4.23.13",
|
|
39
42
|
"typescript": "^7.0.2"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
42
45
|
"@deepseek-ai/dsh-brand": "0.2.1-rc.5",
|
|
43
|
-
"@deepseek-ai/dsh-tool-subagent": "0.2.1-rc.5",
|
|
44
46
|
"@deepseek-ai/schemastery": "3.18.5"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
@@ -50,7 +52,38 @@
|
|
|
50
52
|
"@deepseek-ai/dsh-session": ">=0.2.1-rc.5 <0.3.0",
|
|
51
53
|
"@deepseek-ai/dsh-subagent": ">=0.2.1-rc.5 <0.3.0",
|
|
52
54
|
"@deepseek-ai/dsh-subprocess": ">=0.2.1-rc.5 <0.3.0",
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
55
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.2.1-rc.5 <0.3.0",
|
|
56
|
+
"@deepseek-ai/dsh-working-directory": ">=0.2.1-rc.5 <0.3.0",
|
|
57
|
+
"@deepseek-ai/dsh-tool-subagent": ">=0.2.1-rc.5 <0.3.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@deepseek-ai/cordis": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@deepseek-ai/dsh-commands": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@deepseek-ai/dsh-llm": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@deepseek-ai/dsh-session": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"@deepseek-ai/dsh-subagent": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@deepseek-ai/dsh-subprocess": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@deepseek-ai/dsh-working-directory": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@deepseek-ai/dsh-tool-subagent": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
54
87
|
},
|
|
55
88
|
"overrides": {
|
|
56
89
|
"zod": "^4.6.6",
|
|
@@ -101,6 +134,8 @@
|
|
|
101
134
|
"docs/models.md",
|
|
102
135
|
"docs/session-visibility.md",
|
|
103
136
|
"docs/upstream-2.4.1-certification.md",
|
|
137
|
+
"docs/upstream-2.5.0-certification.md",
|
|
138
|
+
"docs/upstream-2.5.1-certification.md",
|
|
104
139
|
"README.md",
|
|
105
140
|
"LICENSE",
|
|
106
141
|
"CHANGELOG.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP job controls through the local Kimi Code CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
@@ -65,6 +65,7 @@ export async function runCliPrompt(opts) {
|
|
|
65
65
|
kimiHome: resolveKimiHome(opts.env, opts.cwd),
|
|
66
66
|
env: opts.env,
|
|
67
67
|
resumeSessionId: opts.resumeSessionId,
|
|
68
|
+
expectedExecutionRoot: opts.trustedWorkspaceRoot ?? opts.cwd,
|
|
68
69
|
stage: "cli-client.spawn",
|
|
69
70
|
});
|
|
70
71
|
}
|
|
@@ -781,6 +782,13 @@ const KIMI_LEGACY_FORCED_VALUE = "1";
|
|
|
781
782
|
function buildArgs(opts) {
|
|
782
783
|
const args = [...(opts.prefixArgs ?? [])];
|
|
783
784
|
args.push("--output-format", "stream-json");
|
|
785
|
+
const smokeCandidate = opts.executionPlan.certification === "test-bypass" && opts.env.KIMI_PLUGIN_CC_SMOKE === "1"
|
|
786
|
+
? opts.env.KIMI_PLUGIN_CC_SMOKE_V2_CANDIDATE
|
|
787
|
+
: undefined;
|
|
788
|
+
if (opts.resumeSessionId === undefined &&
|
|
789
|
+
["2.5.0", "2.5.1"].includes(opts.executionPlan.kimiVersion ?? smokeCandidate ?? "")) {
|
|
790
|
+
args.push("--environment", "local");
|
|
791
|
+
}
|
|
784
792
|
if (opts.resumeSessionId !== undefined) {
|
|
785
793
|
args.push("-r", opts.resumeSessionId);
|
|
786
794
|
}
|
|
@@ -9,7 +9,8 @@ import { SETUP_SUPPORTED_FLAGS } from "../parsing.js";
|
|
|
9
9
|
// absolute Node path avoids PATH dependence under sanitized launches. Setup
|
|
10
10
|
// probes the hook directly with resolveNodeBinary and through `/bin/sh -c`
|
|
11
11
|
// using the exact installed command bytes. It also warns about broad permission
|
|
12
|
-
// denies
|
|
12
|
+
// denies, repairs orphan marker comments, and refuses remaining
|
|
13
|
+
// duplicate, orphaned, or malformed managed blocks.
|
|
13
14
|
//
|
|
14
15
|
// Subcommands: install (default), --check, --uninstall [--all], and
|
|
15
16
|
// --enable-review-gate/--disable-review-gate.
|
|
@@ -25,12 +26,13 @@ import { readPluginConfig, writePluginConfig } from "../config.js";
|
|
|
25
26
|
import { RuntimeError } from "../errors.js";
|
|
26
27
|
import { normalizeTopLevelInlineHooks, validateKimiHookSetForEnvironment, withKimiConfigLock, } from "../hooks/config-safety.js";
|
|
27
28
|
import { buildHookShellCommand, hostIdFromHookCommand, resolveHookScriptPath, resolveHostId, resolveNodeBinary, } from "../hooks/install-paths.js";
|
|
28
|
-
import { decodeManagedCommandLine, effectiveHost, evaluateInstalled, findUnmanagedApprovalHookBlocks, MARKERS, parseManagedBlock, } from "../hooks/managed-block.js";
|
|
29
|
+
import { decodeManagedCommandLine, effectiveHost, evaluateInstalled, findUnmanagedApprovalHookBlocks, markerCommentLines, MARKERS, parseManagedBlock, } from "../hooks/managed-block.js";
|
|
29
30
|
import { formatVersionOutOfRangeWarning, probeKimiVersion, } from "../kimi-version-probe.js";
|
|
30
31
|
import { isNativeV2CertifiedVersion } from "../kimi-engine.js";
|
|
31
32
|
import { resolveKimiHome } from "../kimi-home.js";
|
|
32
33
|
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
33
34
|
import { KIMI_PLUGIN_CC_VERSION } from "../version.js";
|
|
35
|
+
import { parse as parseToml } from "../vendor/smol-toml/parse.js";
|
|
34
36
|
const BEGIN_MARKER_PREFIX = "# === BEGIN kimi-plugin-cc-managed";
|
|
35
37
|
const DEFAULT_HOOK_TIMEOUT_S = 15;
|
|
36
38
|
// Probe timing. The direct script probe is local and fast; allow 5s for
|
|
@@ -119,7 +121,17 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
119
121
|
const mutation = await withKimiConfigLock(configPath, async () => {
|
|
120
122
|
const original = await readConfigSafe(configPath);
|
|
121
123
|
const lineEnding = detectLineEnding(original);
|
|
122
|
-
|
|
124
|
+
try {
|
|
125
|
+
parseToml(original);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
await assertKimiHookSetValid(original, configPath, "setup.install", context.env);
|
|
129
|
+
}
|
|
130
|
+
const repaired = removeOrphanMarkers(original);
|
|
131
|
+
if (repaired.count > 0) {
|
|
132
|
+
warnings.push(`Removed ${repaired.count} orphaned managed marker comment(s); surrounding config was preserved.`);
|
|
133
|
+
}
|
|
134
|
+
const inlineHooks = normalizeTopLevelInlineHooks(repaired.contents, lineEnding);
|
|
123
135
|
if (inlineHooks.found) {
|
|
124
136
|
// Validate before normalizing so an invalid inline entry is reported
|
|
125
137
|
// against the user's original bytes and the config remains untouched.
|
|
@@ -133,7 +145,7 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
133
145
|
// splice line numbers. Another host's marker-less table may be its live hook
|
|
134
146
|
// after a config rewrite strips comments, so it must remain untouched.
|
|
135
147
|
const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
|
|
136
|
-
const { pruned, count: prunedCount, commands: prunedCommands } = pruneOrphanApprovalHooks(inlineHooks.contents,
|
|
148
|
+
const { pruned, count: prunedCount, commands: prunedCommands } = pruneOrphanApprovalHooks(inlineHooks.contents, hostId, expectedCommand);
|
|
137
149
|
const existing = pruned;
|
|
138
150
|
if (prunedCount > 0) {
|
|
139
151
|
const readorned = prunedCommands.includes(expectedCommand);
|
|
@@ -145,7 +157,7 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
|
|
|
145
157
|
if (state.kind === "orphan") {
|
|
146
158
|
throw new RuntimeError("SETUP_ORPHAN_MARKERS", [
|
|
147
159
|
`kimi-code config at ${configPath} contains an orphaned ${state.detail} marker.`,
|
|
148
|
-
"
|
|
160
|
+
"Inspect the managed marker comments and re-run setup.",
|
|
149
161
|
].join(" "), "setup.install", { details: { configPath, orphan: state.detail } });
|
|
150
162
|
}
|
|
151
163
|
if (state.kind === "duplicate") {
|
|
@@ -393,12 +405,11 @@ async function runUninstallLocked(configPath, hookScriptPath, hostId, removeAllH
|
|
|
393
405
|
// other hosts' blocks and live marker-less tables remain intact. `--all` is
|
|
394
406
|
// the explicit every-host sweep for all of this plugin's hook tables.
|
|
395
407
|
const { stripped: strippedMarkers, removedBlocks, orphansLeft } = stripManagedBlocks(existing, hostId, removeAllHosts);
|
|
396
|
-
const lineEnding = detectLineEnding(existing);
|
|
397
408
|
// Exact current command bytes also identify this host's table when a host-id
|
|
398
409
|
// override disagrees with path derivation or a dev path is outside the strict
|
|
399
410
|
// ownership pattern.
|
|
400
411
|
const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
|
|
401
|
-
const { pruned: stripped, count: prunedCount } = pruneOrphanApprovalHooks(strippedMarkers,
|
|
412
|
+
const { pruned: stripped, count: prunedCount } = pruneOrphanApprovalHooks(strippedMarkers, removeAllHosts ? undefined : hostId, expectedCommand);
|
|
402
413
|
const changed = stripped !== existing;
|
|
403
414
|
if (changed) {
|
|
404
415
|
await writeConfigAtomic(configPath, stripped);
|
|
@@ -517,10 +528,10 @@ function detectLineEnding(contents) {
|
|
|
517
528
|
return contents.includes("\r\n") ? "\r\n" : "\n";
|
|
518
529
|
}
|
|
519
530
|
function spliceBlock(contents, beginLine, endLine, replacement, lineEnding) {
|
|
520
|
-
const lines =
|
|
521
|
-
const before = lines.slice(0, beginLine).join(
|
|
522
|
-
const after = lines.slice(endLine + 1).join(
|
|
523
|
-
const beforeWithSep = before.endsWith(
|
|
531
|
+
const lines = contents.split(/(?<=\n)/);
|
|
532
|
+
const before = lines.slice(0, beginLine).join("");
|
|
533
|
+
const after = lines.slice(endLine + 1).join("");
|
|
534
|
+
const beforeWithSep = before.endsWith("\n") || before.length === 0 ? before : `${before}${lineEnding}`;
|
|
524
535
|
const replacementWithEnding = replacement.endsWith(lineEnding) ? replacement : `${replacement}${lineEnding}`;
|
|
525
536
|
return `${beforeWithSep}${replacementWithEnding}${after}`;
|
|
526
537
|
}
|
|
@@ -536,14 +547,54 @@ function splitPreservingEnding(contents) {
|
|
|
536
547
|
// pass the desired line ending).
|
|
537
548
|
return contents.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
538
549
|
}
|
|
550
|
+
// A partial upstream hooks-array rewrite can leave one marker comment behind.
|
|
551
|
+
// Remove only unpaired marker comments, never the intervening content. The
|
|
552
|
+
// normal full-file validation still gates writes.
|
|
553
|
+
function removeOrphanMarkers(contents) {
|
|
554
|
+
const lines = splitPreservingEnding(contents);
|
|
555
|
+
const orphans = [];
|
|
556
|
+
const comments = markerCommentLines(contents);
|
|
557
|
+
let open = null;
|
|
558
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
559
|
+
if (!comments.has(i))
|
|
560
|
+
continue;
|
|
561
|
+
const line = lines[i].trim();
|
|
562
|
+
const begin = MARKERS.BEGIN_LINE_RE.exec(line);
|
|
563
|
+
if (begin !== null) {
|
|
564
|
+
if (open !== null)
|
|
565
|
+
orphans.push(open);
|
|
566
|
+
open = { index: i, host: begin[1]?.toLowerCase() ?? null };
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const end = MARKERS.END_LINE_RE.exec(line);
|
|
570
|
+
if (end === null)
|
|
571
|
+
continue;
|
|
572
|
+
const endMarker = { index: i, host: end[1]?.toLowerCase() ?? null };
|
|
573
|
+
if (open === null)
|
|
574
|
+
orphans.push(endMarker);
|
|
575
|
+
else {
|
|
576
|
+
if (open.host !== endMarker.host)
|
|
577
|
+
orphans.push(open, endMarker);
|
|
578
|
+
open = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (open !== null)
|
|
583
|
+
orphans.push(open);
|
|
584
|
+
const remove = new Set(orphans.map((marker) => marker.index));
|
|
585
|
+
return remove.size === 0
|
|
586
|
+
? { contents, count: 0 }
|
|
587
|
+
: { contents: contents.split(/(?<=\n)/).filter((_, index) => !remove.has(index)).join(""), count: remove.size };
|
|
588
|
+
}
|
|
539
589
|
/**
|
|
540
590
|
* Remove managed blocks owned by `currentHost`, or every host with `removeAll`.
|
|
541
591
|
* Ownership comes from the marker suffix or a legacy command's derived host.
|
|
542
592
|
* Orphan markers are removed individually so surrounding user content survives.
|
|
543
593
|
*/
|
|
544
594
|
function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
545
|
-
const lineEnding = detectLineEnding(contents);
|
|
546
595
|
const lines = splitPreservingEnding(contents);
|
|
596
|
+
const rawLines = contents.split(/(?<=\n)/);
|
|
597
|
+
const markerLines = markerCommentLines(contents);
|
|
547
598
|
const result = [];
|
|
548
599
|
const orphansLeft = [];
|
|
549
600
|
let removedBlocks = 0;
|
|
@@ -567,7 +618,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
567
618
|
while (i < lines.length) {
|
|
568
619
|
const line = lines[i];
|
|
569
620
|
const trimmed = line.trim();
|
|
570
|
-
const begin = MARKERS.BEGIN_LINE_RE.exec(trimmed);
|
|
621
|
+
const begin = markerLines.has(i) ? MARKERS.BEGIN_LINE_RE.exec(trimmed) : null;
|
|
571
622
|
if (begin !== null) {
|
|
572
623
|
const beginHost = begin[1]?.toLowerCase() ?? null;
|
|
573
624
|
// Look ahead for the matching END, capturing the block's command en route.
|
|
@@ -575,12 +626,14 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
575
626
|
let blockCommand = null;
|
|
576
627
|
for (let j = i + 1; j < lines.length; j += 1) {
|
|
577
628
|
const nextTrimmed = lines[j].trim();
|
|
578
|
-
if (MARKERS.BEGIN_LINE_RE.test(nextTrimmed)) {
|
|
629
|
+
if (markerLines.has(j) && MARKERS.BEGIN_LINE_RE.test(nextTrimmed)) {
|
|
579
630
|
// Two BEGINs in a row — the outer one is an orphan.
|
|
580
631
|
break;
|
|
581
632
|
}
|
|
582
|
-
|
|
583
|
-
|
|
633
|
+
const end = markerLines.has(j) ? MARKERS.END_LINE_RE.exec(nextTrimmed) : null;
|
|
634
|
+
if (end !== null) {
|
|
635
|
+
if ((end[1]?.toLowerCase() ?? null) === beginHost)
|
|
636
|
+
endIdx = j;
|
|
584
637
|
break;
|
|
585
638
|
}
|
|
586
639
|
const decoded = decodeManagedCommandLine(nextTrimmed);
|
|
@@ -594,7 +647,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
594
647
|
i += 1;
|
|
595
648
|
continue;
|
|
596
649
|
}
|
|
597
|
-
result.push(
|
|
650
|
+
result.push(rawLines[i]);
|
|
598
651
|
i += 1;
|
|
599
652
|
continue;
|
|
600
653
|
}
|
|
@@ -606,11 +659,11 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
606
659
|
}
|
|
607
660
|
// Another host's block — keep it verbatim.
|
|
608
661
|
for (let k = i; k <= endIdx; k += 1)
|
|
609
|
-
result.push(
|
|
662
|
+
result.push(rawLines[k]);
|
|
610
663
|
i = endIdx + 1;
|
|
611
664
|
continue;
|
|
612
665
|
}
|
|
613
|
-
const end = MARKERS.END_LINE_RE.exec(trimmed);
|
|
666
|
+
const end = markerLines.has(i) ? MARKERS.END_LINE_RE.exec(trimmed) : null;
|
|
614
667
|
if (end !== null) {
|
|
615
668
|
const endHost = end[1]?.toLowerCase() ?? null;
|
|
616
669
|
// Orphan END with no preceding BEGIN: drop only this line (if ours).
|
|
@@ -619,27 +672,14 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
619
672
|
i += 1;
|
|
620
673
|
continue;
|
|
621
674
|
}
|
|
622
|
-
result.push(
|
|
675
|
+
result.push(rawLines[i]);
|
|
623
676
|
i += 1;
|
|
624
677
|
continue;
|
|
625
678
|
}
|
|
626
|
-
result.push(
|
|
679
|
+
result.push(rawLines[i]);
|
|
627
680
|
i += 1;
|
|
628
681
|
}
|
|
629
|
-
|
|
630
|
-
let blankRun = 0;
|
|
631
|
-
for (const line of result) {
|
|
632
|
-
if (line.length === 0) {
|
|
633
|
-
blankRun += 1;
|
|
634
|
-
if (blankRun <= 2)
|
|
635
|
-
collapsed.push(line);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
blankRun = 0;
|
|
639
|
-
collapsed.push(line);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return { stripped: collapsed.join(lineEnding), removedBlocks, orphansLeft };
|
|
682
|
+
return { stripped: result.join(""), removedBlocks, orphansLeft };
|
|
643
683
|
}
|
|
644
684
|
/**
|
|
645
685
|
* Remove orphaned, marker-less `[[hooks]]` tables that are unambiguously this
|
|
@@ -654,7 +694,7 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
|
|
|
654
694
|
* host — REQUIRED for scoped install/uninstall so one host never disarms the
|
|
655
695
|
* other's live (marker-stripped) hook. Omit only for `--uninstall --all`.
|
|
656
696
|
*/
|
|
657
|
-
function pruneOrphanApprovalHooks(contents,
|
|
697
|
+
function pruneOrphanApprovalHooks(contents, ownedBy, alsoMatchCommand) {
|
|
658
698
|
const ranges = findUnmanagedApprovalHookBlocks(contents, ownedBy, alsoMatchCommand);
|
|
659
699
|
if (ranges.length === 0)
|
|
660
700
|
return { pruned: contents, count: 0, commands: [] };
|
|
@@ -663,23 +703,10 @@ function pruneOrphanApprovalHooks(contents, lineEnding, ownedBy, alsoMatchComman
|
|
|
663
703
|
for (let i = start; i < end; i += 1)
|
|
664
704
|
remove.add(i);
|
|
665
705
|
}
|
|
666
|
-
const lines =
|
|
706
|
+
const lines = contents.split(/(?<=\n)/);
|
|
667
707
|
const kept = lines.filter((_, idx) => !remove.has(idx));
|
|
668
|
-
const collapsed = [];
|
|
669
|
-
let blankRun = 0;
|
|
670
|
-
for (const line of kept) {
|
|
671
|
-
if (line.length === 0) {
|
|
672
|
-
blankRun += 1;
|
|
673
|
-
if (blankRun <= 2)
|
|
674
|
-
collapsed.push(line);
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
blankRun = 0;
|
|
678
|
-
collapsed.push(line);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
708
|
return {
|
|
682
|
-
pruned:
|
|
709
|
+
pruned: kept.join(""),
|
|
683
710
|
count: ranges.length,
|
|
684
711
|
commands: ranges.map((range) => range.command),
|
|
685
712
|
};
|
|
@@ -28,8 +28,8 @@ import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js
|
|
|
28
28
|
// only the wall-clock hard bound. Read/write modes reuse review/rescue lineage,
|
|
29
29
|
// and the mandatory PreToolUse hook enforces read-only policy or trusted-worktree
|
|
30
30
|
// write confinement on the coordinator and every subagent. Write mode requires
|
|
31
|
-
// kimi-code 0.18.0+. Exact 2.3.2, 2.3.3, 2.4.0
|
|
32
|
-
// session through a bounded loopback web lifecycle, then resume it through the normal print path.
|
|
31
|
+
// kimi-code 0.18.0+. Exact 2.3.2, 2.3.3, 2.4.0, 2.4.1, 2.5.0 and 2.5.1 additionally
|
|
32
|
+
// activate a same-job session through a bounded loopback web lifecycle, then resume it through the normal print path.
|
|
33
33
|
const SWARM_SUMMARY_MAX = 120;
|
|
34
34
|
const SWARM_AGENT_PROFILE = "<swarm>";
|
|
35
35
|
const SWARM_WRITE_AGENT_PROFILE = "<swarm-write>";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
3
3
|
import { assertUsableCwd, settleRunResult, subprocessRunHandle, } from "@deepseek-ai/dsh-subagent";
|
|
4
|
+
import { preferStableNodePath } from "../hooks/install-paths.js";
|
|
4
5
|
export const DSH_COMPANION_OPERATIONS = [
|
|
5
6
|
"ask",
|
|
6
7
|
"review",
|
|
@@ -20,7 +21,7 @@ export function companionTextTask(prompt) {
|
|
|
20
21
|
}
|
|
21
22
|
return text;
|
|
22
23
|
}
|
|
23
|
-
export function companionArgv(operation, prompt, companionPath, nodeBin = process.execPath, options = {}) {
|
|
24
|
+
export function companionArgv(operation, prompt, companionPath, nodeBin = preferStableNodePath(process.execPath), options = {}) {
|
|
24
25
|
const command = operation === "ask" || operation === "review"
|
|
25
26
|
? [operation]
|
|
26
27
|
: operation === "swarm-write"
|
|
@@ -41,7 +42,7 @@ export function companionArgv(operation, prompt, companionPath, nodeBin = proces
|
|
|
41
42
|
prompt,
|
|
42
43
|
];
|
|
43
44
|
}
|
|
44
|
-
export function companionEnvironment(dataRoot, cwd, env, nodeBin = process.execPath) {
|
|
45
|
+
export function companionEnvironment(dataRoot, cwd, env, nodeBin = preferStableNodePath(process.execPath)) {
|
|
45
46
|
return {
|
|
46
47
|
...env,
|
|
47
48
|
KIMI_PLUGIN_CC_DATA: dataRoot,
|
|
@@ -79,8 +80,9 @@ export async function startCompanionRun(request, operation, spec) {
|
|
|
79
80
|
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
80
81
|
let child;
|
|
81
82
|
try {
|
|
83
|
+
const nodeBin = preferStableNodePath(process.execPath);
|
|
82
84
|
child = spec.spawn({
|
|
83
|
-
argv: companionArgv(operation, prompt, spec.companionPath,
|
|
85
|
+
argv: companionArgv(operation, prompt, spec.companionPath, nodeBin, spec),
|
|
84
86
|
cwd,
|
|
85
87
|
stdio: {
|
|
86
88
|
stdin: "ignore",
|
|
@@ -89,7 +91,7 @@ export async function startCompanionRun(request, operation, spec) {
|
|
|
89
91
|
},
|
|
90
92
|
graceMs: spec.disposeGraceMs,
|
|
91
93
|
signal: termination.signal,
|
|
92
|
-
env: companionEnvironment(spec.dataRoot, cwd, spec.env),
|
|
94
|
+
env: companionEnvironment(spec.dataRoot, cwd, spec.env, nodeBin),
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
97
|
catch (error) {
|
|
@@ -141,8 +143,9 @@ export async function startCompanionRun(request, operation, spec) {
|
|
|
141
143
|
});
|
|
142
144
|
}
|
|
143
145
|
export async function runCompanionCommand(argv, cwd, spec, signal) {
|
|
146
|
+
const nodeBin = preferStableNodePath(process.execPath);
|
|
144
147
|
const child = spec.spawn({
|
|
145
|
-
argv: [
|
|
148
|
+
argv: [nodeBin, spec.companionPath, ...argv],
|
|
146
149
|
cwd,
|
|
147
150
|
stdio: {
|
|
148
151
|
stdin: "ignore",
|
|
@@ -151,7 +154,7 @@ export async function runCompanionCommand(argv, cwd, spec, signal) {
|
|
|
151
154
|
},
|
|
152
155
|
graceMs: spec.disposeGraceMs,
|
|
153
156
|
signal,
|
|
154
|
-
env: companionEnvironment(spec.dataRoot, cwd, spec.env),
|
|
157
|
+
env: companionEnvironment(spec.dataRoot, cwd, spec.env, nodeBin),
|
|
155
158
|
});
|
|
156
159
|
try {
|
|
157
160
|
const outcome = await child.done;
|
|
@@ -8,6 +8,12 @@ import { DSH_COMPANION_OPERATIONS, runCompanionCommand, startCompanionRun, } fro
|
|
|
8
8
|
import { parseDurationMs } from "../parsing.js";
|
|
9
9
|
export const name = "kimi-plugin-cc";
|
|
10
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(" ");
|
|
11
17
|
const DEFAULT_DISPOSE_GRACE_MS = 6_000;
|
|
12
18
|
const DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
13
19
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
@@ -50,8 +56,8 @@ function resolveConfig(config) {
|
|
|
50
56
|
if (!path.isAbsolute(resolved.dataRoot)) {
|
|
51
57
|
throw new Error("kimi-plugin-cc: dataRoot must be absolute");
|
|
52
58
|
}
|
|
53
|
-
if (!Number.isFinite(resolved.disposeGraceMs) || resolved.disposeGraceMs
|
|
54
|
-
throw new Error(`kimi-plugin-cc: disposeGraceMs must be
|
|
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}`);
|
|
55
61
|
}
|
|
56
62
|
if (!Number.isSafeInteger(resolved.maxOutputBytes) || resolved.maxOutputBytes <= 0) {
|
|
57
63
|
throw new Error("kimi-plugin-cc: maxOutputBytes must be a positive safe integer");
|
|
@@ -105,6 +111,13 @@ export function apply(ctx, config) {
|
|
|
105
111
|
for (const operation of DSH_COMPANION_OPERATIONS) {
|
|
106
112
|
ctx.subagents.registerProvider(new KimiCompanionProvider(`kimi-${operation}`, operation, spec));
|
|
107
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
|
+
});
|
|
108
121
|
ctx.inject(["commands"], (commandCtx) => {
|
|
109
122
|
const execute = async (invocation, argv) => {
|
|
110
123
|
const directories = commandCtx.get("workingDirectory");
|