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/dist/swarm-activation.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { constants as fsConstants } from "node:fs";
|
|
3
4
|
import { open } from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
@@ -9,7 +10,7 @@ import { assertExecutionPlanMatchesSpawn, assertNoUnsafeExperimentalSelector, }
|
|
|
9
10
|
import { resolveKimiHome } from "./kimi-home.js";
|
|
10
11
|
import { assertNativeV2Preflight } from "./native-v2-preflight.js";
|
|
11
12
|
import { collectDescendantIdentities, collectProcessGroupsIdentities, probeProcessIdentity, revalidateProcessIdentities, waitForProcessTreeExit, } from "./process-tree.js";
|
|
12
|
-
const PREACTIVATION_VERSIONS = ["2.3.2", "2.3.3", "2.4.0", "2.4.1"];
|
|
13
|
+
const PREACTIVATION_VERSIONS = ["2.3.2", "2.3.3", "2.4.0", "2.4.1", "2.5.0", "2.5.1"];
|
|
13
14
|
const READY_TIMEOUT_MS = 10_000;
|
|
14
15
|
const REST_TIMEOUT_MS = 5_000;
|
|
15
16
|
// Exact 2.4.0 can take longer than 5s to drain session/workspace lifecycle work.
|
|
@@ -18,6 +19,8 @@ const JOURNAL_TIMEOUT_MS = 2_000;
|
|
|
18
19
|
const JOURNAL_POLL_MS = 50;
|
|
19
20
|
const READY_LINE_MAX_CHARS = 65_536;
|
|
20
21
|
const REST_RESPONSE_MAX_BYTES = 1024 * 1024;
|
|
22
|
+
const WEBSOCKET_MAX_FRAMES = 64;
|
|
23
|
+
const WEBSOCKET_RESPONSE_MAX_CHARS = 1024 * 1024;
|
|
21
24
|
const JOURNAL_MAX_BYTES = 64 * 1024 * 1024;
|
|
22
25
|
const DESCENDANT_SNAPSHOT_TIMEOUT_MS = 2_000;
|
|
23
26
|
const DESCENDANT_TRACK_INTERVAL_MS = 50;
|
|
@@ -39,11 +42,12 @@ export async function preactivateSwarmSession(options) {
|
|
|
39
42
|
web = await startWebChild(options, prepared.env, failureCode);
|
|
40
43
|
await verifyMeta(web, prepared.expectedVersion, options.signal, failureCode);
|
|
41
44
|
const model = await resolveProfileModel(web, options, failureCode);
|
|
42
|
-
identity = await createSession(web, options, failureCode);
|
|
45
|
+
identity = await createSession(web, options, prepared.expectedVersion, failureCode);
|
|
46
|
+
await verifyLocalEnvironment(web, identity, options.cwd, prepared.expectedVersion, options.signal, failureCode);
|
|
43
47
|
options.onRecoveryEvent?.({ kind: "session", ...identity });
|
|
44
48
|
profileMutationAttempted = true;
|
|
45
49
|
await postSwarmProfile(web, identity, true, model, options.signal, failureCode);
|
|
46
|
-
await verifySwarmStatus(web, identity, true, model, options.signal, failureCode);
|
|
50
|
+
await verifySwarmStatus(web, identity, true, model, prepared.expectedVersion, options.signal, failureCode);
|
|
47
51
|
await shutdownWeb(web, options.signal, failureCode);
|
|
48
52
|
await requireSwarmJournal(prepared.kimiHome, identity, "enter", options.signal, failureCode);
|
|
49
53
|
throwIfAborted(options.signal, failureCode, "swarm-activation.complete");
|
|
@@ -86,8 +90,9 @@ export async function deactivateSwarmSession(options) {
|
|
|
86
90
|
throwIfAborted(options.signal, failureCode, "swarm-deactivation.pre-spawn");
|
|
87
91
|
web = await startWebChild(options, prepared.env, failureCode);
|
|
88
92
|
await verifyMeta(web, prepared.expectedVersion, options.signal, failureCode);
|
|
93
|
+
await verifyLocalEnvironment(web, options, options.cwd, prepared.expectedVersion, options.signal, failureCode);
|
|
89
94
|
await postSwarmProfile(web, options, false, undefined, options.signal, failureCode);
|
|
90
|
-
await verifySwarmStatus(web, options, false, undefined, options.signal, failureCode);
|
|
95
|
+
await verifySwarmStatus(web, options, false, undefined, prepared.expectedVersion, options.signal, failureCode);
|
|
91
96
|
await shutdownWeb(web, options.signal, failureCode);
|
|
92
97
|
await requireSwarmJournal(prepared.kimiHome, options, "exit", options.signal, failureCode);
|
|
93
98
|
options.onRecoveryEvent?.({ kind: "disabled" });
|
|
@@ -124,6 +129,7 @@ async function prepareWebSpawn(options, failureCode) {
|
|
|
124
129
|
await assertNativeV2Preflight({
|
|
125
130
|
kimiHome,
|
|
126
131
|
env: options.env,
|
|
132
|
+
expectedExecutionRoot: options.cwd,
|
|
127
133
|
stage: "swarm-activation.pre-spawn",
|
|
128
134
|
});
|
|
129
135
|
}
|
|
@@ -329,13 +335,29 @@ async function resolveProfileModel(web, options, failureCode) {
|
|
|
329
335
|
}
|
|
330
336
|
return data.default_model;
|
|
331
337
|
}
|
|
332
|
-
async function createSession(web, options, failureCode) {
|
|
333
|
-
const
|
|
338
|
+
async function createSession(web, options, expectedVersion, failureCode) {
|
|
339
|
+
const request = { metadata: { cwd: options.cwd } };
|
|
340
|
+
if (["2.5.0", "2.5.1"].includes(expectedVersion)) {
|
|
341
|
+
request.environment_id = "local";
|
|
342
|
+
request.environment_cwd = options.cwd;
|
|
343
|
+
}
|
|
344
|
+
const data = await restData(web, "POST", "/api/v1/sessions", request, options.signal, failureCode);
|
|
334
345
|
if (!isRecord(data) || !validPathId(data.id) || !validPathId(data.workspace_id)) {
|
|
335
346
|
throw failure(failureCode, "swarm-activation.create-session", "Kimi web returned an invalid session identity.");
|
|
336
347
|
}
|
|
337
348
|
return { sessionId: data.id, workspaceId: data.workspace_id };
|
|
338
349
|
}
|
|
350
|
+
async function verifyLocalEnvironment(web, identity, expectedCwd, expectedVersion, signal, failureCode) {
|
|
351
|
+
if (!["2.5.0", "2.5.1"].includes(expectedVersion))
|
|
352
|
+
return;
|
|
353
|
+
const data = await restData(web, "GET", `/api/v1/sessions/${encodeURIComponent(identity.sessionId)}/environment`, undefined, signal, failureCode);
|
|
354
|
+
if (!isRecord(data)) {
|
|
355
|
+
throw failure(failureCode, "swarm-activation.environment", "Kimi web /environment returned an invalid payload.");
|
|
356
|
+
}
|
|
357
|
+
if (data.workspace_id !== identity.workspaceId || data.environment_id !== "local" || data.cwd !== expectedCwd) {
|
|
358
|
+
throw failure(failureCode, "swarm-activation.environment", "Kimi web /environment did not confirm the expected local workspace binding.");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
339
361
|
async function postSwarmProfile(web, identity, active, model, signal, failureCode) {
|
|
340
362
|
const agentConfig = {
|
|
341
363
|
swarm_mode: active,
|
|
@@ -345,24 +367,264 @@ async function postSwarmProfile(web, identity, active, model, signal, failureCod
|
|
|
345
367
|
agentConfig.model = model;
|
|
346
368
|
await restData(web, "POST", `/api/v1/sessions/${encodeURIComponent(identity.sessionId)}/profile`, { agent_config: agentConfig }, signal, failureCode);
|
|
347
369
|
}
|
|
348
|
-
async function verifySwarmStatus(web, identity, active, model, signal, failureCode) {
|
|
349
|
-
const data =
|
|
370
|
+
async function verifySwarmStatus(web, identity, active, model, expectedVersion, signal, failureCode) {
|
|
371
|
+
const data = ["2.5.0", "2.5.1"].includes(expectedVersion)
|
|
372
|
+
? await readWebSocketAgentState(web, identity.sessionId, signal, failureCode)
|
|
373
|
+
: await restData(web, "GET", `/api/v1/sessions/${encodeURIComponent(identity.sessionId)}/status`, undefined, signal, failureCode);
|
|
374
|
+
const source = ["2.5.0", "2.5.1"].includes(expectedVersion) ? "agent state" : "/status";
|
|
350
375
|
if (!isRecord(data)) {
|
|
351
|
-
throw failure(failureCode, "swarm-activation.status",
|
|
376
|
+
throw failure(failureCode, "swarm-activation.status", `Kimi web ${source} returned an invalid payload.`);
|
|
352
377
|
}
|
|
353
378
|
if (data.swarm_mode !== active) {
|
|
354
|
-
throw failure(failureCode, "swarm-activation.status", `Kimi web
|
|
379
|
+
throw failure(failureCode, "swarm-activation.status", `Kimi web ${source} did not confirm swarm_mode=${active}.`, { expected_swarm_mode: active });
|
|
355
380
|
}
|
|
356
381
|
if (data.plan_mode !== false) {
|
|
357
|
-
throw failure(failureCode, "swarm-activation.status",
|
|
382
|
+
throw failure(failureCode, "swarm-activation.status", `Kimi web ${source} did not confirm plan mode is off.`);
|
|
358
383
|
}
|
|
359
|
-
if (data.
|
|
360
|
-
throw failure(failureCode, "swarm-activation.status", "Kimi web
|
|
384
|
+
if (["2.5.0", "2.5.1"].includes(expectedVersion) && data.tower_mode !== false) {
|
|
385
|
+
throw failure(failureCode, "swarm-activation.status", "Kimi web agent state did not confirm tower mode is off.");
|
|
386
|
+
}
|
|
387
|
+
const permission = ["2.5.0", "2.5.1"].includes(expectedVersion) ? data.permission_mode : data.permission;
|
|
388
|
+
if (permission !== "auto") {
|
|
389
|
+
throw failure(failureCode, "swarm-activation.status", `Kimi web ${source} did not confirm auto permission mode.`);
|
|
361
390
|
}
|
|
362
391
|
if (active && data.model !== model) {
|
|
363
|
-
throw failure(failureCode, "swarm-activation.status",
|
|
392
|
+
throw failure(failureCode, "swarm-activation.status", `Kimi web ${source} did not confirm the selected model alias.`);
|
|
364
393
|
}
|
|
365
394
|
}
|
|
395
|
+
async function readWebSocketAgentState(web, sessionId, signal, failureCode) {
|
|
396
|
+
throwIfAborted(signal, failureCode, "swarm-activation.status");
|
|
397
|
+
await refreshOwnedDescendants(web);
|
|
398
|
+
throwIfAborted(signal, failureCode, "swarm-activation.status");
|
|
399
|
+
try {
|
|
400
|
+
return await new Promise((resolve, reject) => {
|
|
401
|
+
let socket;
|
|
402
|
+
const bearerProtocol = `kimi-code.bearer.${web.token}`;
|
|
403
|
+
const helloId = `swarm-activation-hello-${randomUUID()}`;
|
|
404
|
+
const subscribeId = `swarm-activation-subscribe-${randomUUID()}`;
|
|
405
|
+
try {
|
|
406
|
+
const url = new URL(web.origin);
|
|
407
|
+
url.protocol = "ws:";
|
|
408
|
+
url.pathname = "/api/v1/ws";
|
|
409
|
+
socket = new WebSocket(url, bearerProtocol);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
reject(failure(failureCode, "swarm-activation.status", "Could not open the authenticated Kimi web agent-state channel."));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
let settled = false;
|
|
416
|
+
let serverHello = false;
|
|
417
|
+
let clientHelloAcknowledged = false;
|
|
418
|
+
let subscribeSent = false;
|
|
419
|
+
let subscriptionAcknowledged = false;
|
|
420
|
+
let closeRequested = false;
|
|
421
|
+
let latestState;
|
|
422
|
+
let frames = 0;
|
|
423
|
+
let receivedChars = 0;
|
|
424
|
+
const timer = setTimeout(() => {
|
|
425
|
+
settle(failure(failureCode, "swarm-activation.status", "Kimi web agent-state verification timed out.", { timeout_ms: REST_TIMEOUT_MS }));
|
|
426
|
+
}, REST_TIMEOUT_MS);
|
|
427
|
+
const cleanup = () => {
|
|
428
|
+
clearTimeout(timer);
|
|
429
|
+
signal?.removeEventListener("abort", onAbort);
|
|
430
|
+
socket.removeEventListener("open", onOpen);
|
|
431
|
+
socket.removeEventListener("message", onMessage);
|
|
432
|
+
socket.removeEventListener("error", onError);
|
|
433
|
+
socket.removeEventListener("close", onClose);
|
|
434
|
+
try {
|
|
435
|
+
socket.close();
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const settle = (result) => {
|
|
441
|
+
if (settled)
|
|
442
|
+
return;
|
|
443
|
+
settled = true;
|
|
444
|
+
cleanup();
|
|
445
|
+
if (result instanceof RuntimeError)
|
|
446
|
+
reject(result);
|
|
447
|
+
else
|
|
448
|
+
resolve(result);
|
|
449
|
+
};
|
|
450
|
+
const rejectState = (message) => {
|
|
451
|
+
settle(failure(failureCode, "swarm-activation.status", message));
|
|
452
|
+
};
|
|
453
|
+
const send = (frame) => {
|
|
454
|
+
try {
|
|
455
|
+
socket.send(JSON.stringify(frame));
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
rejectState("Could not send a Kimi web agent-state control frame.");
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const completeIfReady = () => {
|
|
464
|
+
if (serverHello &&
|
|
465
|
+
clientHelloAcknowledged &&
|
|
466
|
+
subscriptionAcknowledged &&
|
|
467
|
+
latestState !== undefined &&
|
|
468
|
+
!closeRequested) {
|
|
469
|
+
closeRequested = true;
|
|
470
|
+
try {
|
|
471
|
+
socket.close();
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
rejectState("Could not close the Kimi web agent-state channel for final validation.");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const onOpen = () => {
|
|
479
|
+
if (socket.protocol !== bearerProtocol) {
|
|
480
|
+
rejectState("Kimi web did not authenticate the agent-state channel with the expected subprotocol.");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
send({
|
|
484
|
+
type: "client_hello",
|
|
485
|
+
id: helloId,
|
|
486
|
+
payload: { client_id: "kimi-plugin-cc-swarm-activation" },
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const onMessage = (event) => {
|
|
490
|
+
if (settled)
|
|
491
|
+
return;
|
|
492
|
+
if (typeof event.data !== "string") {
|
|
493
|
+
rejectState("Kimi web agent-state channel returned a non-text frame.");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
frames += 1;
|
|
497
|
+
receivedChars += event.data.length;
|
|
498
|
+
if (frames > WEBSOCKET_MAX_FRAMES || receivedChars > WEBSOCKET_RESPONSE_MAX_CHARS) {
|
|
499
|
+
rejectState("Kimi web agent-state channel exceeded the bounded response limit.");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
let frame;
|
|
503
|
+
try {
|
|
504
|
+
frame = JSON.parse(event.data);
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
rejectState("Kimi web agent-state channel returned malformed JSON.");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (!isRecord(frame) || typeof frame.type !== "string") {
|
|
511
|
+
rejectState("Kimi web agent-state channel returned an invalid frame.");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (frame.type === "server_hello") {
|
|
515
|
+
if (serverHello || !isRecord(frame.payload) ||
|
|
516
|
+
frame.payload.protocol_version !== 2 ||
|
|
517
|
+
typeof frame.payload.ws_connection_id !== "string" ||
|
|
518
|
+
!isRecord(frame.payload.capabilities) ||
|
|
519
|
+
frame.payload.capabilities.event_batching !== false ||
|
|
520
|
+
frame.payload.capabilities.compression !== false) {
|
|
521
|
+
rejectState("Kimi web agent-state channel did not provide the expected protocol-v2 hello.");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
serverHello = true;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (!serverHello) {
|
|
528
|
+
rejectState("Kimi web agent-state channel sent data before its protocol hello.");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (frame.type === "ack") {
|
|
532
|
+
if (typeof frame.id !== "string" || typeof frame.code !== "number" || !isRecord(frame.payload)) {
|
|
533
|
+
rejectState("Kimi web agent-state channel returned an invalid acknowledgement.");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (frame.id === helloId) {
|
|
537
|
+
if (clientHelloAcknowledged || frame.code !== 0) {
|
|
538
|
+
rejectState("Kimi web agent-state channel rejected the client hello.");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
clientHelloAcknowledged = true;
|
|
542
|
+
subscribeSent = send({
|
|
543
|
+
type: "subscribe_v2",
|
|
544
|
+
id: subscribeId,
|
|
545
|
+
payload: { session_id: sessionId, transcript: { main: "block" } },
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (frame.id === subscribeId) {
|
|
550
|
+
if (subscriptionAcknowledged || !subscribeSent || frame.code !== 0 ||
|
|
551
|
+
!Array.isArray(frame.payload.accepted) ||
|
|
552
|
+
frame.payload.accepted.length !== 1 ||
|
|
553
|
+
frame.payload.accepted[0] !== sessionId ||
|
|
554
|
+
!Array.isArray(frame.payload.not_found) ||
|
|
555
|
+
frame.payload.not_found.length !== 0) {
|
|
556
|
+
rejectState("Kimi web agent-state channel did not accept the exact main-session subscription.");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
subscriptionAcknowledged = true;
|
|
560
|
+
completeIfReady();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
rejectState("Kimi web agent-state channel returned an unexpected acknowledgement.");
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (frame.type === "agent") {
|
|
567
|
+
if (!subscribeSent || !isAgentStateFrame(frame, sessionId)) {
|
|
568
|
+
rejectState("Kimi web agent-state channel returned invalid or misbound main-agent state.");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
latestState = frame;
|
|
572
|
+
completeIfReady();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (frame.type === "ping") {
|
|
576
|
+
if (isRecord(frame.payload) && typeof frame.payload.nonce === "string") {
|
|
577
|
+
send({ type: "pong", payload: { nonce: frame.payload.nonce } });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const onAbort = () => {
|
|
582
|
+
settle(cancelledFailure(failureCode, "swarm-activation.status"));
|
|
583
|
+
};
|
|
584
|
+
const onError = () => {
|
|
585
|
+
rejectState("Kimi web agent-state channel failed before valid state was received.");
|
|
586
|
+
};
|
|
587
|
+
const onClose = (event) => {
|
|
588
|
+
if (event.wasClean &&
|
|
589
|
+
closeRequested &&
|
|
590
|
+
serverHello &&
|
|
591
|
+
clientHelloAcknowledged &&
|
|
592
|
+
subscriptionAcknowledged &&
|
|
593
|
+
latestState !== undefined) {
|
|
594
|
+
settle(latestState);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
rejectState("Kimi web agent-state channel closed before valid state was received.");
|
|
598
|
+
};
|
|
599
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
600
|
+
socket.addEventListener("open", onOpen);
|
|
601
|
+
socket.addEventListener("message", onMessage);
|
|
602
|
+
socket.addEventListener("error", onError);
|
|
603
|
+
socket.addEventListener("close", onClose);
|
|
604
|
+
if (signal?.aborted === true)
|
|
605
|
+
onAbort();
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
await refreshOwnedDescendants(web);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function isAgentStateFrame(frame, sessionId) {
|
|
613
|
+
return frame.session_id === sessionId &&
|
|
614
|
+
frame.agent_id === "main" &&
|
|
615
|
+
typeof frame.event_created_at === "string" &&
|
|
616
|
+
Number.isFinite(Date.parse(frame.event_created_at)) &&
|
|
617
|
+
(frame.status === "idle" || frame.status === "running") &&
|
|
618
|
+
(frame.model === undefined || typeof frame.model === "string") &&
|
|
619
|
+
(frame.thinking === undefined || typeof frame.thinking === "string") &&
|
|
620
|
+
(frame.permission_mode === undefined ||
|
|
621
|
+
frame.permission_mode === "manual" ||
|
|
622
|
+
frame.permission_mode === "yolo" ||
|
|
623
|
+
frame.permission_mode === "auto") &&
|
|
624
|
+
typeof frame.plan_mode === "boolean" &&
|
|
625
|
+
typeof frame.swarm_mode === "boolean" &&
|
|
626
|
+
typeof frame.tower_mode === "boolean";
|
|
627
|
+
}
|
|
366
628
|
async function shutdownWeb(web, signal, failureCode) {
|
|
367
629
|
await restData(web, "POST", "/api/v1/shutdown", undefined, signal, failureCode);
|
|
368
630
|
const settlement = await waitForChildClose(web, signal, failureCode);
|
package/dist/version.js
CHANGED
package/docs/migration.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Compatibility and upgrades
|
|
2
2
|
|
|
3
|
-
Plugin 2.
|
|
3
|
+
Plugin 2.5.1 certifies Kimi Code CLI 2.4.1 for all eight operations, alongside exact native-v2 versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, `2.3.2`, `2.3.3`, and `2.4.0`. Later patches and minor releases are not accepted automatically. The npm distribution adds an installation channel; it does not change this compatibility table.
|
|
4
|
+
|
|
5
|
+
Plugin 2.5.1 also adds DeepSeek Harness as a third host with all seven operations and a DSH-specific bundle, data root, and managed hook id. Existing Claude Code and Codex certification, job stores, and host-scoped hooks are unchanged; no migration is required for those hosts. New DSH installations follow the [README DSH section](../README.md#install-in-deepseek-harness), including `/kimi-setup` on that host.
|
|
4
6
|
|
|
5
7
|
Explicitly pinned legacy-v1 binaries remain supported within `KIMI_TESTED_MINORS`, through 0.41.x, subject to each operation's minimum version. The [runtime table](../runtime/kimi-engine.ts) is the authority.
|
|
6
8
|
|
|
@@ -8,15 +10,15 @@ Use the [README installation and update steps](../README.md) for Claude Code or
|
|
|
8
10
|
|
|
9
11
|
## Plugin versions and upgrades
|
|
10
12
|
|
|
11
|
-
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.
|
|
13
|
+
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.5.1 certifies exact CLI 2.4.1; the nearby numbers do not imply version alignment. Published versions and tags are never reused. Every upstream patch requires its own certification. The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
|
|
12
14
|
|
|
13
|
-
## CLI 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
15
|
+
## CLI 2.3.2, 2.3.3, 2.4.0, 2.4.1 and 2.5.0/2.5.1 swarm activation
|
|
14
16
|
|
|
15
|
-
Upstream 2.3.2 removes model self-activation of `AgentSwarm` in fresh print sessions. On exact 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
17
|
+
Upstream 2.3.2 removes model self-activation of `AgentSwarm` in fresh print sessions. On exact 2.3.2, 2.3.3, 2.4.0, 2.4.1 and 2.5.0/2.5.1, swarm commands and MCP calls use the same `runSwarm` lifecycle:
|
|
16
18
|
|
|
17
|
-
1. A short-lived `web --no-open --host 127.0.0.1 --port 0 --log-level error` process uses authenticated loopback routes to verify the exact v2 server, choose the explicit or configured default model, create a session, bind the model and auto permission, enable and verify `swarm_mode`, and shut down. The child environment sets `KIMI_CODE_SESSION_IDLE_RECLAIM_MS=0` to disable ambient idle-session reclamation.
|
|
19
|
+
1. A short-lived `web --no-open --host 127.0.0.1 --port 0 --log-level error` process uses authenticated loopback routes to verify the exact v2 server, choose the explicit or configured default model, create a session, bind the model and auto permission, enable and verify `swarm_mode`, and shut down. On exact 2.5.0/2.5.1, creation explicitly selects the local environment and an authenticated `/environment` read proves its cwd before profile mutation; state verification uses protocol-v2 WebSocket main-agent frames because `/status` was removed. The child environment sets `KIMI_CODE_SESSION_IDLE_RECLAIM_MS=0` to disable ambient idle-session reclamation.
|
|
18
20
|
2. One ordinary `kimi -p -r` prompt runner resumes that session. Its recorded session-id source is `preactivated`, and the returned print session must match it.
|
|
19
|
-
3. A second short-lived web lifecycle disables swarm mode, verifies
|
|
21
|
+
3. A second short-lived web lifecycle rechecks the local binding on 2.5.0/2.5.1, disables swarm mode, verifies the version-appropriate state proof and the `swarm_mode.exit` journal record, and shuts down.
|
|
20
22
|
|
|
21
23
|
Activation is inside the job's finite budget. Graceful process close has a bounded 15-second allowance. `runSwarm` solely owns deactivation: normal cleanup uses the remaining budget, while failure recovery has a separate two-second cancellation-aware bound and is never retried. Print/session failures stay primary. The ready token stays in memory and out of logs, and success cannot complete if cleanup fails. Post-mutation identity or process/group uncertainty preserves diagnostics and, for write-swarm, the recovery worktree. Process groups are rechecked after teardown; unknown survivors are unverified and unproven groups are not signalled. Older certified versions still launch swarm directly with `kimi -p`. This narrow lifecycle is not a full RPC transport or a model-driven activation workaround; the managed hook and workspace boundaries still govern every tool call.
|
|
22
24
|
|
|
@@ -8,6 +8,12 @@ no separate CLI.
|
|
|
8
8
|
|
|
9
9
|
## Publishing status
|
|
10
10
|
|
|
11
|
+
Plugin **2.5.3** certifies exact Kimi Code **2.5.0 and 2.5.1** for all eight operations, adding local-execution/workspace-trust closures and authenticated WebSocket swarm activation with clean-close validation while preserving the no-plan construction and legacy-v1 0.41.x boundary. See the [2.5.0](./upstream-2.5.0-certification.md) and [2.5.1](./upstream-2.5.1-certification.md) certification records. The 2.5.1 source scan passed 18 tests with 153 assertions; candidate and production matrices each passed 24 tests with 167 assertions and no skips. The full check passed 1,412 tests with 18,282 assertions, package validation passed 330 files across all three distributions, isolated consumer security passed, and independent read-only `claude -p` review returned APPROVE. Release-commit CI, OIDC publication, registry artifact identity, and the final Discover timestamp are recorded here after publication.
|
|
12
|
+
|
|
13
|
+
Plugin **2.5.2** hardens setup recovery and DSH dependency ownership. Its real Kimi Code **2.4.1** smoke passed **24 tests and 167 assertions with no skips**; the exact-source audit passed **16 tests and 97 assertions**. Normal npm installation keeps DSH host services optional. DSH use requires explicitly patched host/profile dependencies and both installation audits described below. Exact CLI certification remains unchanged. [Release v2.5.2](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.5.2) is anchored at `8b303bb6e49c581ad96c72bd031e8fd1643c9121`. [Release-commit CI](https://github.com/linxule/kimi-plugin-cc/actions/runs/41361753223) and [npm publication](https://github.com/linxule/kimi-plugin-cc/actions/runs/41362262009) passed. The 328-file registry tarball has SHA-1 `e620040dd35986a6929be7ad677642729a3126e7`, matches the validated workflow artifact byte-for-byte, and carries provenance (sigstore log index `3636981979`). A fresh registry npm installation has zero production advisories; isolated Claude Code, Codex, and DSH setup/checks pass, and all compiled runtime mirrors match. The Discover timestamp records npm publication at `2026-11-01T23:35:04.270Z`.
|
|
14
|
+
|
|
15
|
+
Plugin **2.5.1** adds DeepSeek Harness through the existing root npm package and its established OIDC workflow. [Release v2.5.1](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.5.1) is anchored at `afe68ee2f0e7744e1fc97eb0f1e4007585d969d0`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/41335810767) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/41336273042) passed. The registry tarball has SHA-1 `f0e42febcb14082be6a654b9b0830885242fd92a`, contains 328 files, matches the CI artifact, and carries signed provenance (sigstore log index `3635166158`). A fresh registry installation into an isolated DSH profile composed all seven providers and booted the Web host. DSH users install with `dsh plugin --profile <name> add kimi-plugin-cc`; no second npm package or Trusted Publishing binding is required.
|
|
16
|
+
|
|
11
17
|
Plugin **2.4.5** certifies exact CLI **2.4.1** for all eight operations with host-preactivated swarm sessions. [Release v2.4.5](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.4.5) is anchored at `de8c195c2986443a088b674717133b164d2e4ef2`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226096281) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/41226438326) passed. The registry tarball has SHA-1 `39f97a6de4b26ad51ac77305054682fcd5cd0164`, contains 233 files, and matches the CI artifact byte-for-byte. Fresh registry installs passed setup/check and unsupported-version refusal for both Claude Code and Codex, and both compiled mirrors are byte-identical to the release checkout. See the [certification record](./upstream-2.4.1-certification.md) and structured verification at `.claude/kimi-code-research/release-2.4.5/registry-verification.json`.
|
|
12
18
|
|
|
13
19
|
Plugin **2.4.2** adds durable interrupted-swarm recovery and Windows spawn diagnostics.
|
|
@@ -78,9 +84,11 @@ file at the tag, so fixes to `publish.yml` reach an existing tag only through
|
|
|
78
84
|
## Validation
|
|
79
85
|
|
|
80
86
|
Use the repository's normal version bump and generated-surface procedure in
|
|
81
|
-
AGENTS.md. Keep plugin and npm versions identical
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
AGENTS.md. Keep plugin and npm versions identical, and update
|
|
88
|
+
`DSH_PUBLISH_TIMESTAMP` with the actual publication time so the generated DSH
|
|
89
|
+
Discover index stays current. Certification for new Kimi Code versions still
|
|
90
|
+
requires the source audit and real-binary controls; adding a registry channel
|
|
91
|
+
does not certify another CLI version.
|
|
84
92
|
|
|
85
93
|
```sh
|
|
86
94
|
bun install --frozen-lockfile
|
|
@@ -88,6 +96,7 @@ bun run build
|
|
|
88
96
|
bun run generate:surfaces
|
|
89
97
|
# Review and stage generated distributions before the drift gate.
|
|
90
98
|
bun run check
|
|
99
|
+
bun run check:consumer-security
|
|
91
100
|
node scripts/check-package.mjs .tmp/npm
|
|
92
101
|
node scripts/check-dsh-package.mjs .tmp/dsh-npm
|
|
93
102
|
```
|
|
@@ -101,6 +110,60 @@ check installs that same root tarball into an isolated DSH home, verifies all
|
|
|
101
110
|
seven tools, and boots the Web host. Neither check invokes a model or reads the
|
|
102
111
|
maintainer's Kimi configuration.
|
|
103
112
|
|
|
113
|
+
The consumer-security gate installs the packed package into a fresh npm project
|
|
114
|
+
and audits the resolved production dependency graph. DSH services and its tool
|
|
115
|
+
adapter are optional peers supplied by the DSH host; ordinary npm installation
|
|
116
|
+
for Claude Code or Codex does not install the harness. Optional peers still
|
|
117
|
+
carry compatibility ranges when present. The gate runs in CI and release
|
|
118
|
+
validation and refuses high/critical advisories, failed audits, and incomplete
|
|
119
|
+
reports. The checkout's overrides do not constrain downstream npm resolution.
|
|
120
|
+
|
|
121
|
+
### Patched DSH host and profiles
|
|
122
|
+
|
|
123
|
+
DSH 0.2.1-rc.5 and 0.2.2-alpha.2 still pin vulnerable `zod` and `undici`
|
|
124
|
+
versions. See the [upstream dependency report](https://github.com/deepseek-ai/deepseek-harness/discussions/10847).
|
|
125
|
+
Use a patched DSH host installation with `zod >=4.6.6` and `undici >=8.12.2`.
|
|
126
|
+
This is an explicit installation requirement; the plugin does not rewrite the
|
|
127
|
+
host or profiles automatically. A profile override alone does not patch copies
|
|
128
|
+
inside a separately installed DSH CLI.
|
|
129
|
+
|
|
130
|
+
For example, create a dedicated npm host project (Node >=24.9), then use its
|
|
131
|
+
DSH binary for plugin installation and launch:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
mkdir -p ~/.local/share/kimi-dsh-host
|
|
135
|
+
cd ~/.local/share/kimi-dsh-host
|
|
136
|
+
npm init -y
|
|
137
|
+
npm pkg set 'overrides.zod=^4.6.6' 'overrides.undici=^8.12.2'
|
|
138
|
+
npm install @deepseek-ai/dsh@0.2.1-rc.5
|
|
139
|
+
npm audit --omit=dev --audit-level=high
|
|
140
|
+
./node_modules/.bin/dsh plugin --profile web add kimi-plugin-cc
|
|
141
|
+
./node_modules/.bin/dsh plugin --profile web audit --prod --audit-level high
|
|
142
|
+
./node_modules/.bin/dsh web --no-open
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The host project is separate from `$DSH_HOME/profiles/web`. Keep using that
|
|
146
|
+
project's DSH binary so the audited host is the one that runs. The commands
|
|
147
|
+
above install the published plugin; use a validated local tarball when testing
|
|
148
|
+
an unpublished change.
|
|
149
|
+
|
|
150
|
+
If an existing profile separately installs DSH packages, its owner must also
|
|
151
|
+
patch and audit that profile:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
dsh plugin --profile web pkg set 'pnpm.overrides.zod=^4.6.6' 'pnpm.overrides.undici=^8.12.2'
|
|
155
|
+
dsh plugin --profile web install
|
|
156
|
+
dsh plugin --profile web audit --prod --audit-level high
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Replace `web` with the intended profile, and run these commands with the patched
|
|
160
|
+
host binary. Overrides affect dependency resolution and require application
|
|
161
|
+
checks. The DSH package gate creates a fresh host with the declared overrides,
|
|
162
|
+
audits both that host and the installed profile for high/critical advisories,
|
|
163
|
+
then verifies all seven tool compositions and Web boot using that same host.
|
|
164
|
+
These checks do not claim that every moderate/low advisory is fixed or that
|
|
165
|
+
upstream has published a patched release.
|
|
166
|
+
|
|
104
167
|
## First publication (completed bootstrap reference)
|
|
105
168
|
|
|
106
169
|
The package must first exist on npm before its package settings can be configured.
|
package/docs/safety.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This guide describes the current checkout runtime. Read-only tasks deny file writes and shell commands. Rescue and pursue can edit your workspace. Write-swarm returns a patch from a temporary Git worktree.
|
|
4
4
|
|
|
5
|
-
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, `2.3.2`, `2.3.3`, `2.4.0`, and `2.
|
|
5
|
+
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, `2.3.2`, `2.3.3`, `2.4.0`, `2.4.1`, `2.5.0`, and `2.5.1`, with plan mode disabled. The [runtime certification table](../runtime/kimi-engine.ts) controls routing. The hook is not an operating-system sandbox.
|
|
6
6
|
|
|
7
7
|
## Why the safety story is hook-based
|
|
8
8
|
|
|
@@ -72,7 +72,7 @@ Key constraints (verified by [`runtime/hooks/managed-block.ts`](../runtime/hooks
|
|
|
72
72
|
1. **No `matcher` line.** kimi-code compiles the matcher field via `new RegExp(matcher)`. An empty matcher means "fire for every tool." `new RegExp("*")` throws, which kimi-code catches and treats as "no matcher" → silently disables the hook. The installer omits the field intentionally; the verifier rejects any block that contains a `matcher = ...` line.
|
|
73
73
|
2. **Absolute Node path.** kimi-code spawns hook commands via `/bin/sh -c "<command>"`. If we wrote bare `node`, the shell would resolve it against kimi-code's PATH at execution time. On a system where kimi-code launches from a GUI/LaunchAgent with a sanitized PATH (nvm/asdf/mise users), bare `node` would exit 127 → hook protocol treats non-{0,2} as **allow** → safety contract collapses with no user-visible signal.
|
|
74
74
|
3. **TOML basic-string escaping.** The command field is a TOML basic string. Quotes, backslashes, newlines, and control characters in the resolved hook path are escaped during install; paths containing characters that cannot be safely escaped (literal `"`, raw control chars) are rejected up front with `SETUP_HOOK_PATH_UNSAFE`.
|
|
75
|
-
4. **Exact marker grammar.** The verifier rejects
|
|
75
|
+
4. **Exact marker grammar.** The verifier rejects duplicate blocks and orphan or mismatched host markers. Install removes lexically proven unpaired marker comments across hosts, preserving surrounding content and all foreign hook tables, and warns. Text inside TOML strings is not a marker. The verifier remains fail-closed until repair and full validation succeed.
|
|
76
76
|
5. **Exact command equality, per host.** The verifier reconstructs the canonical command byte string from the current env and equality-checks the installed `command` (never a substring — a crafted `true # /path/to/approval-hook.js` runs only `true` and would auto-allow). This holds *per host id* (below): each host verifies its own block.
|
|
77
77
|
6. **Full-config parse and whole-hook-array validation.** The verifier parses the complete file with vendored `smol-toml@1.9.1` with upstream fixes included and no local JavaScript modifications (see `runtime/vendor/smol-toml/README.md`), before inspecting the managed block. It then applies upstream's strict config schema to every configured hook (`event`, optional string `matcher`, non-empty `command`, optional integer `timeout` 1–600, no extra fields). A malformed unrelated TOML value or one invalid foreign hook can make kimi-code discard enforcement; both therefore fail verification.
|
|
78
78
|
7. **Serialized, identity-bound setup mutations.** Install and uninstall hold a private `0o600` sibling lock across the complete read-modify-write transaction. The candidate lock is published atomically; lock reads are no-follow, nonblocking, regular-file-only, and capped at 4 KiB. Acquisition is bounded, live owners are never stolen, stale recovery is guarded by a nonrecursive recovery lease, and inode/token ownership is rechecked before config mutation or lock removal. Simultaneous Claude Code/Codex setup runs therefore cannot lose either host's block, and a replaced lock is never treated as ours. A newly created Kimi home is `0o700`; atomic config writes remain `0o600`.
|
|
@@ -128,6 +128,8 @@ uninstalls only its **own** block.
|
|
|
128
128
|
|
|
129
129
|
## DeepSeek Harness providers
|
|
130
130
|
|
|
131
|
+
DSH's host and any profile-local harness packages must use patched `zod >=4.6.6` and `undici >=8.12.2`. The plugin treats DSH services as optional host-provided peers; it does not install the harness for other hosts or rewrite an existing DSH installation. Audit the actual host installation as well as the profile. See [patched DSH setup](./registry-releases.md#patched-dsh-host-and-profiles).
|
|
132
|
+
|
|
131
133
|
The DSH bundle exposes the same seven operations as the other hosts: `kimi_ask`, `kimi_review`, `kimi_challenge`, `kimi_rescue`, `kimi_pursue`, `kimi_swarm`, and `kimi_swarm_write`. Each activation starts the existing companion through DSH's managed subprocess service and uses the same `kimi -p` transport, hook verification, exact-version certification, job store, and response or safety budgets. ACP is not used.
|
|
132
134
|
|
|
133
135
|
Run `/kimi-setup` explicitly before delegating. Installing or enabling the bundle does not modify Kimi configuration. DSH uses host id `dsh` and `$DSH_HOME/plugin-data/kimi-plugin-cc` unless an absolute data-root override selects another store. Its provider-owned environment clears other-host roots/data, hook/script overrides, trusted roots, and verification bypasses instead of inheriting them.
|
|
@@ -212,23 +214,23 @@ If another installed host still points to an older hook, that hook may veto thes
|
|
|
212
214
|
|
|
213
215
|
> **Goal-mode entry gate (changed in 0.12.0).** Headless goal mode is reached by a `/goal`-prefixed prompt. On kimi-code 0.8–0.11 it is *additionally* gated by the `goal-command` experimental flag, which `pursue.ts` sets per-spawn (`KIMI_CODE_EXPERIMENTAL_GOAL_COMMAND=1`). kimi-code 0.12.0 (PR #569) **removed that experimental-flag gate**, so the `/goal` prefix alone triggers goal mode. This does not weaken plugin safety: read-only commands (review/challenge/ask/review_gate) hard-prefix an English instruction line, so their trimmed prompt never starts with `/goal` and they cannot enter goal mode on any version; and the managed PreToolUse hook denies every write under a read-only label regardless (see § Native agent-core-v2 for why the hook cannot be pre-empted). The env var `pursue.ts` sets is now redundant on 0.12.0 but harmless, and is kept for 0.8–0.11 compat.
|
|
214
216
|
|
|
215
|
-
- **The hook fires on every tool call in every continuation turn.** On native v2 every tool execution passes `prepareToolCall` → `fireBeforeExecute`, where the external-hooks listener vetoes ahead of any terminal allow (the auto-approve gate uses non-terminal `pass()`; the engine's only `allow()` is the plan-file guard, never armed in a plugin-managed session — see § Native agent-core-v2). (On legacy-v1 ≤ 0.41 the same guarantee came from `PreToolCallHookPermissionPolicy` sitting at policy index 0.) Goal continuation turns are ordinary turns — there is no autonomous-continuation path that bypasses per-tool approval (verified against kimi-code 0.9.0, audit report 58; re-verified through 0.12.0 by the goal-mode real-binary smoke, reports 61-65). A goal-mode run is exactly as write-gated as a single-turn rescue. This is locked by a real-binary smoke (`tests/runtime/real-binary-smoke.test.ts`, run via `KIMI_PLUGIN_CC_SMOKE=1 bun test ... -t "goal mode"`): it runs real headless goal mode under a read-only label
|
|
217
|
+
- **The hook fires on every tool call in every continuation turn.** On native v2 every tool execution passes `prepareToolCall` → `fireBeforeExecute`, where the external-hooks listener vetoes ahead of any terminal allow (the auto-approve gate uses non-terminal `pass()`; the engine's only `allow()` is the plan-file guard, never armed in a plugin-managed session — see § Native agent-core-v2). (On legacy-v1 ≤ 0.41 the same guarantee came from `PreToolCallHookPermissionPolicy` sitting at policy index 0.) Goal continuation turns are ordinary turns — there is no autonomous-continuation path that bypasses per-tool approval (verified against kimi-code 0.9.0, audit report 58; re-verified through 0.12.0 by the goal-mode real-binary smoke, reports 61-65). A goal-mode run is exactly as write-gated as a single-turn rescue. This is locked by a real-binary smoke (`tests/runtime/real-binary-smoke.test.ts`, run via `KIMI_PLUGIN_CC_SMOKE=1 bun test ... -t "goal mode"`): it runs real headless goal mode under a read-only label, asserts that the entire workspace is empty at settlement, and requires same-call managed error denials in at least two distinct turns. It does not prove that no transient file ever existed.
|
|
216
218
|
- **The only new risk is unboundedness, and the plugin bounds it.** Headless goal create sets no token/turn budget, and the managed hook denies `SetGoalBudget`; the plugin's external wall-clock stop is therefore the guaranteed bound. `/kimi:pursue` enforces a **mandatory finite wall-clock ceiling** via its AbortController (`--budget`, default 45m) — the SIGTERM→SIGKILL descendant-reaping cancellation reaps the whole goal process tree. `--turns` is a *soft* instruction injected into the objective, never a request to call the denied `SetGoalBudget` tool; it is not enforceable headless, and stopping at that soft hint is not proof of completion. Treat `--budget` as the real bound.
|
|
217
219
|
- **Loop runaway is now also capped upstream.** kimi-code 0.8.0 (#364) detects repeated identical tool calls and force-stops a turn at a repeat streak. This benefits `/kimi:rescue`, `/kimi:ask`, and `/kimi:pursue` alike: a force-stopped turn ends *normally* (exit 0, flows through the plugin's success path — not a failure), reducing the chance an autonomous run spins against the wall-clock budget. It is a complement to, not a replacement for, the wall-clock ceiling.
|
|
218
220
|
- **Resume is intentionally not exposed yet.** In goal mode `goal.summary` emits a *goalId* distinct from the resume-hint *sessionId*; `kimi -r <sessionId>` re-enters the session but not necessarily the goal continuation. The plugin captures and surfaces the goalId but does not offer `--resume` for pursue until that split is reconciled upstream — exposing it now would be a silent-failure trap. **Re-verified against kimi-code 0.26.0 (2026-07-16):** the headless CLI still has no argv, prompt syntax, or session-resume path that calls `resumeGoal()`. `parseHeadlessGoalCreate` (`apps/kimi-code/src/cli/goal-prompt.ts`) accepts only the `/goal <objective>` *create* shape on both the v1 and experimental-v2 drivers, and upstream's own docs (`docs/en/guides/goals.md`, § "Use goal mode carefully") state that in non-interactive prompt mode *only goal creation is supported* — goal management is a TUI/kimi-web control. PR #552 makes a plain `-r <sessionId>` correctly *restore* the paused goal snapshot, but nothing in the headless path *acts* on it; the real `resumeGoal()` is wired only to the TUI (`/goal resume`) and kimi-web RPC (PR #1693, web-only). Implementing `--resume` would therefore require either prompt-injected model compliance (rejected — the plugin bounds autonomy with hard limits, not model goodwill) or abandoning the subprocess model for kimi-web's RPC. Re-check if upstream ever ships a headless `goal resume` verb.
|
|
219
221
|
- **A model-invocable `kimi-pursue` agent (v1.3) wraps this command.** The main thread can dispatch autonomous goal mode, not just the human-typed `/kimi:pursue` — the same command↔agent mirror as rescue/review/swarm. Auto-dispatch does NOT widen the blast radius: pursue runs under `commandLabel: "rescue"`, so the managed PreToolUse hook + workspace allowlist gate every tool call on every continuation turn (no git mutation), and the mandatory `--budget` wall-clock ceiling bounds the autonomy regardless of who launched it. Because it is write-capable AND autonomous — the plugin's highest blast radius — the agent's description requires an explicit user request for hands-off multi-turn pursuit, steers single bounded tasks to `kimi-rescue`, and never auto-promotes a fix or a question into a goal loop. Like the command, it refuses without the hook and offers no `--resume`.
|
|
220
222
|
|
|
221
|
-
## Swarm activation on CLI 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
223
|
+
## Swarm activation on CLI 2.3.2, 2.3.3, 2.4.0, 2.4.1, 2.5.0 and 2.5.1
|
|
222
224
|
|
|
223
225
|
For interrupted runs, see [durable recovery and its explicit limits](swarm-recovery.md).
|
|
224
226
|
|
|
225
227
|
Upstream 2.3.2 removes model self-activation of `AgentSwarm` in fresh print sessions. A rejected call can therefore coexist with zero child journals and a successful exit; a tool-call record, aggregate denial, or coordinator-produced patch does not prove fan-out. The plugin does not use prompt workarounds or replace its print transport with general RPC.
|
|
226
228
|
|
|
227
|
-
For versions requiring activation (currently exact 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
229
|
+
For versions requiring activation (currently exact 2.3.2, 2.3.3, 2.4.0, 2.4.1, 2.5.0 and 2.5.1), both slash-command and MCP swarm operations use the same `runSwarm` implementation and one ordinary `kimi -p -r` prompt runner per job, bracketed by short-lived host-managed web lifecycles:
|
|
228
230
|
|
|
229
|
-
1. The exact planned CLI starts `web --no-open --host 127.0.0.1 --port 0 --log-level error`. The child environment sets `KIMI_CODE_SESSION_IDLE_RECLAIM_MS=0`, so ambient idle-session reclamation cannot disrupt the plugin-owned lifecycle. The plugin accepts only the expected loopback ready URL, uses a supplied token for authenticated REST requests, and keeps that token in memory, never writing it to logs. `/api/v1/meta` must confirm backend v2, the exact planned version, and no dangerous authentication bypass. The plugin uses the explicit model or environment-model alias, otherwise reading the default alias from `/api/v1/config`; creates a session in the job cwd; binds that model with `permission_mode:auto` and `swarm_mode:true`; verifies those settings and `plan_mode:false` through `/status
|
|
231
|
+
1. The exact planned CLI starts `web --no-open --host 127.0.0.1 --port 0 --log-level error`. The child environment sets `KIMI_CODE_SESSION_IDLE_RECLAIM_MS=0`, so ambient idle-session reclamation cannot disrupt the plugin-owned lifecycle. The plugin accepts only the expected loopback ready URL, uses a supplied token for authenticated REST requests, and keeps that token in memory, never writing it to logs. `/api/v1/meta` must confirm backend v2, the exact planned version, and no dangerous authentication bypass. The plugin uses the explicit model or environment-model alias, otherwise reading the default alias from `/api/v1/config`; creates a session in the job cwd; on exact 2.5.0/2.5.1 explicitly binds `environment_id:"local"` and the job cwd, then verifies that binding through authenticated `/environment` before either profile mutation; binds the selected model with `permission_mode:auto` and `swarm_mode:true`; verifies those settings and `plan_mode:false` through `/status` on earlier versions or protocol-v2 authenticated WebSocket main-agent state on 2.5.0/2.5.1, closing that channel cleanly before validating the latest frame; verifies clean shutdown; and requires the main journal's manual `swarm_mode.enter` record.
|
|
230
232
|
2. The ordinary stream-json print runner resumes that session with `kimi -p -r`. The job records `kimi_session_id_source:"preactivated"`, and the session returned by print must exactly match the preactivated id. Every coordinator and child tool call remains governed by the same managed PreToolUse hook and operation allowlist.
|
|
231
|
-
3. After the print run, a second web lifecycle sets `swarm_mode:false`, verifies
|
|
233
|
+
3. After the print run, a second web lifecycle rechecks the local binding on 2.5.0/2.5.1, sets `swarm_mode:false`, verifies the version-appropriate state proof, shuts down cleanly, and requires `swarm_mode.exit` in the main journal. Activation consumes part of the same finite job budget rather than extending it. `runSwarm` solely owns session deactivation: normal cleanup uses the remaining budget, while failure recovery has a separate two-second cancellation-aware bound and is never retried. Print/session failures stay primary, with exit codes and cleanup diagnostics preserved. Cancellation aborts the owned lifecycle and never starts a fresh cleanup server. Every post-mutation failure retains the session identity; process/group uncertainty is marked without inventing an ID; process groups are rechecked after teardown, unknown survivors are unverified, unproven groups are never signalled, and a same-PID/start-token process that changes PGID remains active but is not a validated signal target. Root loss requires a previously observed matching identity; first-missing roots and actual probe errors/timeouts remain unverified. Reparenting and PGID moves preserve the current verified child identity for revalidation rather than treating it as exited. Unverified cleanup prevents success and preserves raw output, structured diagnostics, and the write worktree for recovery.
|
|
232
234
|
|
|
233
235
|
Older certified versions keep the direct `kimi -p` path. The web processes are narrow, planned lifecycle helpers, not a second model runner and not a general RPC transport.
|
|
234
236
|
|
|
@@ -237,7 +239,7 @@ Older certified versions keep the direct `kimi -p` path. The web processes are n
|
|
|
237
239
|
`/kimi:swarm` (v1.2) fans a **read-only** review out across subagents using kimi-code's `AgentSwarm` tool (kimi-code 0.12.0+). The coordinator launches one subagent per target (file/module/question); each inspects the workspace with read tools and reports findings, which the coordinator consolidates into one markdown report. It opens **zero new write surface**.
|
|
238
240
|
|
|
239
241
|
- **The hook label is `swarm`, and it allows the read-only tool set PLUS `AgentSwarm`.** The coordinator must be allowed to call `AgentSwarm` (else the swarm never launches), but no write/edit/shell tool is allowed. The exact allowed tool name is `AgentSwarm` (verified at 0.42.0 against `packages/agent-core-v2/src/features/swarm/tools/agent-swarm/agentSwarmTool.ts`: `'AgentSwarm'`, strict schema). The singular `Agent` tool is deliberately *not* allowed — swarm is the fan-out surface, not arbitrary delegation.
|
|
240
|
-
- **Every subagent inherits the `swarm` label and fires the same hook.** On native v2 (0.42.0) `AgentSwarm` children run in-process in a child DI scope with their OWN eager external-hooks service, so every child tool call passes the same before-execute channel and the same managed hook — there is no sub-vs-main branch and no deny-all shortcut on the swarm route. So a subagent's `Write`/`Edit`/`Bash` is denied exactly like a single-turn review's. The swarm listener in that channel is non-terminal and nothing auto-approves `AgentSwarm` ahead of the hook. (Legacy-v1 ≤ 0.41 reached the same result by placing the hook policy at index 0 of every agent's permission stack.) **Required for final certification evidence** (`tests/runtime/real-binary-smoke.test.ts`, run via `bun run smoke:real`): distinct child journals must exist, and each assigned Write/Edit `path` must be correlated through its `toolCallId` with the managed denial in that exact tool result; unrelated denials and coordinator-only reports are insufficient. On 2.3.2, 2.3.3, 2.4.0 and 2.
|
|
242
|
+
- **Every subagent inherits the `swarm` label and fires the same hook.** On native v2 (0.42.0) `AgentSwarm` children run in-process in a child DI scope with their OWN eager external-hooks service, so every child tool call passes the same before-execute channel and the same managed hook — there is no sub-vs-main branch and no deny-all shortcut on the swarm route. So a subagent's `Write`/`Edit`/`Bash` is denied exactly like a single-turn review's. The swarm listener in that channel is non-terminal and nothing auto-approves `AgentSwarm` ahead of the hook. (Legacy-v1 ≤ 0.41 reached the same result by placing the hook policy at index 0 of every agent's permission stack.) **Required for final certification evidence** (`tests/runtime/real-binary-smoke.test.ts`, run via `bun run smoke:real`): distinct child journals must exist, and each assigned Write/Edit `path` must be correlated through its `toolCallId` with the managed denial in that exact tool result; unrelated denials and coordinator-only reports are insufficient. On 2.3.2, 2.3.3, 2.4.0, 2.4.1, 2.5.0 and 2.5.1, targeted controls also prove that direct OFF produces no children while the preactivated ON path produces the real child journals and denials.
|
|
241
243
|
- **The managed hook is the read-only boundary; no `explore`-profile second layer is assumed.** Production deliberately omits `subagent_type` for read-only swarm rather than selecting kimi-code's `explore` profile. Every coordinator and subagent tool call is enforced by the same managed hook.
|
|
242
244
|
- **Swarm refuses without the hook, like every model-spawning command.** A swarm makes the reason especially acute: it asks for up to `--cap` subagents, but this total-count hint is advisory, so a missing hook would remove enforcement from all of them at once. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` remains an explicit tests/diagnostics escape hatch, not a recovery path.
|
|
243
245
|
- **The only new risk is COST, and the plugin bounds it.** Read-only swarm writes nothing, so the new risk is runaway cost from N parallel model runs (and possible nested `AgentSwarm`). `/kimi:swarm` enforces a **mandatory finite wall-clock ceiling** via its AbortController (`--budget`, default 30m); the SIGTERM→SIGKILL descendant-reaping cancellation reaps the whole subagent process tree. Two **distinct** subagent bounds: `--cap N` is a *soft* prompt-injected hint on TOTAL subagent count (the hook is stateless and cannot enforce a count, so the model may exceed it), and `--max-concurrency N` is a *hard* ceiling on how many subagents kimi-code's `SubagentBatch` runs concurrently — exported as `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY` (PR #888) on **kimi-code 0.18.0+** (older binaries ignore the unknown env var, so on < 0.18 only the soft hint applies). It **defaults to 4** when the user passes none (`resolveSwarmMaxConcurrency` in swarm.ts), so every swarm run — agent-dispatched or human-typed — carries an enforced peak-parallelism ceiling on 0.18.0+ rather than falling back to kimi-code's internal ramp; pass an explicit value to widen or throttle. **Never merge them into one flag:** concurrency ≠ total count — a hard concurrency cap of N still permits more than N subagents over the life of the run, just never more than N at once. The `--budget` bounds elapsed time, not a dollar amount or total token count; `--max-concurrency` bounds *peak* parallelism (the spike in simultaneous model spend), and `--cap` is only an advisory total-count nudge. Folding both onto one flag would create the "cap-as-total-count illusion" — a user reading `--cap N` would expect a total bound but receive only a peak-parallelism one.
|
|
@@ -296,6 +298,8 @@ worktree. A directory the operator trusted interactively in the Kimi TUI still
|
|
|
296
298
|
has that property; that persisted-trust route is a documented residual, not
|
|
297
299
|
something the plugin can close.
|
|
298
300
|
|
|
301
|
+
Plugin-managed execution must remain local. Unused remote declarations can remain in operator config, but a configured remote default refuses before spawn. Disabling workspace trust through `[workspace] require_trust = false` also refuses, because it would enable project MCP outside the managed hook. Launcher environment selectors are reserved. Fresh exact 2.5.0/2.5.1 print selects `--environment local`; resume instead inspects persisted metadata and every agent journal and refuses nonlocal or wrong-root records against the expected execution root (the isolated worktree for swarm-write); older sessions without environment records do not fail this check merely for lacking them.
|
|
302
|
+
|
|
299
303
|
## Running a newer Kimi than the plugin certifies
|
|
300
304
|
|
|
301
305
|
The plugin refuses any kimi-code version it has not certified, so an operator
|
|
@@ -306,9 +310,9 @@ at it (the runtime resolves that path instead of `kimi` on `PATH`; every spawn
|
|
|
306
310
|
still exports `KIMI_CODE_NO_AUTO_UPDATE=1`, so the pinned copy never drifts).
|
|
307
311
|
|
|
308
312
|
```sh
|
|
309
|
-
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.
|
|
313
|
+
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.5.1
|
|
310
314
|
mkdir -p "$PIN" && cd "$PIN" && printf '{"name":"kimi-plugin-cc-pin","private":true}\n' > package.json
|
|
311
|
-
npm install @moonshot-ai/kimi-code@2.
|
|
315
|
+
npm install @moonshot-ai/kimi-code@2.5.1
|
|
312
316
|
export KIMI_PLUGIN_CC_KIMI_BIN="$PIN/node_modules/.bin/kimi" # e.g. in ~/.zshenv
|
|
313
317
|
```
|
|
314
318
|
|