kimi-plugin-cc 2.5.2 → 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 +6 -0
- package/dist/cli-client.js +8 -0
- package/dist/commands/swarm.js +2 -2
- package/dist/hooks/config-safety.js +5 -2
- 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 +4 -4
- package/docs/registry-releases.md +3 -1
- package/docs/safety.md +11 -9
- 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 +3 -1
- 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/swarm.js +2 -2
- package/plugins/kimi-codex/dist/hooks/config-safety.js +5 -2
- 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/dist/cli-client.js +8 -0
- package/plugins/kimi-dsh/dist/commands/swarm.js +2 -2
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +5 -2
- 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 +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
3
|
"description": "Kimi for Claude Code: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tasks through the local Kimi Code CLI, with PreToolUse safety hooks.",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.3",
|
|
5
5
|
"homepage": "https://github.com/linxule/kimi-plugin-cc",
|
|
6
6
|
"repository": "https://github.com/linxule/kimi-plugin-cc",
|
|
7
7
|
"license": "Apache-2.0",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
> **Post-1.0 release history (v1.0.1 -> present) lives in [ROADMAP-TO-GA.md § Post-GA audit log](./ROADMAP-TO-GA.md#post-ga-audit-log)** and the "Version" / "Upstream compat" lines of [AGENTS.md](./AGENTS.md). Docs-only kimi-code compat checkups that don't bump the plugin version (e.g. the 0.14.2 / 0.14.3 patches) are recorded there, not here. Notable releases are summarized below; the GA entry and full pre-GA detail follow.
|
|
4
4
|
|
|
5
|
+
## 2.5.3 — 2026-11-06
|
|
6
|
+
|
|
7
|
+
- Certify exact kimi-code 2.5.0 and 2.5.1 for all eight native-v2 operations. Preserve the no-plan construction, local execution and workspace-trust enforcement, authenticated WebSocket swarm state, bounded lifecycle cleanup, and the legacy-v1 0.41.x boundary. See the [2.5.0](docs/upstream-2.5.0-certification.md) and [2.5.1](docs/upstream-2.5.1-certification.md) evidence.
|
|
8
|
+
- Review every exact-version branch for fresh `--environment local`, hook schemas, preactivation, local environment/cwd binding, and protocol-v2 agent state. Require a clean WebSocket close before accepting final agent state, preserving timeout and abnormal-close refusals across Bun platforms. 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, including real rescue, Bash.cwd confinement, resume/taint refusal, goal-turn denials, and child-authored patches.
|
|
9
|
+
- Pass the full repository gate with 1,412 tests and 18,282 assertions, 330-file package checks across all three distributions, isolated consumer security, and independent read-only `claude -p` review with no must-fix findings. Update the manual smoke default to exact 2.5.1 and the current compatibility documentation; publication does not update installed hosts or operator pins.
|
|
10
|
+
|
|
5
11
|
## 2.5.2 — 2026-11-02
|
|
6
12
|
|
|
7
13
|
- Preserve TOML multiline values, line endings, and foreign hook fields during setup; repair mixed-host orphan marker comments without altering foreign hooks.
|
package/dist/cli-client.js
CHANGED
|
@@ -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
|
}
|
package/dist/commands/swarm.js
CHANGED
|
@@ -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>";
|
|
@@ -595,7 +595,8 @@ function validateVersionSensitiveEvents(events, version) {
|
|
|
595
595
|
// Exact schemas reviewed at 2.0.0 (1b89e4b0), 2.0.1 (caf7d4e2),
|
|
596
596
|
// 2.0.2 (9d07f634), 2.1.0 (52437299), 2.1.1 (f67e6398), 2.2.0 (472192c3),
|
|
597
597
|
// 2.3.0 (b5e28fd6), 2.3.1 (684bae9e), 2.3.2 (b8dd3a13), 2.3.3 (ec495f0c),
|
|
598
|
-
// 2.4.0 (f26e4d91),
|
|
598
|
+
// 2.4.0 (f26e4d91), 2.4.1 (2c73f3f5), 2.5.0 (41c34143), and 2.5.1 (1b02030d):
|
|
599
|
+
// the additive events and HookDefSchema are unchanged.
|
|
599
600
|
// Require the full version string AND matching components; no inferred 2.x compatibility.
|
|
600
601
|
if (version.major === 2 && ((version.minor === 0 && ((version.version === "2.0.0" && version.patch === 0) ||
|
|
601
602
|
(version.version === "2.0.1" && version.patch === 1) ||
|
|
@@ -608,7 +609,9 @@ function validateVersionSensitiveEvents(events, version) {
|
|
|
608
609
|
(version.version === "2.3.2" && version.patch === 2) ||
|
|
609
610
|
(version.version === "2.3.3" && version.patch === 3))) ||
|
|
610
611
|
(version.minor === 4 && ((version.version === "2.4.0" && version.patch === 0) ||
|
|
611
|
-
(version.version === "2.4.1" && version.patch === 1)))
|
|
612
|
+
(version.version === "2.4.1" && version.patch === 1))) ||
|
|
613
|
+
(version.minor === 5 && ((version.version === "2.5.0" && version.patch === 0) ||
|
|
614
|
+
(version.version === "2.5.1" && version.patch === 1))))) {
|
|
612
615
|
return { valid: true };
|
|
613
616
|
}
|
|
614
617
|
if (version.major !== 0) {
|
package/dist/kimi-command.js
CHANGED
|
@@ -47,6 +47,7 @@ const RESERVED_PREFIX_FLAGS = new Set([
|
|
|
47
47
|
"--plan", "--auto", "-y", "--yolo", "--yes", "--auto-approve", "--manual",
|
|
48
48
|
// Model / profile / roots.
|
|
49
49
|
"-m", "--model", "--skills-dir", "--agent", "--agent-file", "--add-dir",
|
|
50
|
+
"--environment", "--environment-cwd",
|
|
50
51
|
// Terminal informational flags (would replace the run with a banner).
|
|
51
52
|
"-V", "--version", "-h", "--help",
|
|
52
53
|
]);
|
package/dist/kimi-engine.js
CHANGED
|
@@ -45,6 +45,8 @@ export const NATIVE_V2_CERTIFIED_VERSIONS = Object.freeze([
|
|
|
45
45
|
"2.3.3",
|
|
46
46
|
"2.4.0",
|
|
47
47
|
"2.4.1",
|
|
48
|
+
"2.5.0",
|
|
49
|
+
"2.5.1",
|
|
48
50
|
]);
|
|
49
51
|
/** Per-operation native-v2 certification. One green operation never certifies another. */
|
|
50
52
|
export const NATIVE_V2_CERTIFIED = new Map([
|
|
@@ -127,6 +129,7 @@ export async function prepareKimiExecutionPlan(options) {
|
|
|
127
129
|
kimiHome: resolveKimiHome(options.env, options.cwd),
|
|
128
130
|
env: options.env,
|
|
129
131
|
resumeSessionId: options.resumeSessionId,
|
|
132
|
+
expectedExecutionRoot: options.cwd,
|
|
130
133
|
stage: "kimi-engine.plan",
|
|
131
134
|
});
|
|
132
135
|
};
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
// B. the EnterPlanMode tool (already hook-denied for every label);
|
|
14
14
|
// C. resuming a session whose agent wire journal holds a durable
|
|
15
15
|
// `plan_mode.enter` record (restore() folds ONLY agents/*/wire.jsonl).
|
|
16
|
-
// This module closes A and C before any process is spawned,
|
|
16
|
+
// This module closes A and C before any process is spawned, refuses the
|
|
17
17
|
// v2-only experimental features (tower, subagent fork) that this plugin does
|
|
18
|
-
// not certify
|
|
18
|
+
// not certify, and requires local execution for configured and resumed sessions.
|
|
19
|
+
// B stays in runtime/hooks/approval-policy.ts.
|
|
19
20
|
//
|
|
20
21
|
// Contract:
|
|
21
22
|
// - Pure inspection: never writes, never rewrites operator config, never
|
|
@@ -155,6 +156,48 @@ export async function inspectPlanModeConfig(kimiHome) {
|
|
|
155
156
|
reason: `\`${offending.join("`, `")}\` (upstream setting \`default_plan_mode\`) is set in ${configPath} and is not \`false\`.`,
|
|
156
157
|
};
|
|
157
158
|
}
|
|
159
|
+
export async function inspectEnvironmentConfig(kimiHome) {
|
|
160
|
+
const parsed = await readParsedConfig(kimiHome);
|
|
161
|
+
if (parsed.kind === "unreadable") {
|
|
162
|
+
return { kind: "refuse", refusalKind: "v2-config-unreadable", reason: parsed.reason };
|
|
163
|
+
}
|
|
164
|
+
const section = parsed.config?.["environments"];
|
|
165
|
+
if (section !== undefined) {
|
|
166
|
+
if (section === null || typeof section !== "object" || Array.isArray(section)) {
|
|
167
|
+
return {
|
|
168
|
+
kind: "refuse",
|
|
169
|
+
refusalKind: "v2-environment-configured",
|
|
170
|
+
reason: "The [environments] section is not a table, so the execution environment cannot be verified as local.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (section["default"] !== undefined) {
|
|
174
|
+
return {
|
|
175
|
+
kind: "refuse",
|
|
176
|
+
refusalKind: "v2-environment-configured",
|
|
177
|
+
reason: "A default remote execution environment is configured in [environments]; plugin-managed jobs require the local environment.",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const workspace = parsed.config?.["workspace"];
|
|
182
|
+
if (workspace !== undefined) {
|
|
183
|
+
if (workspace === null || typeof workspace !== "object" || Array.isArray(workspace)) {
|
|
184
|
+
return {
|
|
185
|
+
kind: "refuse",
|
|
186
|
+
refusalKind: "v2-workspace-trust-disabled",
|
|
187
|
+
reason: "The [workspace] section is not a table, so project trust requirements cannot be verified.",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const trustDisabled = Object.entries(workspace).some(([key, value]) => upstreamSnakeToCamel(key) === "requireTrust" && value !== true);
|
|
191
|
+
if (trustDisabled) {
|
|
192
|
+
return {
|
|
193
|
+
kind: "refuse",
|
|
194
|
+
refusalKind: "v2-workspace-trust-disabled",
|
|
195
|
+
reason: "Workspace trust is disabled in [workspace]; plugin-managed jobs require project trust enforcement to remain enabled.",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { kind: "ok" };
|
|
200
|
+
}
|
|
158
201
|
/**
|
|
159
202
|
* Refuse v2-only experimental features this plugin does not certify.
|
|
160
203
|
* Sources (per-flag precedence upstream is env → [experimental] config →
|
|
@@ -239,40 +282,39 @@ async function openJournalSafely(journalPath, maxBytes) {
|
|
|
239
282
|
}
|
|
240
283
|
return { kind: "ok", handle };
|
|
241
284
|
}
|
|
242
|
-
|
|
243
|
-
* Scan one open journal's lines for a plan-mode control record. Returns the
|
|
244
|
-
* record `type` string on a match, "<unparseable>" for a suspicious line
|
|
245
|
-
* that fails to parse, or null when the journal is clean.
|
|
246
|
-
*/
|
|
247
|
-
async function scanJournalLines(handle) {
|
|
285
|
+
async function scanJournalLines(handle, expectedExecutionRoot) {
|
|
248
286
|
const rl = createInterface({ input: handle.createReadStream() });
|
|
249
287
|
try {
|
|
250
288
|
for await (const rawLine of rl) {
|
|
251
289
|
const line = rawLine.trim();
|
|
252
290
|
if (line.length === 0)
|
|
253
291
|
continue;
|
|
254
|
-
// Parse EVERY record and check its DECODED `type`. A raw-substring filter
|
|
255
|
-
// is unsafe on its own: a JSON-escaped type such as
|
|
256
|
-
// {"type":"plan_mode.enter"} contains no literal "plan_mode." yet
|
|
257
|
-
// upstream JSON.parse decodes it to "plan_mode.enter" and restores active
|
|
258
|
-
// plan mode. So parse first (this catches the escape). If a line FAILS to
|
|
259
|
-
// parse, upstream cannot fold it into a plan event either — but be
|
|
260
|
-
// conservative: a malformed line that still LOOKS plan-related is treated
|
|
261
|
-
// as tainted rather than skipped, so a hand-corrupted journal cannot hide
|
|
262
|
-
// a plan record behind a parse error.
|
|
263
292
|
let parsed;
|
|
264
293
|
try {
|
|
265
294
|
parsed = JSON.parse(line);
|
|
266
295
|
}
|
|
267
296
|
catch {
|
|
268
297
|
if (line.includes('"plan_mode.') || line.includes('"plan.')) {
|
|
269
|
-
return "<unparseable>";
|
|
298
|
+
return { kind: "tainted", recordType: "<unparseable>" };
|
|
299
|
+
}
|
|
300
|
+
if (line.includes('"environment.set_binding')) {
|
|
301
|
+
return { kind: "environment", recordType: "<unparseable-environment-binding>" };
|
|
270
302
|
}
|
|
271
303
|
continue;
|
|
272
304
|
}
|
|
273
|
-
const
|
|
305
|
+
const record = parsed;
|
|
306
|
+
const type = record?.["type"];
|
|
274
307
|
if (typeof type === "string" && (type.startsWith("plan_mode.") || type.startsWith("plan."))) {
|
|
275
|
-
return type;
|
|
308
|
+
return { kind: "tainted", recordType: type };
|
|
309
|
+
}
|
|
310
|
+
if (type === "environment.set_binding") {
|
|
311
|
+
if (record?.["environmentId"] !== "local") {
|
|
312
|
+
return { kind: "environment", recordType: type };
|
|
313
|
+
}
|
|
314
|
+
const cwd = record?.["cwd"];
|
|
315
|
+
if (expectedExecutionRoot !== undefined && cwd !== undefined && cwd !== expectedExecutionRoot) {
|
|
316
|
+
return { kind: "environment", recordType: "environment.set_binding.cwd" };
|
|
317
|
+
}
|
|
276
318
|
}
|
|
277
319
|
}
|
|
278
320
|
return null;
|
|
@@ -281,16 +323,48 @@ async function scanJournalLines(handle) {
|
|
|
281
323
|
rl.close();
|
|
282
324
|
}
|
|
283
325
|
}
|
|
326
|
+
async function inspectSessionMetadataEnvironment(statePath, expectedExecutionRoot) {
|
|
327
|
+
const read = await readSmallFileSafe(statePath, CONFIG_MAX_BYTES);
|
|
328
|
+
if (read.kind === "absent")
|
|
329
|
+
return null;
|
|
330
|
+
if (read.kind === "unreadable") {
|
|
331
|
+
return { kind: "unavailable", reason: `${statePath}: ${read.reason}` };
|
|
332
|
+
}
|
|
333
|
+
let metadata;
|
|
334
|
+
try {
|
|
335
|
+
metadata = JSON.parse(read.contents);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return { kind: "unavailable", reason: `${statePath}: malformed session metadata` };
|
|
339
|
+
}
|
|
340
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
341
|
+
return { kind: "unavailable", reason: `${statePath}: invalid session metadata` };
|
|
342
|
+
}
|
|
343
|
+
const record = metadata;
|
|
344
|
+
const environmentId = record["environmentId"];
|
|
345
|
+
if (environmentId !== undefined && environmentId !== "local") {
|
|
346
|
+
return { kind: "environment", journal: statePath, recordType: "session.metadata.environmentId" };
|
|
347
|
+
}
|
|
348
|
+
const environmentCwd = record["environmentCwd"];
|
|
349
|
+
if ((environmentId === undefined || environmentId === "local") &&
|
|
350
|
+
expectedExecutionRoot !== undefined &&
|
|
351
|
+
environmentCwd !== undefined &&
|
|
352
|
+
environmentCwd !== expectedExecutionRoot) {
|
|
353
|
+
return { kind: "environment", journal: statePath, recordType: "session.metadata.environmentCwd" };
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
284
357
|
/**
|
|
285
|
-
* Scan every agent wire journal
|
|
286
|
-
* Layout at 0.42.0:
|
|
358
|
+
* Scan saved session metadata and every agent wire journal for plan-mode records
|
|
359
|
+
* and nonlocal environment bindings. Layout at 0.42.0:
|
|
360
|
+
* `<kimiHome>/sessions/<workspaceId>/<sessionId>/agents/<agentId>/wire.jsonl`
|
|
287
361
|
* (workspaceId is unknown to the plugin → enumerate `sessions/*` with bounded
|
|
288
362
|
* readdir). Raw-line scan of EVERY agent journal: a JSON line whose `type`
|
|
289
|
-
* starts with `plan_mode.` or `plan.` → tainted
|
|
290
|
-
*
|
|
363
|
+
* starts with `plan_mode.` or `plan.` → tainted; `environment.set_binding` must
|
|
364
|
+
* name `environmentId:"local"`. No session dir, no journal, unreadable metadata,
|
|
291
365
|
* or any single journal above the size cap → unavailable. Never modifies files.
|
|
292
366
|
*/
|
|
293
|
-
export async function scanSessionJournalsForPlan(kimiHome, sessionId) {
|
|
367
|
+
export async function scanSessionJournalsForPlan(kimiHome, sessionId, expectedExecutionRoot) {
|
|
294
368
|
const sessionsDir = path.join(kimiHome, "sessions");
|
|
295
369
|
const workspaceEntries = await boundedReaddir(sessionsDir, DIR_ENUM_CAP);
|
|
296
370
|
if (workspaceEntries.kind === "absent") {
|
|
@@ -316,7 +390,11 @@ export async function scanSessionJournalsForPlan(kimiHome, sessionId) {
|
|
|
316
390
|
}
|
|
317
391
|
if (!workspaceEntry.isDirectory())
|
|
318
392
|
continue;
|
|
319
|
-
const
|
|
393
|
+
const sessionDir = path.join(sessionsDir, workspaceEntry.name, sessionId);
|
|
394
|
+
const metadataFinding = await inspectSessionMetadataEnvironment(path.join(sessionDir, "state.json"), expectedExecutionRoot);
|
|
395
|
+
if (metadataFinding !== null)
|
|
396
|
+
return metadataFinding;
|
|
397
|
+
const agentsDir = path.join(sessionDir, "agents");
|
|
320
398
|
const agentEntries = await boundedReaddir(agentsDir, DIR_ENUM_CAP);
|
|
321
399
|
if (agentEntries.kind === "absent")
|
|
322
400
|
continue;
|
|
@@ -347,9 +425,12 @@ export async function scanSessionJournalsForPlan(kimiHome, sessionId) {
|
|
|
347
425
|
return { kind: "unavailable", reason: `${journalPath}: ${opened.reason}` };
|
|
348
426
|
}
|
|
349
427
|
try {
|
|
350
|
-
const
|
|
351
|
-
if (
|
|
352
|
-
return { kind: "tainted", journal: journalPath, recordType:
|
|
428
|
+
const finding = await scanJournalLines(opened.handle, expectedExecutionRoot);
|
|
429
|
+
if (finding?.kind === "tainted") {
|
|
430
|
+
return { kind: "tainted", journal: journalPath, recordType: finding.recordType };
|
|
431
|
+
}
|
|
432
|
+
if (finding?.kind === "environment") {
|
|
433
|
+
return { kind: "environment", journal: journalPath, recordType: finding.recordType };
|
|
353
434
|
}
|
|
354
435
|
}
|
|
355
436
|
finally {
|
|
@@ -363,6 +444,8 @@ export async function scanSessionJournalsForPlan(kimiHome, sessionId) {
|
|
|
363
444
|
* CLI_V2_PLAN_MODE_CONFIGURED details.refusal_kind = "v2-plan-mode-configured"
|
|
364
445
|
* CLI_V2_CONFIG_UNREADABLE details.refusal_kind = "v2-config-unreadable"
|
|
365
446
|
* CLI_V2_EXPERIMENTAL_UNSAFE details.refusal_kind = "v2-experimental-unsafe", details.selectors
|
|
447
|
+
* CLI_V2_ENVIRONMENT_UNSAFE / CLI_V2_WORKSPACE_TRUST_UNSAFE for unsafe config
|
|
448
|
+
* KIMI_SESSION_ENVIRONMENT_UNSAFE details.refusal_kind = "session-environment-unsafe"
|
|
366
449
|
* KIMI_SESSION_PLAN_TAINTED details.refusal_kind = "session-plan-tainted", details.record_type
|
|
367
450
|
* KIMI_SESSION_JOURNAL_UNAVAILABLE details.refusal_kind = "session-journal-unavailable"
|
|
368
451
|
* Every message carries a truthful remedy (edit the named setting / start a
|
|
@@ -384,6 +467,22 @@ export async function assertNativeV2Preflight(options) {
|
|
|
384
467
|
details: { refusal_kind: planMode.refusalKind, retryable_after_setup: false },
|
|
385
468
|
});
|
|
386
469
|
}
|
|
470
|
+
const environment = await inspectEnvironmentConfig(options.kimiHome);
|
|
471
|
+
if (environment.kind === "refuse") {
|
|
472
|
+
const code = environment.refusalKind === "v2-environment-configured"
|
|
473
|
+
? "CLI_V2_ENVIRONMENT_UNSAFE"
|
|
474
|
+
: environment.refusalKind === "v2-workspace-trust-disabled"
|
|
475
|
+
? "CLI_V2_WORKSPACE_TRUST_UNSAFE"
|
|
476
|
+
: "CLI_V2_CONFIG_UNREADABLE";
|
|
477
|
+
const remedy = environment.refusalKind === "v2-environment-configured"
|
|
478
|
+
? `Remove \`environments.default\` in ${configPath} so plugin-managed sessions stay local.`
|
|
479
|
+
: environment.refusalKind === "v2-workspace-trust-disabled"
|
|
480
|
+
? `Set \`workspace.require_trust = true\` (or remove it) in ${configPath}.`
|
|
481
|
+
: `Fix or remove the unreadable file at ${configPath} so the execution environment can be verified as local.`;
|
|
482
|
+
throw new RuntimeError(code, `${environment.reason} ${remedy}`, stage, {
|
|
483
|
+
details: { refusal_kind: environment.refusalKind, retryable_after_setup: false },
|
|
484
|
+
});
|
|
485
|
+
}
|
|
387
486
|
const experimental = await inspectExperimentalSelectors(options.kimiHome, options.env);
|
|
388
487
|
if (experimental.kind === "refuse") {
|
|
389
488
|
throw new RuntimeError("CLI_V2_EXPERIMENTAL_UNSAFE", `${experimental.reason} Unset the listed selector(s) before retrying.`, stage, {
|
|
@@ -395,7 +494,16 @@ export async function assertNativeV2Preflight(options) {
|
|
|
395
494
|
});
|
|
396
495
|
}
|
|
397
496
|
if (options.resumeSessionId !== undefined) {
|
|
398
|
-
const journalScan = await scanSessionJournalsForPlan(options.kimiHome, options.resumeSessionId);
|
|
497
|
+
const journalScan = await scanSessionJournalsForPlan(options.kimiHome, options.resumeSessionId, options.expectedExecutionRoot);
|
|
498
|
+
if (journalScan.kind === "environment") {
|
|
499
|
+
throw new RuntimeError("KIMI_SESSION_ENVIRONMENT_UNSAFE", `Saved session ${options.resumeSessionId} has an unsafe execution environment binding (${journalScan.recordType} in ${journalScan.journal}). Start a fresh local session; saved session state was not modified.`, stage, {
|
|
500
|
+
details: {
|
|
501
|
+
refusal_kind: "session-environment-unsafe",
|
|
502
|
+
record_type: journalScan.recordType,
|
|
503
|
+
retryable_after_setup: false,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
}
|
|
399
507
|
if (journalScan.kind === "tainted") {
|
|
400
508
|
throw new RuntimeError("KIMI_SESSION_PLAN_TAINTED", `Saved session ${options.resumeSessionId} previously entered plan mode (record type "${journalScan.recordType}" in ${journalScan.journal}). Start a fresh session; saved session state was not modified.`, stage, {
|
|
401
509
|
details: {
|