kimi-plugin-cc 2.2.2 → 2.4.0
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 +2 -2
- package/.claude-plugin/plugin.json +5 -3
- package/.mcp.json +13 -0
- package/CHANGELOG.md +18 -0
- package/README.md +57 -5
- package/agents/kimi-challenge.md +3 -3
- package/agents/kimi-pursue.md +1 -1
- package/agents/kimi-rescue.md +2 -2
- package/agents/kimi-review.md +3 -3
- package/commands/README.md +2 -2
- package/commands/challenge.md +1 -1
- package/commands/pursue.md +1 -1
- package/commands/review.md +1 -1
- package/dist/background-spawn.js +9 -17
- package/dist/cli-client.js +24 -63
- package/dist/commands/ask.js +28 -31
- package/dist/commands/cancel.js +50 -23
- package/dist/commands/cli-helpers.js +7 -16
- package/dist/commands/pursue.js +24 -33
- package/dist/commands/registry.js +6 -19
- package/dist/commands/rescue.js +23 -22
- package/dist/commands/review-gate.js +18 -28
- package/dist/commands/review.js +41 -45
- package/dist/commands/setup.js +85 -171
- package/dist/commands/swarm.js +107 -91
- package/dist/commands/worker.js +0 -6
- package/dist/companion.js +13 -40
- package/dist/git.js +14 -7
- package/dist/help.js +2 -0
- package/dist/hooks/approval-hook.js +9 -20
- package/dist/hooks/approval-policy.js +36 -69
- package/dist/hooks/config-safety.js +6 -4
- package/dist/hooks/install-paths.js +62 -138
- package/dist/hooks/install.js +37 -108
- package/dist/hooks/managed-block.js +88 -214
- package/dist/job-id.js +12 -0
- package/dist/job-store.js +65 -28
- package/dist/jobs.js +9 -1
- package/dist/kimi-command.js +1 -3
- package/dist/kimi-engine.js +1 -1
- package/dist/kimi-timeouts.js +9 -21
- package/dist/kimi-version-probe.js +16 -1210
- package/dist/mcp/binding.js +58 -0
- package/dist/mcp/job-control.js +345 -0
- package/dist/mcp/launcher.js +257 -0
- package/dist/mcp/operations.js +301 -0
- package/dist/mcp/protocol.js +275 -0
- package/dist/mcp/server.js +8 -0
- package/dist/mcp/stdio.js +78 -0
- package/dist/mcp/supervised-child.js +15 -0
- package/dist/mcp/tool-result.js +7 -0
- package/dist/mcp/workspace.js +34 -0
- package/dist/model-command.js +32 -0
- package/dist/native-v2-preflight.js +1 -1
- package/dist/parsing.js +4 -18
- package/dist/rescue-approval.js +78 -164
- package/dist/session-recovery.js +7 -10
- package/dist/stream-json.js +6 -174
- package/dist/version.js +1 -1
- package/docs/migration.md +2 -6
- package/docs/registry-releases.md +23 -7
- package/docs/safety.md +30 -14
- package/package.json +7 -3
- package/plugins/kimi-codex/.codex-plugin/plugin.json +8 -5
- package/plugins/kimi-codex/.mcp.json +28 -0
- package/plugins/kimi-codex/dist/background-spawn.js +9 -17
- package/plugins/kimi-codex/dist/cli-client.js +24 -63
- package/plugins/kimi-codex/dist/commands/ask.js +28 -31
- package/plugins/kimi-codex/dist/commands/cancel.js +50 -23
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +7 -16
- package/plugins/kimi-codex/dist/commands/pursue.js +24 -33
- package/plugins/kimi-codex/dist/commands/registry.js +6 -19
- package/plugins/kimi-codex/dist/commands/rescue.js +23 -22
- package/plugins/kimi-codex/dist/commands/review-gate.js +18 -28
- package/plugins/kimi-codex/dist/commands/review.js +41 -45
- package/plugins/kimi-codex/dist/commands/setup.js +85 -171
- package/plugins/kimi-codex/dist/commands/swarm.js +107 -91
- package/plugins/kimi-codex/dist/commands/worker.js +0 -6
- package/plugins/kimi-codex/dist/companion.js +13 -40
- package/plugins/kimi-codex/dist/git.js +14 -7
- package/plugins/kimi-codex/dist/help.js +2 -0
- package/plugins/kimi-codex/dist/hooks/approval-hook.js +9 -20
- package/plugins/kimi-codex/dist/hooks/approval-policy.js +36 -69
- package/plugins/kimi-codex/dist/hooks/config-safety.js +6 -4
- package/plugins/kimi-codex/dist/hooks/install-paths.js +62 -138
- package/plugins/kimi-codex/dist/hooks/install.js +37 -108
- package/plugins/kimi-codex/dist/hooks/managed-block.js +88 -214
- package/plugins/kimi-codex/dist/job-id.js +12 -0
- package/plugins/kimi-codex/dist/job-store.js +65 -28
- package/plugins/kimi-codex/dist/jobs.js +9 -1
- package/plugins/kimi-codex/dist/kimi-command.js +1 -3
- package/plugins/kimi-codex/dist/kimi-engine.js +1 -1
- package/plugins/kimi-codex/dist/kimi-timeouts.js +9 -21
- package/plugins/kimi-codex/dist/kimi-version-probe.js +16 -1210
- package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
- package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
- package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-codex/dist/mcp/server.js +8 -0
- package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-codex/dist/model-command.js +32 -0
- package/plugins/kimi-codex/dist/native-v2-preflight.js +1 -1
- package/plugins/kimi-codex/dist/parsing.js +4 -18
- package/plugins/kimi-codex/dist/rescue-approval.js +78 -164
- package/plugins/kimi-codex/dist/session-recovery.js +7 -10
- package/plugins/kimi-codex/dist/stream-json.js +6 -174
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +2 -2
- package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-result/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +3 -3
- package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-status/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +1 -1
- package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { RuntimeError } from "../errors.js";
|
|
4
|
+
import { resolveRepoIdentity } from "../git.js";
|
|
5
|
+
export async function resolveMcpWorkspace(env, requested) {
|
|
6
|
+
const defaultCandidate = env.KIMI_PLUGIN_CC_MCP_WORKSPACE;
|
|
7
|
+
const candidate = requested ?? defaultCandidate;
|
|
8
|
+
if (!candidate) {
|
|
9
|
+
throw new RuntimeError("MCP_WORKSPACE_REQUIRED", "This MCP server has no default workspace. Supply the absolute workspace directory explicitly; Codex calls normally require it.", "mcp.workspace");
|
|
10
|
+
}
|
|
11
|
+
const cwd = await canonicalDirectory(candidate, "workspace");
|
|
12
|
+
if (defaultCandidate) {
|
|
13
|
+
const defaultCwd = await canonicalDirectory(defaultCandidate, "host workspace");
|
|
14
|
+
const relative = path.relative(defaultCwd, cwd);
|
|
15
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
16
|
+
throw new RuntimeError("MCP_WORKSPACE_OUTSIDE_DEFAULT", `workspace must be the host workspace or one of its descendants, not ${cwd} (host: ${defaultCwd}).`, "mcp.workspace");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { cwd, repo: await resolveRepoIdentity(cwd) };
|
|
20
|
+
}
|
|
21
|
+
async function canonicalDirectory(candidate, label) {
|
|
22
|
+
if (!path.isAbsolute(candidate)) {
|
|
23
|
+
throw new RuntimeError("MCP_WORKSPACE_NOT_ABSOLUTE", `${label} must be an absolute existing directory.`, "mcp.workspace");
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const cwd = await realpath(candidate);
|
|
27
|
+
if (!(await stat(cwd)).isDirectory())
|
|
28
|
+
throw new Error("not a directory");
|
|
29
|
+
return cwd;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new RuntimeError("MCP_WORKSPACE_INVALID", `${label} is not an accessible existing directory: ${candidate}`, "mcp.workspace", error instanceof Error ? { cause: error } : undefined);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { runAsk } from "./commands/ask.js";
|
|
2
|
+
import { notImplementedCompanionCommand } from "./commands/not-implemented.js";
|
|
3
|
+
import { runPursue } from "./commands/pursue.js";
|
|
4
|
+
import { runRescue } from "./commands/rescue.js";
|
|
5
|
+
import { runReview } from "./commands/review.js";
|
|
6
|
+
import { runSwarm } from "./commands/swarm.js";
|
|
7
|
+
export async function runModelCommand(argv, context) {
|
|
8
|
+
const [command, ...rest] = argv;
|
|
9
|
+
switch (command) {
|
|
10
|
+
case "review":
|
|
11
|
+
return `${await runReview(rest, context, "review")}\n`;
|
|
12
|
+
case "ask":
|
|
13
|
+
return `${await runAsk(rest, context)}\n`;
|
|
14
|
+
case "task": {
|
|
15
|
+
const [taskType, ...taskArgs] = rest;
|
|
16
|
+
switch (taskType) {
|
|
17
|
+
case "rescue":
|
|
18
|
+
return runRescue(taskArgs, context);
|
|
19
|
+
case "challenge":
|
|
20
|
+
return `${await runReview(taskArgs, context, "challenge")}\n`;
|
|
21
|
+
case "pursue":
|
|
22
|
+
return runPursue(taskArgs, context);
|
|
23
|
+
case "swarm":
|
|
24
|
+
return runSwarm(taskArgs, context);
|
|
25
|
+
default:
|
|
26
|
+
notImplementedCompanionCommand(taskType ? `task ${taskType}` : "task");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
default:
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// chain-breaking `event.allow()` is the plan-file guard
|
|
7
7
|
// (features/plan/planService.ts:110), and it fires only while plan mode is
|
|
8
8
|
// ACTIVE. Plan mode can arm in a plugin-spawned `kimi -p` session in exactly
|
|
9
|
-
// three ways
|
|
9
|
+
// three ways:
|
|
10
10
|
// A. `default_plan_mode = true` in <KIMI_CODE_HOME>/config.toml, read ONCE
|
|
11
11
|
// inside sessions.create() (single user file; no env, argv or project
|
|
12
12
|
// overlay; invalid values fall back to false upstream);
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* or — for agent-mediated callers — hallucinated flag names (e.g. wrappers
|
|
6
|
-
* inventing `--file` / `--context` because that shape is common in other
|
|
7
|
-
* CLIs). The historical behavior — silently treating them as free-form
|
|
8
|
-
* trailing prompt/focus text with an advisory stderr warning — produced
|
|
9
|
-
* misleading runs (Kimi chewing on a bloated blob, or hitting the prompt
|
|
10
|
-
* timeout). All command parsers now hard-fail on unknown flag-shaped tokens
|
|
11
|
-
* while still preserving prose mode after the first non-flag token and the
|
|
12
|
-
* `--` escape.
|
|
3
|
+
* Unknown flag-shaped tokens hard-fail instead of being absorbed into prose.
|
|
4
|
+
* Parsing still switches to prose at the first non-flag token or after `--`.
|
|
13
5
|
*/
|
|
14
6
|
function looksLikeFlag(token) {
|
|
15
7
|
if (!token.startsWith("-")) {
|
|
@@ -39,8 +31,8 @@ export function validateThinkingEffort(value, stage = "args.parse", lowercase =
|
|
|
39
31
|
export const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --turns <N>";
|
|
40
32
|
export const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
|
|
41
33
|
/**
|
|
42
|
-
* Parser for /kimi:swarm
|
|
43
|
-
*
|
|
34
|
+
* Parser for /kimi:swarm. Foreground-only: no --background/--fresh/--resume.
|
|
35
|
+
* Trailing tokens are the objective.
|
|
44
36
|
*/
|
|
45
37
|
export function parseSwarmArgs(argv) {
|
|
46
38
|
let model;
|
|
@@ -256,7 +248,6 @@ export function parsePursueArgs(argv) {
|
|
|
256
248
|
objective: trailingTokens?.join(" "),
|
|
257
249
|
};
|
|
258
250
|
}
|
|
259
|
-
/** Help before -- wins even over malformed flags or prose trailing-token mode. */
|
|
260
251
|
const VALUE_TAKING_FLAGS = new Set(["--thinking", "-m", "--model", "--budget", "--resume", "--base", "--cap", "--max-concurrency", "--turns", "--type"]);
|
|
261
252
|
/**
|
|
262
253
|
* `--help` / `-h` counts only inside the leading flag region: before `--`
|
|
@@ -565,11 +556,6 @@ function parseKnownFlags(argv, knownFlags, commandName) {
|
|
|
565
556
|
}
|
|
566
557
|
if (!knownFlags.has(token)) {
|
|
567
558
|
if (looksLikeFlag(token)) {
|
|
568
|
-
// Wrapper agents routinely hallucinate flags like `--file` /
|
|
569
|
-
// `--context` because that shape is common in other CLIs. Silently
|
|
570
|
-
// slurping them into focus text produced a bloated prompt that
|
|
571
|
-
// looked like a hang. Hard-fail with the supported list inline so
|
|
572
|
-
// the error message itself is the correction.
|
|
573
559
|
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ${commandName}. Supported flags: ${REVIEW_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped focus text to pass it through.`, "args.parse");
|
|
574
560
|
}
|
|
575
561
|
trailingTokens = argv.slice(index);
|
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
import { access, lstat, realpath } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse } from "./vendor/shell-quote/index.js";
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// types lived in `runtime/wire/` which PR 4 deletes.
|
|
10
|
-
//
|
|
11
|
-
// The v1.0 entrypoint, `evaluateRescueHookRequest`, takes the
|
|
12
|
-
// PreToolUse hook shape (toolName + raw toolInput from kimi-code's
|
|
13
|
-
// hook stdin) and returns a `HookDecision`. All the security helpers
|
|
14
|
-
// below are unchanged from v0.4 — `checkApprovedPath`,
|
|
15
|
-
// `validateShellCommand`, `validateShellStage`, `hasUnsafeShellSyntax`,
|
|
16
|
-
// `hasMutatingFlag`, `validatePackageManagerCommand`, plus the
|
|
17
|
-
// constant tables — kept verbatim so the bug-history reasoning that
|
|
18
|
-
// went into them (e.g. git pre-subcommand flags, sed `-i.bak`,
|
|
19
|
-
// shell-quote pipeline parsing) doesn't have to be re-derived. The
|
|
20
|
-
// thin dispatcher at the top translates between the two surfaces.
|
|
21
|
-
// Flags that direct a command to write its output to a file. Audit
|
|
22
|
-
// report 28 (Codex M2) found that `git diff --output=secret.txt` slipped
|
|
23
|
-
// through the allowlist: the path argument bypasses rescue's
|
|
24
|
-
// workspace-bound file-edit check because the write happens via the
|
|
25
|
-
// tool's own --output semantics, not via shell redirection. Treating
|
|
26
|
-
// these as mutating closes that class (git diff, jq, dot, curl, openssl,
|
|
27
|
-
// many compilers — anything that advertises `--output=PATH`).
|
|
28
|
-
//
|
|
29
|
-
// `-o` short form is intentionally NOT blanket-banned: too many
|
|
30
|
-
// read-only commands use it for non-file purposes (rg -o, awk -o,
|
|
31
|
-
// grep --only-matching aliases). The space-separated `--output PATH`
|
|
32
|
-
// two-arg form is implicitly rejected because the scanner doesn't pair
|
|
33
|
-
// args — `--output` exact match below catches the flag itself.
|
|
4
|
+
// Tool-owned output flags write without shell redirection and therefore bypass
|
|
5
|
+
// path checks unless treated as mutating. The `-o` short form is not banned
|
|
6
|
+
// globally because tools such as rg and grep use it for read-only behavior;
|
|
7
|
+
// per-tool checks handle it. Exact `--output` also rejects the two-argument
|
|
8
|
+
// form because this scanner intentionally does not pair arguments.
|
|
34
9
|
const MUTATING_FLAGS_EXACT = new Set([
|
|
35
10
|
"--fix",
|
|
36
11
|
"--write",
|
|
@@ -53,16 +28,10 @@ const MUTATING_FLAG_PREFIXES = [
|
|
|
53
28
|
"--output-directory=",
|
|
54
29
|
"--output-dir=",
|
|
55
30
|
];
|
|
56
|
-
// Flags whose VALUE is executed as a command or external tool
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// - `git grep --open-files-in-pager=touch /tmp/x needle` → git runs `touch`
|
|
61
|
-
// (F1, CRITICAL — same pager-smuggling class as the pre-subcommand
|
|
62
|
-
// `git -c core.pager=` defense, but via a subcommand flag)
|
|
63
|
-
// - `go vet -vettool=<bin>`, `go test -exec <bin>` / `-toolexec <bin>` → runs <bin>
|
|
64
|
-
// The git `-O` short form of --open-files-in-pager is handled in the git
|
|
65
|
-
// branch (it collides with `find -O<level>`, so it can't be banned globally).
|
|
31
|
+
// Flags whose VALUE is executed as a command or external tool are rejected for
|
|
32
|
+
// every tool: Git pager overrides and Go -vettool/-exec/-toolexec are direct
|
|
33
|
+
// command-execution escapes. Git's -O short form is handled in its own branch
|
|
34
|
+
// because it collides with find's read-only -O<level> syntax.
|
|
66
35
|
const EXEC_DELEGATING_FLAGS_EXACT = new Set([
|
|
67
36
|
"--open-files-in-pager",
|
|
68
37
|
"-vettool",
|
|
@@ -71,9 +40,8 @@ const EXEC_DELEGATING_FLAGS_EXACT = new Set([
|
|
|
71
40
|
"--toolexec",
|
|
72
41
|
"-exec",
|
|
73
42
|
"-execdir",
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// CMD to (de)compress spill files (RCE on GNU sort). Verified 2026-07-17.
|
|
43
|
+
// rg --pre/--hostname-bin and GNU sort --compress-program also execute their
|
|
44
|
+
// values as commands.
|
|
77
45
|
"--pre",
|
|
78
46
|
"--hostname-bin",
|
|
79
47
|
"--compress-program",
|
|
@@ -88,10 +56,9 @@ const EXEC_DELEGATING_FLAG_PREFIXES = [
|
|
|
88
56
|
"--hostname-bin=",
|
|
89
57
|
"--compress-program=",
|
|
90
58
|
];
|
|
91
|
-
// Pytest
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// with argparse (allow_abbrev), so match these as abbreviation-aware prefixes.
|
|
59
|
+
// Pytest report flags write to arbitrary paths. Test runners execute repo code
|
|
60
|
+
// by design, so these checks block known runner-owned report outputs rather
|
|
61
|
+
// than every possible write. Match argparse allow_abbrev prefixes as well.
|
|
95
62
|
const PYTEST_REPORT_FLAGS = [
|
|
96
63
|
"--junitxml",
|
|
97
64
|
"--junit-xml",
|
|
@@ -114,7 +81,7 @@ const BANNED_FIND_ACTIONS = new Set([
|
|
|
114
81
|
"-fls",
|
|
115
82
|
]);
|
|
116
83
|
// Per-tool flags that write to an arbitrary path even in the tool's read/check
|
|
117
|
-
// mode
|
|
84
|
+
// mode. Each takes a path value (space- or =-separated);
|
|
118
85
|
// `hasFlagFromSet` matches both spellings.
|
|
119
86
|
const GO_WRITE_FLAGS = new Set([
|
|
120
87
|
"-coverprofile",
|
|
@@ -128,7 +95,7 @@ const GO_WRITE_FLAGS = new Set([
|
|
|
128
95
|
const CARGO_WRITE_FLAGS = new Set(["--target-dir", "--out-dir"]);
|
|
129
96
|
// tsc matches option names case-INSENSITIVELY (the compiler lowercases them via
|
|
130
97
|
// optionsNameMap), so `--outdir` == `--outDir`. Stored lowercase; matched
|
|
131
|
-
// against lowercased arg names
|
|
98
|
+
// against lowercased arg names.
|
|
132
99
|
const TSC_WRITE_FLAGS_LC = new Set([
|
|
133
100
|
"--generatecpuprofile",
|
|
134
101
|
"--generatetrace",
|
|
@@ -140,25 +107,22 @@ const TSC_WRITE_FLAGS_LC = new Set([
|
|
|
140
107
|
]);
|
|
141
108
|
// ruff --config accepts an inline `key=value` override (`--config 'fix=true'`
|
|
142
109
|
// flips ruff into file-rewriting mode) or an arbitrary config path — reject it
|
|
143
|
-
// alongside the direct write flags
|
|
110
|
+
// alongside the direct write flags.
|
|
144
111
|
const RUFF_WRITE_FLAGS = new Set(["--add-noqa", "--fix-only", "--cache-dir", "--output-file", "--config"]);
|
|
145
112
|
const ESLINT_WRITE_FLAGS = new Set(["--cache-location", "--output-file"]);
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
// project's eslintrc, not these CLI flags (Fable review 2026-07-17).
|
|
113
|
+
// ESLint loads and executes the module named by these flags, so an arbitrary
|
|
114
|
+
// parser/rules/plugin path is a code-execution escape. Normal linting uses the
|
|
115
|
+
// project's own config instead of these overrides.
|
|
150
116
|
const ESLINT_CODELOAD_FLAGS = new Set(["--parser", "--rulesdir", "--resolve-plugins-relative-to"]);
|
|
151
|
-
// mypy
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// `--cache-di`→--cache-dir); matched abbreviation-aware, not exact.
|
|
117
|
+
// By default mypy does not execute repo code, but config plugins are imported
|
|
118
|
+
// (the --config-file exception denied below). Reports/cache can target arbitrary
|
|
119
|
+
// paths and --install-types runs pip. Match argparse allow_abbrev prefixes.
|
|
155
120
|
const MYPY_WRITE_FLAGS_ABBREV = [
|
|
156
121
|
"--junit-xml",
|
|
157
122
|
"--cache-dir",
|
|
158
123
|
"--install-types",
|
|
159
|
-
// A mypy config file can
|
|
160
|
-
//
|
|
161
|
-
// (Fable review 2026-07-17). A reviewer uses the workspace mypy.ini.
|
|
124
|
+
// A mypy config file can import plugins, so an arbitrary --config-file is a
|
|
125
|
+
// code-execution escape; normal checks use the workspace mypy.ini.
|
|
162
126
|
"--config-file",
|
|
163
127
|
"--any-exprs-report",
|
|
164
128
|
"--cobertura-xml-report",
|
|
@@ -203,9 +167,8 @@ function matchesAbbreviatedFlag(args, flags) {
|
|
|
203
167
|
});
|
|
204
168
|
}
|
|
205
169
|
/**
|
|
206
|
-
*
|
|
207
|
-
* the
|
|
208
|
-
* historical "no harm done" defaults.
|
|
170
|
+
* Direct-read bypass for rescue. Keep this set identical to READ_ONLY_TOOLS in
|
|
171
|
+
* approval-policy.ts; the deliberate duplication must be updated in both places.
|
|
209
172
|
*/
|
|
210
173
|
const RESCUE_READ_ONLY_TOOLS = new Set([
|
|
211
174
|
"Read",
|
|
@@ -218,17 +181,9 @@ const RESCUE_READ_ONLY_TOOLS = new Set([
|
|
|
218
181
|
/**
|
|
219
182
|
* Evaluate a PreToolUse hook request under the rescue policy.
|
|
220
183
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* Inputs come from kimi-code's hook stdin:
|
|
226
|
-
* - `workspaceRoot`: the plugin-owned trusted root — the per-spawn
|
|
227
|
-
* `KIMI_PLUGIN_CC_WORKSPACE_ROOT` the hook entry passes through, NEVER the
|
|
228
|
-
* hook payload's top-level `cwd` (at kimi-code 0.42.0 that field is the
|
|
229
|
-
* process cwd, and the model cannot forge an env var on its own process)
|
|
230
|
-
* - `toolName`: e.g. `Bash`, `Write`, `Edit`, `MultiEdit`, etc.
|
|
231
|
-
* - `toolInput`: the raw arguments kimi will pass to the tool
|
|
184
|
+
* `workspaceRoot` is the plugin-owned KIMI_PLUGIN_CC_WORKSPACE_ROOT passed by
|
|
185
|
+
* the hook entry, never the payload cwd; the model cannot forge that per-spawn
|
|
186
|
+
* environment value. `toolName` and `toolInput` are the raw upstream tool call.
|
|
232
187
|
*/
|
|
233
188
|
export async function evaluateRescueHookRequest(workspaceRoot, toolName, toolInput) {
|
|
234
189
|
if (RESCUE_READ_ONLY_TOOLS.has(toolName)) {
|
|
@@ -326,38 +281,21 @@ function extractBashCommand(toolInput) {
|
|
|
326
281
|
function extractFilePath(toolInput) {
|
|
327
282
|
if (!isObject(toolInput))
|
|
328
283
|
return null;
|
|
329
|
-
// kimi-code
|
|
330
|
-
//
|
|
331
|
-
// z.object({ path: ... })) — NOT `file_path` (the Anthropic/Claude Code
|
|
332
|
-
// convention). The original `file_path`-only read meant EVERY real Write/Edit
|
|
333
|
-
// was denied with "no path field" (fail-closed but broken) for rescue/pursue/
|
|
334
|
-
// swarm-write — invisible to the unit tests, which all mock `file_path`, and
|
|
335
|
-
// only caught once the write-swarm real-binary smoke landed (v1.4.1). Prefer
|
|
336
|
-
// the kimi-code key; accept `file_path` too for forward/backward compat.
|
|
284
|
+
// kimi-code Write/Edit/MultiEdit call this field `path`, not Claude Code's
|
|
285
|
+
// `file_path`. Prefer the native key and accept the alias for compatibility.
|
|
337
286
|
const candidate = toolInput.path ?? toolInput.file_path;
|
|
338
287
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
|
|
339
288
|
}
|
|
340
289
|
function isObject(value) {
|
|
341
290
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
342
291
|
}
|
|
343
|
-
// ---------------------------------------------------------------------
|
|
344
|
-
// Internal helpers — verbatim from v0.4 rescue-approval.ts. The bug
|
|
345
|
-
// history that motivates each subtle check (e.g. git pre-subcommand
|
|
346
|
-
// flags, sed -i.bak suffix, shell-quote pipeline collapse, .git
|
|
347
|
-
// containment check) is documented inline and must NOT be removed.
|
|
348
|
-
// ---------------------------------------------------------------------
|
|
349
292
|
/**
|
|
350
|
-
* TOCTOU
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* Write tool (not available) or a recheck immediately before the
|
|
357
|
-
* write (also unavailable to plugin code). The practical mitigation
|
|
358
|
-
* is to trust the workspace's existing write controls — anything
|
|
359
|
-
* able to swap a symlink can already mutate workspace files directly,
|
|
360
|
-
* and rescue is intentionally write-capable.
|
|
293
|
+
* TOCTOU residual: this check runs before kimi-code writes. An attacker with
|
|
294
|
+
* workspace write access could replace the approved path with an escaping
|
|
295
|
+
* symlink before that write. The plugin cannot open-by-fd or recheck inside the
|
|
296
|
+
* Write tool, so the explicit trust boundary is the workspace's existing write
|
|
297
|
+
* controls: such an attacker can already mutate workspace files directly, and
|
|
298
|
+
* rescue is intentionally write-capable.
|
|
361
299
|
*/
|
|
362
300
|
export async function checkApprovedPath(workspaceRoot, rawTargetPath) {
|
|
363
301
|
const absoluteTarget = path.isAbsolute(rawTargetPath)
|
|
@@ -480,10 +418,9 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
480
418
|
reason: `Rescue rejects git pre-subcommand flags (e.g. -c/-C/-p/--no-pager/--exec-path): ${tokens.join(" ")}. Put the read-only subcommand immediately after \`git\`.`,
|
|
481
419
|
};
|
|
482
420
|
}
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
// `find -O<level>` so it is rejected here, git-locally.
|
|
421
|
+
// git grep -O/--open-files-in-pager executes its pager value. The long form
|
|
422
|
+
// is caught globally; reject -O here because find uses that spelling for a
|
|
423
|
+
// read-only optimization level.
|
|
487
424
|
if (args.some((arg) => arg === "-O" || /^-O./.test(arg))) {
|
|
488
425
|
return {
|
|
489
426
|
ok: false,
|
|
@@ -505,7 +442,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
505
442
|
if (command === "tsc") {
|
|
506
443
|
// tsc option names are case-INSENSITIVE (compiler lowercases them), so match
|
|
507
444
|
// lowercased. `--noemit`/`--noEmit` both suppress JS emit; `--outdir` is the
|
|
508
|
-
// same write flag as `--outDir
|
|
445
|
+
// same write flag as `--outDir`.
|
|
509
446
|
const tscLowerNames = args.map((arg) => {
|
|
510
447
|
const eq = arg.indexOf("=");
|
|
511
448
|
return (eq === -1 ? arg : arg.slice(0, eq)).toLowerCase();
|
|
@@ -514,7 +451,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
514
451
|
return { ok: false, reason: "Rescue allows tsc only with --noEmit." };
|
|
515
452
|
}
|
|
516
453
|
// --noEmit suppresses JS emit but NOT the profile/trace/buildinfo/declaration
|
|
517
|
-
// writers, which write to arbitrary paths
|
|
454
|
+
// writers, which write to arbitrary paths.
|
|
518
455
|
if (tscLowerNames.some((name) => TSC_WRITE_FLAGS_LC.has(name))) {
|
|
519
456
|
return {
|
|
520
457
|
ok: false,
|
|
@@ -527,9 +464,8 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
527
464
|
if (args[0] !== "check") {
|
|
528
465
|
return { ok: false, reason: "Rescue allows biome only in check mode." };
|
|
529
466
|
}
|
|
530
|
-
// biome check applies fixes
|
|
531
|
-
// -unsafe/-only suffixes
|
|
532
|
-
// (--apply is caught globally, --apply-unsafe is not). Verified 2026-07-17.
|
|
467
|
+
// biome check applies fixes for every --apply/--write/--fix variant,
|
|
468
|
+
// including -unsafe/-only suffixes missed by the global mutating-flag set.
|
|
533
469
|
if (args.some((arg) => /^--(apply|write|fix)/.test(arg))) {
|
|
534
470
|
return {
|
|
535
471
|
ok: false,
|
|
@@ -573,7 +509,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
573
509
|
return { ok: false, reason: `Rescue rejects cargo subcommand: ${tokens.join(" ")}` };
|
|
574
510
|
}
|
|
575
511
|
// --target-dir/--out-dir redirect the whole artifact tree to an arbitrary
|
|
576
|
-
// path outside the workspace
|
|
512
|
+
// path outside the workspace.
|
|
577
513
|
if (hasFlagFromSet(args, CARGO_WRITE_FLAGS)) {
|
|
578
514
|
return {
|
|
579
515
|
ok: false,
|
|
@@ -586,9 +522,8 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
586
522
|
if (!GO_SUBCOMMANDS.has(args[0] ?? "")) {
|
|
587
523
|
return { ok: false, reason: `Rescue rejects go subcommand: ${tokens.join(" ")}` };
|
|
588
524
|
}
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
// by the global exec-delegating check above.
|
|
525
|
+
// go build/test -o writes a binary to an arbitrary path. Exec-delegating
|
|
526
|
+
// -vettool/-exec/-toolexec values are already rejected globally.
|
|
592
527
|
if (hasWriteShortFlag(args)) {
|
|
593
528
|
return {
|
|
594
529
|
ok: false,
|
|
@@ -596,7 +531,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
596
531
|
};
|
|
597
532
|
}
|
|
598
533
|
// Profile/trace/output-dir flags on `go test`/`go build` write to arbitrary
|
|
599
|
-
// paths without `-o
|
|
534
|
+
// paths without `-o`.
|
|
600
535
|
if (hasFlagFromSet(args, GO_WRITE_FLAGS)) {
|
|
601
536
|
return {
|
|
602
537
|
ok: false,
|
|
@@ -621,15 +556,11 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
621
556
|
return validatePackageManagerCommand(command, args);
|
|
622
557
|
}
|
|
623
558
|
if (command === "eslint") {
|
|
624
|
-
// ESLint's
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
// (report 34 Codex HIGH) found this gap. `--fix` is also already
|
|
630
|
-
// in MUTATING_FLAGS_EXACT, so the eslint command surface is now
|
|
631
|
-
// read-only by construction.
|
|
632
|
-
// Match `-o` and the joined `-o<path>` spelling (eslint accepts both).
|
|
559
|
+
// Block ESLint's runner-owned report/cache paths. `-o` and joined
|
|
560
|
+
// `-o<path>` need a tool-specific check because rg/grep use `-o` read-only;
|
|
561
|
+
// the long --output-file form is already caught globally. These checks do
|
|
562
|
+
// not make linting read-only: project config and linter code still execute
|
|
563
|
+
// within rescue's repository trust boundary.
|
|
633
564
|
if (args.some((arg) => arg === "-o" || /^-o./.test(arg))) {
|
|
634
565
|
return {
|
|
635
566
|
ok: false,
|
|
@@ -637,7 +568,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
637
568
|
};
|
|
638
569
|
}
|
|
639
570
|
// --output-file (long form) is caught globally; --cache-location writes the
|
|
640
|
-
// cache tree to an arbitrary path
|
|
571
|
+
// cache tree to an arbitrary path.
|
|
641
572
|
if (hasFlagFromSet(args, ESLINT_WRITE_FLAGS)) {
|
|
642
573
|
return {
|
|
643
574
|
ok: false,
|
|
@@ -645,7 +576,7 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
645
576
|
};
|
|
646
577
|
}
|
|
647
578
|
// --parser/--rulesdir/--resolve-plugins-relative-to LOAD AND EXECUTE a module
|
|
648
|
-
// from the given path
|
|
579
|
+
// from the given path — a code-exec escape.
|
|
649
580
|
if (hasFlagFromSet(args, ESLINT_CODELOAD_FLAGS)) {
|
|
650
581
|
return {
|
|
651
582
|
ok: false,
|
|
@@ -655,10 +586,9 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
655
586
|
return { ok: true };
|
|
656
587
|
}
|
|
657
588
|
if (command === "mypy") {
|
|
658
|
-
// mypy
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
// (verified 2026-07-17).
|
|
589
|
+
// By default mypy does not execute repo code, but --config-file can import
|
|
590
|
+
// plugins and is denied below. Block report/cache/install outputs and their
|
|
591
|
+
// argparse allow_abbrev prefixes.
|
|
662
592
|
if (matchesAbbreviatedFlag(args, MYPY_WRITE_FLAGS_ABBREV)) {
|
|
663
593
|
return {
|
|
664
594
|
ok: false,
|
|
@@ -668,9 +598,8 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
668
598
|
return { ok: true };
|
|
669
599
|
}
|
|
670
600
|
if (command === "pytest") {
|
|
671
|
-
// pytest executes repo code by design
|
|
672
|
-
//
|
|
673
|
-
// outside the workspace.
|
|
601
|
+
// pytest executes repo code by design. Block its known runner-owned report
|
|
602
|
+
// flags, not every write that test code itself can perform.
|
|
674
603
|
if (hasPytestReportFlag(args)) {
|
|
675
604
|
return {
|
|
676
605
|
ok: false,
|
|
@@ -683,8 +612,8 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
683
612
|
return { ok: true };
|
|
684
613
|
}
|
|
685
614
|
if (pipelineMode && PIPELINE_PLUMBING.has(command)) {
|
|
686
|
-
//
|
|
687
|
-
//
|
|
615
|
+
// sort -o and uniq's second positional are output writes, unnecessary for
|
|
616
|
+
// pipeline stages that read stdin.
|
|
688
617
|
if (hasWriteShortFlag(args) || args.includes("--output")) {
|
|
689
618
|
return {
|
|
690
619
|
ok: false,
|
|
@@ -718,7 +647,6 @@ export function validateShellStage(tokens, pipelineMode) {
|
|
|
718
647
|
}
|
|
719
648
|
return { ok: false, reason: `Rescue rejects shell command: ${tokens.join(" ")}` };
|
|
720
649
|
}
|
|
721
|
-
/** Thrown by `throwingExpansionEnv` when the parser resolves a real variable. */
|
|
722
650
|
class ShellExpansionError extends Error {
|
|
723
651
|
}
|
|
724
652
|
/**
|
|
@@ -741,8 +669,7 @@ function throwingExpansionEnv(key) {
|
|
|
741
669
|
* smuggled flag from every per-flag check:
|
|
742
670
|
* - `${VAR:-flag}` collapsed to an empty token (no `:-`-default modelling), and
|
|
743
671
|
* - `$IFS`-gluing (`rg x$IFS--pre$IFS'sh'` → one token here, but bash splits it
|
|
744
|
-
* into `rg x --pre sh` — an RCE).
|
|
745
|
-
* missed the glued form because `$` was preceded by a letter, not whitespace.
|
|
672
|
+
* into `rg x --pre sh` — an RCE).
|
|
746
673
|
* A malformed substitution (`${}`, unterminated) throws a plain parser Error;
|
|
747
674
|
* that is itself unsafe to pass through, so any throw ⇒ unsafe (fail closed).
|
|
748
675
|
*/
|
|
@@ -854,14 +781,11 @@ function scanUnsafeUnquoted(command) {
|
|
|
854
781
|
return false;
|
|
855
782
|
}
|
|
856
783
|
/**
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
* command/process substitution, `$'…'`/`$"…"` openers, brace expansion) and
|
|
863
|
-
* `hasShellExpansion` (`$VAR`/`${…}` parameter expansion, incl. `$IFS`-gluing and
|
|
864
|
-
* `${VAR:-flag}`). Verified against a bypass + false-positive battery 2026-07-17.
|
|
784
|
+
* Syntax gate before the per-flag allowlist. Reject the expansion/substitution
|
|
785
|
+
* classes shell-quote does not model: backticks, command/process substitution,
|
|
786
|
+
* `$'…'`/`$"…"` openers, brace expansion, and `$VAR`/`${…}` parameter expansion
|
|
787
|
+
* including `$IFS` gluing and default-value forms. Two complementary quote-aware
|
|
788
|
+
* passes prevent these forms from smuggling flags or commands downstream.
|
|
865
789
|
*/
|
|
866
790
|
export function hasUnsafeShellSyntax(command) {
|
|
867
791
|
return scanUnsafeUnquoted(command) || hasShellExpansion(command);
|
|
@@ -883,20 +807,15 @@ export function hasMutatingFlag(args) {
|
|
|
883
807
|
}
|
|
884
808
|
return false;
|
|
885
809
|
}
|
|
886
|
-
/**
|
|
887
|
-
* True if any arg is a flag whose value is executed as a command/tool.
|
|
888
|
-
* Rejected for every command (report 43 F1/F3).
|
|
889
|
-
*/
|
|
810
|
+
/** True if any argument delegates execution to a command or external tool. */
|
|
890
811
|
export function hasExecDelegatingFlag(args) {
|
|
891
812
|
return args.some((arg) => EXEC_DELEGATING_FLAGS_EXACT.has(arg) ||
|
|
892
813
|
EXEC_DELEGATING_FLAG_PREFIXES.some((prefix) => arg.startsWith(prefix)));
|
|
893
814
|
}
|
|
894
815
|
/**
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
* per-tool where `-o` writes a file (report 43 F2/F3/F5). The long
|
|
899
|
-
* `--output*` forms are already covered by MUTATING_FLAG_PREFIXES.
|
|
816
|
+
* Per-tool check for `-o`, joined `-ofile`, and `-o=file` where that flag writes
|
|
817
|
+
* output (go, sort, ruff, and ESLint). It is not global because rg/grep use `-o`
|
|
818
|
+
* for read-only matching. Long --output forms are covered by the mutating set.
|
|
900
819
|
*/
|
|
901
820
|
export function hasWriteShortFlag(args) {
|
|
902
821
|
return args.some((arg) => arg === "-o" || /^-o./.test(arg));
|
|
@@ -906,7 +825,7 @@ export function hasWriteShortFlag(args) {
|
|
|
906
825
|
* cluster (`-rT DIR`, `-ro FILE`) the plain `-o` check misses. getopt consumes a
|
|
907
826
|
* cluster left to right; the first argument-taking option grabs the rest as its
|
|
908
827
|
* value, so an `o`/`T` AFTER a `-t`/`-k`/`-S` (sort's other arg-taking options)
|
|
909
|
-
* is that option's value, not a write flag.
|
|
828
|
+
* is that option's value, not a write flag.
|
|
910
829
|
*/
|
|
911
830
|
function sortShortClusterWrites(token) {
|
|
912
831
|
if (token.length < 2 || token[0] !== "-" || token[1] === "-")
|
|
@@ -924,15 +843,10 @@ function hasPytestReportFlag(args) {
|
|
|
924
843
|
return matchesAbbreviatedFlag(args, PYTEST_REPORT_FLAGS);
|
|
925
844
|
}
|
|
926
845
|
export function validatePackageManagerCommand(command, args) {
|
|
927
|
-
// Direct
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
// rejecting `test` would only cripple the expected surface without adding
|
|
932
|
-
// safety. `run <arbitrary-script>` below is different: it is a fully general
|
|
933
|
-
// script executor (dev/deploy/publish), not a test entry point, so it stays
|
|
934
|
-
// rejected. (Fable review 2026-07-17 flagged the `test`/`run` asymmetry — this
|
|
935
|
-
// is the deliberate resolution, not an oversight.)
|
|
846
|
+
// Direct <pm> test executes the repository's test script by design, like
|
|
847
|
+
// pytest/go test/cargo test, and remains inside the documented test-runner
|
|
848
|
+
// trust boundary. `run <arbitrary-script>` is instead a general script
|
|
849
|
+
// executor (dev/deploy/publish), so this deliberate test/run asymmetry stays.
|
|
936
850
|
if (args[0] === "test") {
|
|
937
851
|
return { ok: true };
|
|
938
852
|
}
|
|
@@ -2,16 +2,13 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { lstat, open, opendir, realpath } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { digestPrompt } from "./jobs.js";
|
|
5
|
-
// Total entries visited across both enumeration levels.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// predates the spawn cannot have been created by this run and are skipped
|
|
13
|
-
// before any read, so the byte budget scales with concurrent activity, not
|
|
14
|
-
// with store size.
|
|
5
|
+
// Total entries visited across both enumeration levels. The cap guards against
|
|
6
|
+
// a pathological tree rather than budgeting normal store size. opendir streams,
|
|
7
|
+
// so entries are not buffered. Dot-entries (`.index-dirty`, `.index-cache`) are
|
|
8
|
+
// upstream index state, never workspaces, and are skipped without being counted.
|
|
9
|
+
// Directories whose mtime predates the spawn cannot have been created by this
|
|
10
|
+
// run and are skipped before any read, so the byte budget scales with concurrent
|
|
11
|
+
// activity, not with store size.
|
|
15
12
|
const DIR_ENUM_CAP = 65_536;
|
|
16
13
|
const STATE_MAX_BYTES = 1024 * 1024;
|
|
17
14
|
const JOURNAL_PREFIX_BYTES = 4 * 1024 * 1024; // The prompt is argv-bounded (< 1 MiB); the first user message follows a few config records.
|