kimi-plugin-cc 2.4.5 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +42 -26
- package/dist/dsh/companion.js +174 -0
- package/dist/dsh/index.js +156 -0
- package/dist/hooks/install-paths.js +2 -2
- package/dist/version.js +1 -1
- package/docs/models.md +5 -1
- package/docs/registry-releases.md +20 -17
- package/docs/safety.md +22 -9
- package/docs/upstream-2.4.1-certification.md +6 -0
- package/package.json +40 -6
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
- package/plugins/kimi-codex/dist/dsh/index.js +156 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/LICENSE +201 -0
- package/plugins/kimi-dsh/README.md +80 -0
- package/plugins/kimi-dsh/cordis.patch.yml +45 -0
- package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
- package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
- package/plugins/kimi-dsh/dist/cli-client.js +876 -0
- package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
- package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
- package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
- package/plugins/kimi-dsh/dist/commands/models.js +140 -0
- package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
- package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
- package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
- package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
- package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
- package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
- package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
- package/plugins/kimi-dsh/dist/commands/result.js +66 -0
- package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
- package/plugins/kimi-dsh/dist/commands/review.js +220 -0
- package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
- package/plugins/kimi-dsh/dist/commands/status.js +27 -0
- package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
- package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
- package/plugins/kimi-dsh/dist/companion.js +97 -0
- package/plugins/kimi-dsh/dist/config.js +22 -0
- package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
- package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
- package/plugins/kimi-dsh/dist/errors.js +40 -0
- package/plugins/kimi-dsh/dist/git.js +224 -0
- package/plugins/kimi-dsh/dist/help.js +43 -0
- package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
- package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
- package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
- package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
- package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
- package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
- package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
- package/plugins/kimi-dsh/dist/job-id.js +12 -0
- package/plugins/kimi-dsh/dist/job-store.js +659 -0
- package/plugins/kimi-dsh/dist/jobs.js +217 -0
- package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
- package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
- package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
- package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
- package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
- package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
- package/plugins/kimi-dsh/dist/logging.js +22 -0
- package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
- package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
- package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
- package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-dsh/dist/model-command.js +32 -0
- package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
- package/plugins/kimi-dsh/dist/package.json +1 -0
- package/plugins/kimi-dsh/dist/parsing.js +620 -0
- package/plugins/kimi-dsh/dist/paths.js +27 -0
- package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
- package/plugins/kimi-dsh/dist/process-tree.js +479 -0
- package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
- package/plugins/kimi-dsh/dist/render.js +177 -0
- package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
- package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
- package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
- package/plugins/kimi-dsh/dist/session-title.js +293 -0
- package/plugins/kimi-dsh/dist/stream-json.js +346 -0
- package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
- package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
- package/plugins/kimi-dsh/dist/types.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
- package/plugins/kimi-dsh/dist/version.js +7 -0
- package/plugins/kimi-dsh/package.json +48 -0
- package/plugins/kimi-dsh/scripts/companion.sh +37 -0
- package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { access, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { RuntimeError } from "./errors.js";
|
|
5
|
+
import { resolveKimiCliCommand } from "./kimi-command.js";
|
|
6
|
+
import { resolveKimiHome } from "./kimi-home.js";
|
|
7
|
+
import { assertNativeV2Preflight } from "./native-v2-preflight.js";
|
|
8
|
+
import { isInTestedRange, parseVersionLine, probeKimiVersion, } from "./kimi-version-probe.js";
|
|
9
|
+
export const NATIVE_V2_SAFETY_PROFILE = "native-v2-no-plan/1";
|
|
10
|
+
/**
|
|
11
|
+
* Minimum legacy-v1 version that actually contains each operation. The upper
|
|
12
|
+
* boundary is KIMI_TESTED_MINORS, enforced by probe.inTestedRange.
|
|
13
|
+
*/
|
|
14
|
+
const LEGACY_V1_MINIMUMS = {
|
|
15
|
+
review: { major: 0, minor: 1 },
|
|
16
|
+
challenge: { major: 0, minor: 1 },
|
|
17
|
+
ask: { major: 0, minor: 1 },
|
|
18
|
+
rescue: { major: 0, minor: 1 },
|
|
19
|
+
review_gate: { major: 0, minor: 1 },
|
|
20
|
+
pursue: { major: 0, minor: 8 },
|
|
21
|
+
swarm: { major: 0, minor: 12 },
|
|
22
|
+
"swarm-write": { major: 0, minor: 18 },
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Exact kimi-code releases certified for native v2 under
|
|
26
|
+
* NATIVE_V2_SAFETY_PROFILE. Exact, not minor-ranged: upstream ships
|
|
27
|
+
* behavioural changes in patch releases, and the no-plan construction must be
|
|
28
|
+
* re-proven (mechanized tag scan + real-binary smoke) per tag before a version
|
|
29
|
+
* is appended. Each entry has its own row in tests/audit/v2-tag-scan.test.ts
|
|
30
|
+
* and a dated entry in ROADMAP-TO-GA.md § Post-GA audit log.
|
|
31
|
+
*/
|
|
32
|
+
export const NATIVE_V2_CERTIFIED_VERSIONS = Object.freeze([
|
|
33
|
+
"0.42.0",
|
|
34
|
+
"0.43.0",
|
|
35
|
+
"0.43.1",
|
|
36
|
+
"2.0.0",
|
|
37
|
+
"2.0.1",
|
|
38
|
+
"2.0.2",
|
|
39
|
+
"2.1.0",
|
|
40
|
+
"2.1.1",
|
|
41
|
+
"2.2.0",
|
|
42
|
+
"2.3.0",
|
|
43
|
+
"2.3.1",
|
|
44
|
+
"2.3.2",
|
|
45
|
+
"2.3.3",
|
|
46
|
+
"2.4.0",
|
|
47
|
+
"2.4.1",
|
|
48
|
+
]);
|
|
49
|
+
/** Per-operation native-v2 certification. One green operation never certifies another. */
|
|
50
|
+
export const NATIVE_V2_CERTIFIED = new Map([
|
|
51
|
+
["review", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
52
|
+
["challenge", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
53
|
+
["ask", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
54
|
+
["rescue", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
55
|
+
["review_gate", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
56
|
+
["pursue", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
57
|
+
["swarm", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
58
|
+
["swarm-write", NATIVE_V2_CERTIFIED_VERSIONS],
|
|
59
|
+
]);
|
|
60
|
+
export function isNativeV2Certified(operationKind, version) {
|
|
61
|
+
const normalized = parseVersionLine(version)?.raw;
|
|
62
|
+
if (normalized === undefined)
|
|
63
|
+
return false;
|
|
64
|
+
return NATIVE_V2_CERTIFIED.get(operationKind)?.includes(normalized) ?? false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* True when this exact version is certified for native v2 for ANY operation.
|
|
68
|
+
* Used by setup to phrase its version notice: a v2-certified version is NOT an
|
|
69
|
+
* "out of tested range, will refuse" case even though it is outside the legacy
|
|
70
|
+
* `KIMI_TESTED_MINORS` table (that table intentionally never gains 0.42).
|
|
71
|
+
*/
|
|
72
|
+
export function isNativeV2CertifiedVersion(version) {
|
|
73
|
+
const normalized = parseVersionLine(version)?.raw;
|
|
74
|
+
if (normalized === undefined)
|
|
75
|
+
return false;
|
|
76
|
+
return NATIVE_V2_CERTIFIED_VERSIONS.includes(normalized);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Engine selection for a probed binary: native v2 when the exact version is
|
|
80
|
+
* certified for this operation, else legacy-v1 (whose own tested-range check
|
|
81
|
+
* still applies). kimi-code 0.42.0 removed the v1 engine, so a v1 plan on a
|
|
82
|
+
* ≥0.42 binary is refused by the legacy range rather than silently run on v2.
|
|
83
|
+
*/
|
|
84
|
+
export function selectIntendedEngine(operationKind, probe) {
|
|
85
|
+
return isNativeV2Certified(operationKind, probe.version) ? "native-v2" : "legacy-v1";
|
|
86
|
+
}
|
|
87
|
+
const UNSAFE_EXPERIMENTAL_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
88
|
+
/**
|
|
89
|
+
* Refuse the ambient experimental-feature selector before any model or version
|
|
90
|
+
* subprocess. cli-client repeats this at the final spawn boundary in case the
|
|
91
|
+
* environment changes after plan creation.
|
|
92
|
+
*/
|
|
93
|
+
export function assertNoUnsafeExperimentalSelector(env, stage = "kimi-engine.plan") {
|
|
94
|
+
const enabled = UNSAFE_EXPERIMENTAL_VALUES.has((env.KIMI_CODE_EXPERIMENTAL_FLAG ?? "").trim().toLowerCase());
|
|
95
|
+
if (!enabled)
|
|
96
|
+
return;
|
|
97
|
+
throw new RuntimeError("CLI_V2_HOOK_ORDER_UNSAFE", "Refusing kimi-code experimental features: the master KIMI_CODE_EXPERIMENTAL_FLAG enables unreviewed agent-core-v2 features (tower, subagent fork) that kimi-plugin-cc has not certified under its no-plan safety profile. Unset KIMI_CODE_EXPERIMENTAL_FLAG and retry.", stage, {
|
|
98
|
+
details: {
|
|
99
|
+
refusal_kind: "v2-hook-order-unsafe",
|
|
100
|
+
retryable_after_setup: false,
|
|
101
|
+
experimental_v2: true,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve and certify the exact command tuple before a job row is created.
|
|
107
|
+
* The same tuple is persisted and later supplied to cli-client, including by a
|
|
108
|
+
* detached worker, so ambient PATH/config changes cannot silently choose an
|
|
109
|
+
* alternate route between dispatch and execution.
|
|
110
|
+
*/
|
|
111
|
+
export async function prepareKimiExecutionPlan(options) {
|
|
112
|
+
assertNoUnsafeExperimentalSelector(options.env);
|
|
113
|
+
if (options.intendedEngine === "native-v2" &&
|
|
114
|
+
!NATIVE_V2_CERTIFIED.has(options.operationKind)) {
|
|
115
|
+
throw nativeV2NotCertified(options.operationKind, null);
|
|
116
|
+
}
|
|
117
|
+
const kimi = resolveKimiCliCommand(options.env);
|
|
118
|
+
const exactCommand = await resolveExactExecutable(kimi.command, options.cwd, options.env);
|
|
119
|
+
const runPreflight = async (intendedEngine) => {
|
|
120
|
+
if (options.resumeSource) {
|
|
121
|
+
assertResumeEngineCompatible(options.resumeSource, intendedEngine, options.operationKind);
|
|
122
|
+
}
|
|
123
|
+
if (intendedEngine !== "native-v2")
|
|
124
|
+
return;
|
|
125
|
+
// Safety, not certification: runs for test-bypass plans too.
|
|
126
|
+
await assertNativeV2Preflight({
|
|
127
|
+
kimiHome: resolveKimiHome(options.env, options.cwd),
|
|
128
|
+
env: options.env,
|
|
129
|
+
resumeSessionId: options.resumeSessionId,
|
|
130
|
+
stage: "kimi-engine.plan",
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
if (options.env.KIMI_PLUGIN_CC_SKIP_VERSION_PROBE === "1") {
|
|
134
|
+
const intendedEngine = options.intendedEngine ?? "legacy-v1";
|
|
135
|
+
const safetyProfile = intendedEngine === "native-v2" ? NATIVE_V2_SAFETY_PROFILE : null;
|
|
136
|
+
await runPreflight(intendedEngine);
|
|
137
|
+
return Object.freeze({
|
|
138
|
+
schemaVersion: 1,
|
|
139
|
+
operationKind: options.operationKind,
|
|
140
|
+
intendedEngine,
|
|
141
|
+
command: exactCommand,
|
|
142
|
+
prefixArgs: Object.freeze([...kimi.prefixArgs]),
|
|
143
|
+
kimiVersion: null,
|
|
144
|
+
certification: "test-bypass",
|
|
145
|
+
resumedFromJobId: options.resumedFromJobId ?? null,
|
|
146
|
+
safetyProfile,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const probe = await probeKimiVersion({
|
|
150
|
+
kimiBin: exactCommand,
|
|
151
|
+
prefixArgs: kimi.prefixArgs,
|
|
152
|
+
cwd: options.cwd,
|
|
153
|
+
env: options.env,
|
|
154
|
+
});
|
|
155
|
+
if (probe.kind !== "ok") {
|
|
156
|
+
throw new RuntimeError("KIMI_EXECUTION_PLAN_UNRESOLVED", `Refusing to spawn kimi because the exact command/version plan could not be established: ${probe.reason}. Run Claude Code \`/kimi:setup\` or Codex \`$kimi-setup\` to verify the active binary, or correct KIMI_PLUGIN_CC_KIMI_BIN / KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS and retry.`, "kimi-engine.plan", {
|
|
157
|
+
details: {
|
|
158
|
+
operation_kind: options.operationKind,
|
|
159
|
+
intended_engine: options.intendedEngine ?? null,
|
|
160
|
+
command: exactCommand,
|
|
161
|
+
prefix_args: kimi.prefixArgs,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const intendedEngine = options.intendedEngine ?? selectIntendedEngine(options.operationKind, probe);
|
|
166
|
+
const safetyProfile = intendedEngine === "native-v2" ? NATIVE_V2_SAFETY_PROFILE : null;
|
|
167
|
+
assertCertifiedCapability(intendedEngine, options.operationKind, probe);
|
|
168
|
+
await runPreflight(intendedEngine);
|
|
169
|
+
return Object.freeze({
|
|
170
|
+
schemaVersion: 1,
|
|
171
|
+
operationKind: options.operationKind,
|
|
172
|
+
intendedEngine,
|
|
173
|
+
command: exactCommand,
|
|
174
|
+
prefixArgs: Object.freeze([...kimi.prefixArgs]),
|
|
175
|
+
kimiVersion: probe.version,
|
|
176
|
+
certification: "certified",
|
|
177
|
+
resumedFromJobId: options.resumedFromJobId ?? null,
|
|
178
|
+
safetyProfile,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export function assertExecutionPlanMatchesSpawn(plan, command, prefixArgs, env = {}) {
|
|
182
|
+
assertExecutionPlanShape(plan);
|
|
183
|
+
assertPlanCertification(plan, env);
|
|
184
|
+
if (plan.command !== command || !sameStrings(plan.prefixArgs, prefixArgs)) {
|
|
185
|
+
throw new RuntimeError("KIMI_EXECUTION_PLAN_MISMATCH", "Refusing to spawn kimi because the command tuple differs from the certified execution plan.", "cli-client.pre-spawn", {
|
|
186
|
+
details: {
|
|
187
|
+
operation_kind: plan.operationKind,
|
|
188
|
+
intended_engine: plan.intendedEngine,
|
|
189
|
+
planned_command: plan.command,
|
|
190
|
+
planned_prefix_args: plan.prefixArgs,
|
|
191
|
+
requested_command: command,
|
|
192
|
+
requested_prefix_args: prefixArgs,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function persistedExecutionPlanFields(plan) {
|
|
198
|
+
return {
|
|
199
|
+
operation_kind: plan.operationKind,
|
|
200
|
+
intended_engine: plan.intendedEngine,
|
|
201
|
+
observed_engine: null,
|
|
202
|
+
kimi_version: plan.kimiVersion,
|
|
203
|
+
system_version: null,
|
|
204
|
+
kimi_command: plan.command,
|
|
205
|
+
kimi_prefix_args: JSON.stringify(plan.prefixArgs),
|
|
206
|
+
plan_certification: plan.certification,
|
|
207
|
+
resumed_from_job_id: plan.resumedFromJobId,
|
|
208
|
+
safety_profile: plan.safetyProfile,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export function observedExecutionFields(result) {
|
|
212
|
+
return {
|
|
213
|
+
observed_engine: result.observedEngine,
|
|
214
|
+
system_version: result.systemVersion ?? null,
|
|
215
|
+
// Detection evidence from runtime/hook-escape.ts, JSON array or NULL.
|
|
216
|
+
hook_escapes: result.hookEscapes && result.hookEscapes.length > 0 ? JSON.stringify(result.hookEscapes) : null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
export function executionPlanFromPersisted(fields) {
|
|
220
|
+
if (fields.operation_kind === null ||
|
|
221
|
+
fields.intended_engine === null ||
|
|
222
|
+
fields.kimi_command === null ||
|
|
223
|
+
fields.kimi_prefix_args === null ||
|
|
224
|
+
fields.plan_certification === null) {
|
|
225
|
+
throw new RuntimeError("KIMI_EXECUTION_PLAN_MISSING", "This job predates durable engine provenance and cannot be used as a subprocess execution plan.", "kimi-engine.persisted-plan");
|
|
226
|
+
}
|
|
227
|
+
if (!isOperationKind(fields.operation_kind)) {
|
|
228
|
+
throw invalidPersistedPlan(`unknown operation_kind ${JSON.stringify(fields.operation_kind)}`);
|
|
229
|
+
}
|
|
230
|
+
if (!isKimiEngine(fields.intended_engine)) {
|
|
231
|
+
throw invalidPersistedPlan(`unknown intended_engine ${JSON.stringify(fields.intended_engine)}`);
|
|
232
|
+
}
|
|
233
|
+
if (!isPlanCertification(fields.plan_certification)) {
|
|
234
|
+
throw invalidPersistedPlan(`unknown plan_certification ${JSON.stringify(fields.plan_certification)}`);
|
|
235
|
+
}
|
|
236
|
+
if (fields.kimi_command.length === 0) {
|
|
237
|
+
throw invalidPersistedPlan("kimi_command is empty");
|
|
238
|
+
}
|
|
239
|
+
let prefixArgs;
|
|
240
|
+
try {
|
|
241
|
+
prefixArgs = JSON.parse(fields.kimi_prefix_args);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
throw invalidPersistedPlan("kimi_prefix_args is not valid JSON", error);
|
|
245
|
+
}
|
|
246
|
+
if (!Array.isArray(prefixArgs) || !prefixArgs.every((entry) => typeof entry === "string")) {
|
|
247
|
+
throw invalidPersistedPlan("kimi_prefix_args is not a string array");
|
|
248
|
+
}
|
|
249
|
+
const plan = Object.freeze({
|
|
250
|
+
schemaVersion: 1,
|
|
251
|
+
operationKind: fields.operation_kind,
|
|
252
|
+
intendedEngine: fields.intended_engine,
|
|
253
|
+
command: fields.kimi_command,
|
|
254
|
+
prefixArgs: Object.freeze([...prefixArgs]),
|
|
255
|
+
kimiVersion: fields.kimi_version,
|
|
256
|
+
certification: fields.plan_certification,
|
|
257
|
+
resumedFromJobId: fields.resumed_from_job_id,
|
|
258
|
+
safetyProfile: fields.safety_profile ?? null,
|
|
259
|
+
});
|
|
260
|
+
assertExecutionPlanShape(plan);
|
|
261
|
+
return plan;
|
|
262
|
+
}
|
|
263
|
+
const MAX_FORENSIC_LOG_BYTES = 32 * 1024 * 1024;
|
|
264
|
+
/**
|
|
265
|
+
* Best-effort, evidence-only persistence for a historical row. Missing,
|
|
266
|
+
* oversized, malformed, or conflicting logs leave the row unknown. This is
|
|
267
|
+
* deliberately non-blocking: provenance inspection must not break status or
|
|
268
|
+
* result retrieval for an old job.
|
|
269
|
+
*/
|
|
270
|
+
export async function reconcileHistoricalJobProvenance(store, job) {
|
|
271
|
+
if (job.observed_engine !== null && job.operation_kind !== null)
|
|
272
|
+
return job;
|
|
273
|
+
if (!job.stream_log_path)
|
|
274
|
+
return job;
|
|
275
|
+
try {
|
|
276
|
+
const info = await stat(job.stream_log_path);
|
|
277
|
+
if (!info.isFile() || info.size > MAX_FORENSIC_LOG_BYTES)
|
|
278
|
+
return job;
|
|
279
|
+
const evidence = classifyHistoricalEngineProvenance(await readFile(job.stream_log_path, "utf8"));
|
|
280
|
+
if (evidence.conflict)
|
|
281
|
+
return job;
|
|
282
|
+
return (store.backfillHistoricalProvenance(job.job_id, {
|
|
283
|
+
operation_kind: evidence.operationKind,
|
|
284
|
+
observed_engine: evidence.observedEngine,
|
|
285
|
+
kimi_version: evidence.kimiVersion,
|
|
286
|
+
system_version: evidence.observedEngine === "native-v2" ? evidence.kimiVersion : null,
|
|
287
|
+
}) ?? job);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return job;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Known cross-engine resumes are forbidden. Unknown historical rows keep the
|
|
295
|
+
* pre-provenance legacy-v1 behavior, but can never be continued on native v2:
|
|
296
|
+
* a v2 resume replays the saved journal, so only proven, plugin-managed v2
|
|
297
|
+
* lineage is accepted there.
|
|
298
|
+
*/
|
|
299
|
+
export function assertResumeEngineCompatible(source, targetEngine, operationKind) {
|
|
300
|
+
if (source.observed_engine === null && targetEngine === "native-v2") {
|
|
301
|
+
throw new RuntimeError("KIMI_SESSION_LINEAGE_UNKNOWN", `Refusing to resume session ${source.kimi_session_id ?? "<unknown>"}: job ${source.job_id} has no proven engine provenance, and native-v2 continues only plugin-managed native-v2 sessions. Start a fresh ${operationKind} session instead; the old job's status/result/replay remain available.`, `${operationKind}.resume`, {
|
|
302
|
+
details: {
|
|
303
|
+
refusal_kind: "session-lineage-unknown",
|
|
304
|
+
retryable_after_setup: false,
|
|
305
|
+
source_job_id: source.job_id,
|
|
306
|
+
source_engine: null,
|
|
307
|
+
target_engine: targetEngine,
|
|
308
|
+
operation_kind: operationKind,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (source.observed_engine === null || source.observed_engine === targetEngine)
|
|
313
|
+
return;
|
|
314
|
+
throw new RuntimeError("KIMI_SESSION_ENGINE_MISMATCH", `Refusing to resume session ${source.kimi_session_id ?? "<unknown>"}: job ${source.job_id} is proven ${source.observed_engine}, while the new ${operationKind} plan is ${targetEngine}. Start a fresh session instead.`, `${operationKind}.resume`, {
|
|
315
|
+
details: {
|
|
316
|
+
source_job_id: source.job_id,
|
|
317
|
+
source_engine: source.observed_engine,
|
|
318
|
+
target_engine: targetEngine,
|
|
319
|
+
operation_kind: operationKind,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Re-validate a PERSISTED resume before a detached worker spawns it. The
|
|
325
|
+
* dispatch path checks resume lineage against `resumeSource`, but a worker (or
|
|
326
|
+
* a forged/corrupt queued row) reloads the plan and hands `kimi_session_id`
|
|
327
|
+
* straight to the spawn — so the binding and lineage must be re-established
|
|
328
|
+
* from the store here, not trusted from the row. Callers invoke this ONLY when
|
|
329
|
+
* the row carries a session id to resume; a genuinely fresh session has none
|
|
330
|
+
* yet (kimi assigns it after the run), so it never reaches this check.
|
|
331
|
+
*
|
|
332
|
+
* Refuses when: the row carries a session id but the plan records no source
|
|
333
|
+
* job (a session with no plugin lineage can never be a plugin-managed resume);
|
|
334
|
+
* the recorded source job is missing; the source it names does not actually
|
|
335
|
+
* own the session being resumed (binding forgery); or the source's proven
|
|
336
|
+
* engine is incompatible with the plan's engine (incl. unknown lineage on a
|
|
337
|
+
* native-v2 target). The v2 preflight separately re-scans that session's
|
|
338
|
+
* journal for plan taint at the spawn boundary.
|
|
339
|
+
*/
|
|
340
|
+
export async function assertPersistedResumeLineage(store, plan, resumeSessionId, operationKind) {
|
|
341
|
+
if (plan.resumedFromJobId === null) {
|
|
342
|
+
throw new RuntimeError("KIMI_SESSION_LINEAGE_UNKNOWN", `Refusing to resume session ${resumeSessionId}: the persisted job carries a session id but records no source job, so its lineage cannot be established. Start a fresh session.`, `${operationKind}.resume`, {
|
|
343
|
+
details: {
|
|
344
|
+
refusal_kind: "session-lineage-unknown",
|
|
345
|
+
retryable_after_setup: false,
|
|
346
|
+
source_job_id: null,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const source = store.getJob(plan.resumedFromJobId);
|
|
351
|
+
if (!source) {
|
|
352
|
+
throw new RuntimeError("KIMI_SESSION_LINEAGE_UNKNOWN", `Refusing to resume session ${resumeSessionId}: the persisted source job ${plan.resumedFromJobId} no longer exists, so its engine lineage cannot be established. Start a fresh session.`, `${operationKind}.resume`, {
|
|
353
|
+
details: {
|
|
354
|
+
refusal_kind: "session-lineage-unknown",
|
|
355
|
+
retryable_after_setup: false,
|
|
356
|
+
source_job_id: plan.resumedFromJobId,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const reconciled = await reconcileHistoricalJobProvenance(store, source);
|
|
361
|
+
if (reconciled.kimi_session_id !== resumeSessionId) {
|
|
362
|
+
throw new RuntimeError("KIMI_SESSION_LINEAGE_UNKNOWN", `Refusing to resume session ${resumeSessionId}: the persisted source job ${plan.resumedFromJobId} owns a different session (${reconciled.kimi_session_id ?? "<none>"}). Start a fresh session.`, `${operationKind}.resume`, {
|
|
363
|
+
details: {
|
|
364
|
+
refusal_kind: "session-lineage-unknown",
|
|
365
|
+
retryable_after_setup: false,
|
|
366
|
+
source_job_id: plan.resumedFromJobId,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
assertResumeEngineCompatible(reconciled, plan.intendedEngine, operationKind);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Classify saved plugin logs without guessing. `system.version` and wire
|
|
374
|
+
* protocol 1.5 are positive v2 evidence; a spawn that explicitly records the
|
|
375
|
+
* forced legacy flag, or wire protocol <=1.4, is positive v1 evidence. Absence
|
|
376
|
+
* of either remains unknown, covering the historical v1.9.5 routing window.
|
|
377
|
+
*/
|
|
378
|
+
export function classifyHistoricalEngineProvenance(contents) {
|
|
379
|
+
let sawLegacy = false;
|
|
380
|
+
let sawNative = false;
|
|
381
|
+
let sawLegacyForcedSpawn = false;
|
|
382
|
+
let sawRunEvidence = false;
|
|
383
|
+
let kimiVersion = null;
|
|
384
|
+
let operationKind = null;
|
|
385
|
+
const evidence = [];
|
|
386
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
387
|
+
const line = rawLine.trim();
|
|
388
|
+
if (line.length === 0)
|
|
389
|
+
continue;
|
|
390
|
+
let entry;
|
|
391
|
+
try {
|
|
392
|
+
entry = JSON.parse(line);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (!isRecord(entry))
|
|
398
|
+
continue;
|
|
399
|
+
if (entry.event === "system_version" && typeof entry.version === "string") {
|
|
400
|
+
sawNative = true;
|
|
401
|
+
kimiVersion = parseVersionLine(entry.version)?.raw ?? entry.version;
|
|
402
|
+
evidence.push("stream-json:system.version");
|
|
403
|
+
}
|
|
404
|
+
if (entry.event === "spawn" && entry.legacy_v1_forced === true) {
|
|
405
|
+
sawLegacyForcedSpawn = true;
|
|
406
|
+
}
|
|
407
|
+
if (entry.event === "record" ||
|
|
408
|
+
entry.event === "session_announce" ||
|
|
409
|
+
entry.event === "goal_summary" ||
|
|
410
|
+
(entry.event === "exit" && entry.exit_code === 0)) {
|
|
411
|
+
sawRunEvidence = true;
|
|
412
|
+
}
|
|
413
|
+
const protocolVersions = entry.type === "metadata" && typeof entry.protocol_version === "string"
|
|
414
|
+
? [entry.protocol_version]
|
|
415
|
+
: [];
|
|
416
|
+
for (const protocolVersion of protocolVersions) {
|
|
417
|
+
const parsed = parseProtocolVersion(protocolVersion);
|
|
418
|
+
if (parsed === null)
|
|
419
|
+
continue;
|
|
420
|
+
if (parsed.major > 1 || (parsed.major === 1 && parsed.minor >= 5)) {
|
|
421
|
+
sawNative = true;
|
|
422
|
+
evidence.push(`session-wire:protocol-${protocolVersion}`);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
sawLegacy = true;
|
|
426
|
+
evidence.push(`session-wire:protocol-${protocolVersion}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
operationKind ??= operationKindFromLogEntry(entry);
|
|
430
|
+
}
|
|
431
|
+
if (sawLegacyForcedSpawn && sawRunEvidence) {
|
|
432
|
+
sawLegacy = true;
|
|
433
|
+
evidence.push("spawn:legacy_v1_forced+run-evidence");
|
|
434
|
+
}
|
|
435
|
+
const conflict = sawLegacy && sawNative;
|
|
436
|
+
return {
|
|
437
|
+
observedEngine: conflict ? null : sawNative ? "native-v2" : sawLegacy ? "legacy-v1" : null,
|
|
438
|
+
kimiVersion,
|
|
439
|
+
operationKind,
|
|
440
|
+
evidence: Object.freeze([...new Set(evidence)]),
|
|
441
|
+
conflict,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function assertCertifiedCapability(engine, operationKind, probe) {
|
|
445
|
+
if (engine === "native-v2") {
|
|
446
|
+
if (!isNativeV2Certified(operationKind, probe.version)) {
|
|
447
|
+
throw nativeV2NotCertified(operationKind, probe.version);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const minimum = LEGACY_V1_MINIMUMS[operationKind];
|
|
452
|
+
const meetsMinimum = probe.major > minimum.major ||
|
|
453
|
+
(probe.major === minimum.major && probe.minor >= minimum.minor);
|
|
454
|
+
if (!probe.inTestedRange || !meetsMinimum) {
|
|
455
|
+
const remedy = !probe.inTestedRange
|
|
456
|
+
? "Update kimi-plugin-cc to a release that certifies this kimi-code minor, or point KIMI_PLUGIN_CC_KIMI_BIN at a certified binary."
|
|
457
|
+
: `Upgrade kimi-code to at least ${minimum.major}.${minimum.minor}.0 within a certified minor.`;
|
|
458
|
+
throw new RuntimeError("KIMI_CAPABILITY_NOT_CERTIFIED", `Refusing ${operationKind}: kimi-code ${probe.version} is not in this operation's certified legacy-v1 range. ${remedy} KIMI_PLUGIN_CC_SKIP_VERSION_PROBE is a test/smoke seam, not a production repair path.`, "kimi-engine.capability", {
|
|
459
|
+
details: {
|
|
460
|
+
refusal_kind: "v1-version-not-certified",
|
|
461
|
+
retryable_after_setup: false,
|
|
462
|
+
operation_kind: operationKind,
|
|
463
|
+
intended_engine: engine,
|
|
464
|
+
kimi_version: probe.version,
|
|
465
|
+
minimum_version: `${minimum.major}.${minimum.minor}.0`,
|
|
466
|
+
tested_minor: probe.inTestedRange,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Revalidate the persisted certification at the final subprocess boundary.
|
|
473
|
+
* This keeps a corrupt/forged SQLite row from converting a once-certified plan
|
|
474
|
+
* into an untested-version or native-v2 spawn.
|
|
475
|
+
*/
|
|
476
|
+
function assertPlanCertification(plan, env) {
|
|
477
|
+
if (plan.intendedEngine === "native-v2" && plan.safetyProfile !== NATIVE_V2_SAFETY_PROFILE) {
|
|
478
|
+
// A forged/deserialized v2 row cannot reach spawn without the profile it
|
|
479
|
+
// was certified under.
|
|
480
|
+
throw invalidPersistedPlan("native-v2 plans require the native-v2-no-plan safety profile");
|
|
481
|
+
}
|
|
482
|
+
if (plan.intendedEngine === "legacy-v1" && plan.safetyProfile !== null) {
|
|
483
|
+
throw invalidPersistedPlan("legacy-v1 plans must not carry a safety profile");
|
|
484
|
+
}
|
|
485
|
+
if (plan.certification === "test-bypass") {
|
|
486
|
+
if (plan.kimiVersion !== null) {
|
|
487
|
+
throw invalidPersistedPlan("test-bypass plans must have kimi_version=null");
|
|
488
|
+
}
|
|
489
|
+
if (env.KIMI_PLUGIN_CC_SKIP_VERSION_PROBE !== "1") {
|
|
490
|
+
throw invalidPersistedPlan("test-bypass plans require KIMI_PLUGIN_CC_SKIP_VERSION_PROBE=1 at the final spawn boundary");
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (plan.kimiVersion === null) {
|
|
495
|
+
throw invalidPersistedPlan("certified plans require kimi_version");
|
|
496
|
+
}
|
|
497
|
+
const parsed = parseVersionLine(plan.kimiVersion);
|
|
498
|
+
if (parsed === undefined) {
|
|
499
|
+
throw invalidPersistedPlan("kimi_version is not a parseable semantic version");
|
|
500
|
+
}
|
|
501
|
+
assertCertifiedCapability(plan.intendedEngine, plan.operationKind, {
|
|
502
|
+
kind: "ok",
|
|
503
|
+
version: parsed.raw,
|
|
504
|
+
major: parsed.major,
|
|
505
|
+
minor: parsed.minor,
|
|
506
|
+
patch: parsed.patch,
|
|
507
|
+
inTestedRange: isInTestedRange(parsed.major, parsed.minor),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function assertExecutionPlanShape(plan) {
|
|
511
|
+
const candidate = plan;
|
|
512
|
+
if (candidate.schemaVersion !== 1) {
|
|
513
|
+
throw invalidPersistedPlan("unsupported schemaVersion");
|
|
514
|
+
}
|
|
515
|
+
if (typeof candidate.operationKind !== "string" || !isOperationKind(candidate.operationKind)) {
|
|
516
|
+
throw invalidPersistedPlan("operationKind is invalid");
|
|
517
|
+
}
|
|
518
|
+
if (typeof candidate.intendedEngine !== "string" || !isKimiEngine(candidate.intendedEngine)) {
|
|
519
|
+
throw invalidPersistedPlan("intendedEngine is invalid");
|
|
520
|
+
}
|
|
521
|
+
if (typeof candidate.certification !== "string" ||
|
|
522
|
+
!isPlanCertification(candidate.certification)) {
|
|
523
|
+
throw invalidPersistedPlan("certification is invalid");
|
|
524
|
+
}
|
|
525
|
+
if (typeof candidate.command !== "string" || candidate.command.length === 0) {
|
|
526
|
+
throw invalidPersistedPlan("command is empty or invalid");
|
|
527
|
+
}
|
|
528
|
+
if (!Array.isArray(candidate.prefixArgs) ||
|
|
529
|
+
!candidate.prefixArgs.every((entry) => typeof entry === "string")) {
|
|
530
|
+
throw invalidPersistedPlan("prefixArgs is not a string array");
|
|
531
|
+
}
|
|
532
|
+
if (candidate.kimiVersion !== null && typeof candidate.kimiVersion !== "string") {
|
|
533
|
+
throw invalidPersistedPlan("kimiVersion is invalid");
|
|
534
|
+
}
|
|
535
|
+
if (candidate.resumedFromJobId !== null &&
|
|
536
|
+
typeof candidate.resumedFromJobId !== "string") {
|
|
537
|
+
throw invalidPersistedPlan("resumedFromJobId is invalid");
|
|
538
|
+
}
|
|
539
|
+
if (candidate.safetyProfile !== null && candidate.safetyProfile !== NATIVE_V2_SAFETY_PROFILE) {
|
|
540
|
+
throw invalidPersistedPlan("safetyProfile is invalid");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function nativeV2NotCertified(operationKind, version) {
|
|
544
|
+
const certified = NATIVE_V2_CERTIFIED.get(operationKind) ?? [];
|
|
545
|
+
const observed = version === null ? "an unprobed version" : `kimi-code ${version}`;
|
|
546
|
+
return new RuntimeError("KIMI_CAPABILITY_NOT_CERTIFIED", `Refusing ${operationKind} on native v2: ${observed} is not in this operation's exactly certified set [${certified.join(", ")}]. Update kimi-plugin-cc to a release that certifies this kimi-code version, or point KIMI_PLUGIN_CC_KIMI_BIN at a certified binary. KIMI_PLUGIN_CC_SKIP_VERSION_PROBE is a test/smoke seam, not a production repair path.`, "kimi-engine.capability", {
|
|
547
|
+
details: {
|
|
548
|
+
refusal_kind: "v2-version-not-certified",
|
|
549
|
+
retryable_after_setup: false,
|
|
550
|
+
operation_kind: operationKind,
|
|
551
|
+
intended_engine: "native-v2",
|
|
552
|
+
kimi_version: version,
|
|
553
|
+
certified_versions: [...certified],
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function invalidPersistedPlan(message, cause) {
|
|
558
|
+
return new RuntimeError("KIMI_EXECUTION_PLAN_INVALID", `Persisted kimi execution plan is invalid: ${message}.`, "kimi-engine.persisted-plan", cause instanceof Error ? { cause } : undefined);
|
|
559
|
+
}
|
|
560
|
+
function sameStrings(left, right) {
|
|
561
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
562
|
+
}
|
|
563
|
+
async function resolveExactExecutable(command, cwd, env) {
|
|
564
|
+
const candidates = [];
|
|
565
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
566
|
+
candidates.push(path.isAbsolute(command) ? command : path.resolve(cwd, command));
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const searchPath = env.PATH ?? process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
570
|
+
const extensions = process.platform === "win32"
|
|
571
|
+
? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
572
|
+
: [""];
|
|
573
|
+
for (const entry of searchPath.split(path.delimiter)) {
|
|
574
|
+
const directory = entry.length === 0 ? cwd : path.isAbsolute(entry) ? entry : path.resolve(cwd, entry);
|
|
575
|
+
for (const extension of extensions) {
|
|
576
|
+
candidates.push(path.join(directory, `${command}${extension}`));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const candidate of candidates) {
|
|
581
|
+
try {
|
|
582
|
+
await access(candidate, process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK);
|
|
583
|
+
return await realpath(candidate);
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
throw new RuntimeError("KIMI_EXECUTION_PLAN_UNRESOLVED", `Refusing to spawn kimi because executable ${JSON.stringify(command)} could not be resolved to an exact runnable path. Run Claude Code \`/kimi:setup\` or Codex \`$kimi-setup\` to verify the active binary, or correct KIMI_PLUGIN_CC_KIMI_BIN and retry.`, "kimi-engine.plan", { details: { command, cwd } });
|
|
589
|
+
}
|
|
590
|
+
function isRecord(value) {
|
|
591
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
592
|
+
}
|
|
593
|
+
function parseProtocolVersion(value) {
|
|
594
|
+
const match = value.match(/^(\d+)\.(\d+)(?:\.\d+)?$/);
|
|
595
|
+
if (match === null)
|
|
596
|
+
return null;
|
|
597
|
+
return { major: Number(match[1]), minor: Number(match[2]) };
|
|
598
|
+
}
|
|
599
|
+
function operationKindFromLogEntry(entry) {
|
|
600
|
+
if (typeof entry.operation_kind === "string" && isOperationKind(entry.operation_kind)) {
|
|
601
|
+
return entry.operation_kind;
|
|
602
|
+
}
|
|
603
|
+
const candidate = entry.event === "spawn"
|
|
604
|
+
? entry.command_label
|
|
605
|
+
: entry.direction === "meta" && isRecord(entry.message)
|
|
606
|
+
? entry.message.commandType
|
|
607
|
+
: null;
|
|
608
|
+
if (typeof candidate !== "string")
|
|
609
|
+
return null;
|
|
610
|
+
// Historical `rescue` labels cover both ordinary rescue and pursue, whose
|
|
611
|
+
// write-policy label intentionally reuses rescue. Without the newer explicit
|
|
612
|
+
// operation_kind field, choosing either would be a guess.
|
|
613
|
+
if (candidate === "rescue")
|
|
614
|
+
return null;
|
|
615
|
+
return isOperationKind(candidate) ? candidate : null;
|
|
616
|
+
}
|
|
617
|
+
function isOperationKind(value) {
|
|
618
|
+
return (value === "review" ||
|
|
619
|
+
value === "challenge" ||
|
|
620
|
+
value === "ask" ||
|
|
621
|
+
value === "rescue" ||
|
|
622
|
+
value === "review_gate" ||
|
|
623
|
+
value === "pursue" ||
|
|
624
|
+
value === "swarm" ||
|
|
625
|
+
value === "swarm-write");
|
|
626
|
+
}
|
|
627
|
+
function isKimiEngine(value) {
|
|
628
|
+
return value === "legacy-v1" || value === "native-v2";
|
|
629
|
+
}
|
|
630
|
+
function isPlanCertification(value) {
|
|
631
|
+
return value === "certified" || value === "test-bypass";
|
|
632
|
+
}
|