@mrciphersmith/keryx 0.3.41 → 0.3.43
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/README.md +10 -6
- package/dist/cli.js +231 -35
- package/package.json +1 -1
- package/src/gdskills/contracts/subagent-dispatch.schema.json +1 -1
package/README.md
CHANGED
|
@@ -487,7 +487,7 @@ What is in it today:
|
|
|
487
487
|
bounded parallel scheduling, and an offline fleet report over a recorded event
|
|
488
488
|
log (`keryx agents monitor <events-file>`).
|
|
489
489
|
- **Vendor CLIs as child agents — off by default.** keryx can hand a bounded,
|
|
490
|
-
**read-only** task (or, for `claude-cli`
|
|
490
|
+
**read-only** task (or, for `claude-cli` and `codex-cli`, a reviewed write — see below)
|
|
491
491
|
to a coding CLI you already have installed (`codex exec`,
|
|
492
492
|
`claude -p`, or Google's Antigravity CLI, `agy -p --output-format
|
|
493
493
|
stream-json`) and host it as a child of the same harness: a disposable git
|
|
@@ -509,16 +509,20 @@ What is in it today:
|
|
|
509
509
|
- **A write from an external agent you review before it lands.**
|
|
510
510
|
`keryx agents external run claude-cli --task "…" --write` runs `claude` in a
|
|
511
511
|
throwaway worktree with only `Read Grep Glob Edit Write` (no shell, no network,
|
|
512
|
-
no MCP)
|
|
512
|
+
no MCP); `codex-cli --write` runs `codex` in the same kind of worktree under an
|
|
513
|
+
OS sandbox (as measured: writes outside the worktree fail, a DNS lookup fails, `.git` read-only)
|
|
514
|
+
that still leaves it a shell able to read files your account can read, so review
|
|
515
|
+
its output too. Its diff is captured, secret-redacted, hashed and stored as a pending
|
|
513
516
|
review — nothing reaches your checkout. `keryx agents external review <run-id>`
|
|
514
517
|
shows it; `apply <run-id>` needs a real terminal, shows the diff and asks you to
|
|
515
518
|
type the first 12 hex digits of the patch hash, then creates a NEW local branch
|
|
516
519
|
`external/<run-id>` with one commit. Your current branch and working tree are
|
|
517
520
|
never touched, nothing is pushed and no pull request is opened;
|
|
518
521
|
`discard <run-id>` drops it. There is no flag that skips the confirmation. Only
|
|
519
|
-
`claude-cli`
|
|
520
|
-
review is a human reading the diff (a model review is not built), and
|
|
521
|
-
|
|
522
|
+
`claude-cli` and `codex-cli` can write (`antigravity-cli` refuses `--write`), the
|
|
523
|
+
review is a human reading the diff (a model review is not built), and neither
|
|
524
|
+
has a long live history (`codex` was verified in a scratch repository on
|
|
525
|
+
`codex` 0.159.2; versions outside 0.159.2 up to 0.160.0 are refused). In the TUI: `/external-diff` and a
|
|
522
526
|
sidebar row while a diff waits. See the [external agent write
|
|
523
527
|
guide](docs/docs/guides/external-agent-write.md).
|
|
524
528
|
- **Completion you can audit.** The completion gate blocks on missing evidence: a
|
|
@@ -948,7 +952,7 @@ untouched.
|
|
|
948
952
|
| No bundled embedding runtime | No semantic ranking in memory search | Lexical memory search remains fully available |
|
|
949
953
|
| ripgrep is external | `keryx ctx rg` needs `rg` on `PATH` | Install ripgrep, or let the agent read files directly |
|
|
950
954
|
| Model commands need a credential | Four of the five commands above exit non-zero without one; `wiki enrich` exits `0` and marks the affected pages skipped | Everything else runs deterministically offline |
|
|
951
|
-
| External agents are read-only, except `claude-cli` write mode, which
|
|
955
|
+
| External agents are read-only, except `claude-cli` and `codex-cli` write mode, which have no long live history | A delegated CLI can read and search; only `claude-cli` and `codex-cli` can write, and only as a stored diff a human reviews (`keryx agents external review\|apply\|discard`) that lands as a new local branch — never on your checkout, never pushed. `claude` is confined by a tool allow-list (no shell, no network); `codex` by an OS sandbox that still leaves it a shell that can read files your user can read (it cannot send them, but they can show up in the run output), and is refused when older than 0.159.0 — live-verified in a scratch repository on `codex` 0.159.0. `antigravity-cli` refuses `--write`. A model review is not built. The parent gets the child's result and nothing before it — supervision of a running external child is not implemented. `claude` and `agy` read-only runs are live-verified | Use keryx's own child agents for work that must mutate the tree, or the [external agent write guide](docs/docs/guides/external-agent-write.md) for a reviewed `claude-cli` or `codex-cli` diff |
|
|
952
956
|
| A dispatched `flow-next` task is "done" by checks, not review | `task done` means normal end + a commit on the trigger branch + `keryx health gate` passing in the worktree; nobody has read the diff | Review and merge the `trigger/<flow>-<task>` branch yourself |
|
|
953
957
|
| `trust` dispatch needs Linux + a working bubblewrap | The hardened unattended sandbox (network off, home hidden, allow-listed env) is bwrap-only; without it — or on macOS — a `trust` dispatch refuses before starting | Use `permissionMode: "ask"` (read-only), or run triggers on a Linux host with bwrap |
|
|
954
958
|
| `dispatch.network: true` is the host's full network | The agent's commands then reach the internet and every host loopback service; the model call never needs it (it is made outside the sandbox) | Leave `network` off; review the `trigger/*` branch before installing or building it |
|
package/dist/cli.js
CHANGED
|
@@ -14119,7 +14119,7 @@ var package_default;
|
|
|
14119
14119
|
var init_package = __esm(() => {
|
|
14120
14120
|
package_default = {
|
|
14121
14121
|
name: "@mrciphersmith/keryx",
|
|
14122
|
-
version: "0.3.
|
|
14122
|
+
version: "0.3.43",
|
|
14123
14123
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
14124
14124
|
private: false,
|
|
14125
14125
|
publishConfig: {
|
|
@@ -51368,13 +51368,15 @@ permission bridge.
|
|
|
51368
51368
|
A line-stream agent (\`claude-cli\`, \`codex-cli\`) is a DIFFERENT thing:
|
|
51369
51369
|
\`run\` refuses it outright ("speaks the one-way line stream. Delegate to it
|
|
51370
51370
|
from \`keryx shell\` with /delegate"). There is no ACP permission bridge and
|
|
51371
|
-
no patch capture for it
|
|
51372
|
-
ACP transport
|
|
51373
|
-
|
|
51374
|
-
|
|
51375
|
-
\`runtime\` block, and
|
|
51376
|
-
|
|
51377
|
-
|
|
51371
|
+
no patch capture for it in those paths \u2014 \`worktree-write\` is implemented for
|
|
51372
|
+
the ACP transport, and for \`claude-cli\` and \`codex-cli\` only through the
|
|
51373
|
+
operator's own \`keryx agents external run <id> --write\`, which captures the
|
|
51374
|
+
diff for human review. A dispatch that asks for \`worktree-write\` on a
|
|
51375
|
+
line-stream agent anywhere else (the internal \`runtime\` block, and
|
|
51376
|
+
\`keryx shell\`'s /delegate) is REFUSED fail-closed with a distinguishable
|
|
51377
|
+
\`not-implemented\` reason \u2014 never silently accepted and its writes
|
|
51378
|
+
discarded. There it stays \`read-only\`, contained by the same disposable
|
|
51379
|
+
worktree as every external run.
|
|
51378
51380
|
|
|
51379
51381
|
See \`docs/docs/guides/acp-client.md\` for the full ACP contract, including the
|
|
51380
51382
|
output size bounds (a stderr-only flood and an output flood both end the run
|
|
@@ -85139,14 +85141,14 @@ var init_registry3 = __esm(() => {
|
|
|
85139
85141
|
label: "Codex",
|
|
85140
85142
|
binary: "codex",
|
|
85141
85143
|
detect: ["--version"],
|
|
85142
|
-
versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+)",
|
|
85144
|
+
versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.]+)?)",
|
|
85143
85145
|
knownGoodRange: { min: "0.147.0" },
|
|
85144
85146
|
sandboxModes: ["read-only", "worktree-write"],
|
|
85145
85147
|
streamingInput: false,
|
|
85146
85148
|
resumable: true,
|
|
85147
85149
|
reportsCost: false,
|
|
85148
85150
|
budgetFlag: false,
|
|
85149
|
-
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so
|
|
85151
|
+
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so a read-only resume inherits " + "its sandbox, a write run re-asserts its confinement as `-c` overrides, and it must be " + "spawned with cwd already set to the worktree. Write mode needs codex 0.159.2 up to, but not including, 0.160.0."
|
|
85150
85152
|
},
|
|
85151
85153
|
{
|
|
85152
85154
|
id: "claude-cli",
|
|
@@ -85196,10 +85198,27 @@ var init_registry3 = __esm(() => {
|
|
|
85196
85198
|
});
|
|
85197
85199
|
|
|
85198
85200
|
// src/harness/external/dispatch.ts
|
|
85201
|
+
function codecWriteVersionRefusal(entry, version) {
|
|
85202
|
+
const verified = CODEC_WRITE_VERIFIED_VERSIONS[entry.id];
|
|
85203
|
+
if (verified === undefined)
|
|
85204
|
+
return;
|
|
85205
|
+
const range = `\`${entry.binary}\` ${verified.min} up to, but not including, ${verified.before}`;
|
|
85206
|
+
const prefix = `${entry.label} write mode needs ${range}, the versions whose sandbox confinement was measured; `;
|
|
85207
|
+
if (version === undefined)
|
|
85208
|
+
return `${prefix}the installed version could not be read`;
|
|
85209
|
+
if (!/^\d+\.\d+\.\d+$/.test(version))
|
|
85210
|
+
return `${prefix}found ${version}, a pre-release`;
|
|
85211
|
+
if (compareVersions(version, verified.min) < 0)
|
|
85212
|
+
return `${prefix}found ${version}, which is older`;
|
|
85213
|
+
if (compareVersions(version, verified.before) >= 0) {
|
|
85214
|
+
return `${prefix}found ${version}, which is newer and not yet verified: update keryx, or use \`--task\` without \`--write\``;
|
|
85215
|
+
}
|
|
85216
|
+
return;
|
|
85217
|
+
}
|
|
85199
85218
|
function implementedSandboxModesFor(entry, options = {}) {
|
|
85200
85219
|
if (transportOf(entry) === "acp")
|
|
85201
85220
|
return IMPLEMENTED_ACP_SANDBOX_MODES;
|
|
85202
|
-
return entry.id
|
|
85221
|
+
return CODEC_WRITE_AGENT_IDS.includes(entry.id) && options.ownsWriteCapture === true ? IMPLEMENTED_ACP_SANDBOX_MODES : IMPLEMENTED_SANDBOX_MODES;
|
|
85203
85222
|
}
|
|
85204
85223
|
function validateRuntimeBlock(runtime, allowedActions, options = {}) {
|
|
85205
85224
|
if (runtime === undefined || runtime.kind === "keryx") {
|
|
@@ -85241,7 +85260,7 @@ function validateRuntimeBlock(runtime, allowedActions, options = {}) {
|
|
|
85241
85260
|
return {
|
|
85242
85261
|
ok: false,
|
|
85243
85262
|
code: "not-implemented",
|
|
85244
|
-
reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is
|
|
85263
|
+
reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is available for ${CODEC_WRITE_AGENT_IDS.join(", ")} only` + (entry.id === "antigravity-cli" ? ": antigravity-cli's edit tool writes outside the worktree" : "") : "")
|
|
85245
85264
|
};
|
|
85246
85265
|
}
|
|
85247
85266
|
if (sandbox === "read-only") {
|
|
@@ -85267,12 +85286,16 @@ function readRuntimeBlock(dispatch) {
|
|
|
85267
85286
|
return;
|
|
85268
85287
|
return raw;
|
|
85269
85288
|
}
|
|
85270
|
-
var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES,
|
|
85289
|
+
var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES, CODEC_WRITE_AGENT_IDS, CODEC_WRITE_VERIFIED_VERSIONS;
|
|
85271
85290
|
var init_dispatch = __esm(() => {
|
|
85272
85291
|
init_registry3();
|
|
85273
85292
|
READ_ONLY_FORBIDDEN_ACTIONS = ["write", "network", "spawn-subagent"];
|
|
85274
85293
|
IMPLEMENTED_SANDBOX_MODES = ["read-only"];
|
|
85275
85294
|
IMPLEMENTED_ACP_SANDBOX_MODES = ["read-only", "worktree-write"];
|
|
85295
|
+
CODEC_WRITE_AGENT_IDS = ["claude-cli", "codex-cli"];
|
|
85296
|
+
CODEC_WRITE_VERIFIED_VERSIONS = {
|
|
85297
|
+
"codex-cli": { min: "0.159.2", before: "0.160.0" }
|
|
85298
|
+
};
|
|
85276
85299
|
});
|
|
85277
85300
|
|
|
85278
85301
|
// src/harness/external/env-deny.ts
|
|
@@ -85691,6 +85714,127 @@ var init_contracts = __esm(() => {
|
|
|
85691
85714
|
];
|
|
85692
85715
|
});
|
|
85693
85716
|
|
|
85717
|
+
// src/harness/external/strict-schema.ts
|
|
85718
|
+
function isSchema(value) {
|
|
85719
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
85720
|
+
}
|
|
85721
|
+
function mapSchemas(value) {
|
|
85722
|
+
const out = {};
|
|
85723
|
+
if (!isSchema(value))
|
|
85724
|
+
return out;
|
|
85725
|
+
for (const [key, child] of Object.entries(value))
|
|
85726
|
+
out[key] = toStrictNode(child);
|
|
85727
|
+
return out;
|
|
85728
|
+
}
|
|
85729
|
+
function makeNullable(schema) {
|
|
85730
|
+
const type = schema.type;
|
|
85731
|
+
if (typeof type === "string") {
|
|
85732
|
+
if (type === "null")
|
|
85733
|
+
return schema;
|
|
85734
|
+
const out = { ...schema, type: [type, "null"] };
|
|
85735
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(null))
|
|
85736
|
+
out.enum = [...schema.enum, null];
|
|
85737
|
+
return out;
|
|
85738
|
+
}
|
|
85739
|
+
if (Array.isArray(type)) {
|
|
85740
|
+
if (type.includes("null")) {
|
|
85741
|
+
const out = { ...schema };
|
|
85742
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(null))
|
|
85743
|
+
out.enum = [...schema.enum, null];
|
|
85744
|
+
return out;
|
|
85745
|
+
}
|
|
85746
|
+
const out = { ...schema, type: [...type, "null"] };
|
|
85747
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(null))
|
|
85748
|
+
out.enum = [...schema.enum, null];
|
|
85749
|
+
return out;
|
|
85750
|
+
}
|
|
85751
|
+
if (Array.isArray(schema.enum) && schema.anyOf === undefined) {
|
|
85752
|
+
return schema.enum.includes(null) ? schema : { ...schema, enum: [...schema.enum, null] };
|
|
85753
|
+
}
|
|
85754
|
+
if (Array.isArray(schema.anyOf)) {
|
|
85755
|
+
return { ...schema, anyOf: [...schema.anyOf, NULL_SCHEMA] };
|
|
85756
|
+
}
|
|
85757
|
+
return { anyOf: [schema, NULL_SCHEMA] };
|
|
85758
|
+
}
|
|
85759
|
+
function toStrictNode(node) {
|
|
85760
|
+
if (!isSchema(node))
|
|
85761
|
+
return {};
|
|
85762
|
+
const out = {};
|
|
85763
|
+
if (node.type !== undefined)
|
|
85764
|
+
out.type = node.type;
|
|
85765
|
+
if (Array.isArray(node.enum))
|
|
85766
|
+
out.enum = [...node.enum];
|
|
85767
|
+
else if ("const" in node)
|
|
85768
|
+
out.enum = [node.const];
|
|
85769
|
+
if (typeof node.$ref === "string")
|
|
85770
|
+
out.$ref = node.$ref;
|
|
85771
|
+
const union = node.anyOf ?? node.oneOf;
|
|
85772
|
+
if (Array.isArray(union))
|
|
85773
|
+
out.anyOf = union.map(toStrictNode);
|
|
85774
|
+
if (isSchema(node.items))
|
|
85775
|
+
out.items = toStrictNode(node.items);
|
|
85776
|
+
if (isSchema(node.$defs))
|
|
85777
|
+
out.$defs = mapSchemas(node.$defs);
|
|
85778
|
+
const isObject = node.type === "object" || isSchema(node.properties);
|
|
85779
|
+
if (isObject) {
|
|
85780
|
+
const required = new Set(Array.isArray(node.required) ? node.required : []);
|
|
85781
|
+
const properties = {};
|
|
85782
|
+
for (const [key, child] of Object.entries(isSchema(node.properties) ? node.properties : {})) {
|
|
85783
|
+
const strict = toStrictNode(child);
|
|
85784
|
+
properties[key] = required.has(key) ? strict : makeNullable(strict);
|
|
85785
|
+
}
|
|
85786
|
+
out.type ??= "object";
|
|
85787
|
+
out.properties = properties;
|
|
85788
|
+
out.required = Object.keys(properties);
|
|
85789
|
+
out.additionalProperties = false;
|
|
85790
|
+
}
|
|
85791
|
+
return out;
|
|
85792
|
+
}
|
|
85793
|
+
function toStrictOutputSchema(bundled) {
|
|
85794
|
+
return toStrictNode(bundled);
|
|
85795
|
+
}
|
|
85796
|
+
function resolveRef3(node, root) {
|
|
85797
|
+
let current = node;
|
|
85798
|
+
for (let hops = 0;typeof current.$ref === "string" && hops < 16; hops += 1) {
|
|
85799
|
+
const ref = current.$ref;
|
|
85800
|
+
if (!ref.startsWith("#/"))
|
|
85801
|
+
return current;
|
|
85802
|
+
let target = root;
|
|
85803
|
+
for (const part of ref.slice(2).split("/")) {
|
|
85804
|
+
target = isSchema(target) ? target[decodeURIComponent(part)] : undefined;
|
|
85805
|
+
}
|
|
85806
|
+
if (!isSchema(target))
|
|
85807
|
+
return current;
|
|
85808
|
+
current = target;
|
|
85809
|
+
}
|
|
85810
|
+
return current;
|
|
85811
|
+
}
|
|
85812
|
+
function dropNulls(value, node, root) {
|
|
85813
|
+
if (!isSchema(node))
|
|
85814
|
+
return value;
|
|
85815
|
+
const schema = resolveRef3(node, root);
|
|
85816
|
+
if (Array.isArray(value)) {
|
|
85817
|
+
return isSchema(schema.items) ? value.map((item) => dropNulls(item, schema.items, root)) : value;
|
|
85818
|
+
}
|
|
85819
|
+
if (!isSchema(value) || !isSchema(schema.properties))
|
|
85820
|
+
return value;
|
|
85821
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
85822
|
+
const out = {};
|
|
85823
|
+
for (const [key, child] of Object.entries(value)) {
|
|
85824
|
+
if (child === null && !required.has(key) && key in schema.properties)
|
|
85825
|
+
continue;
|
|
85826
|
+
out[key] = dropNulls(child, schema.properties[key], root);
|
|
85827
|
+
}
|
|
85828
|
+
return out;
|
|
85829
|
+
}
|
|
85830
|
+
function dropOptionalNulls(value, bundled) {
|
|
85831
|
+
return isSchema(bundled) ? dropNulls(value, bundled, bundled) : value;
|
|
85832
|
+
}
|
|
85833
|
+
var NULL_SCHEMA;
|
|
85834
|
+
var init_strict_schema = __esm(() => {
|
|
85835
|
+
NULL_SCHEMA = { type: "null" };
|
|
85836
|
+
});
|
|
85837
|
+
|
|
85694
85838
|
// src/harness/external/prompt.ts
|
|
85695
85839
|
function byteLength(text) {
|
|
85696
85840
|
return Buffer.byteLength(text, "utf8");
|
|
@@ -85806,7 +85950,7 @@ var init_prompt2 = __esm(() => {
|
|
|
85806
85950
|
EXTERNAL_WRITE_RUNTIME_DIRECTIVE = [
|
|
85807
85951
|
"# Runtime directive",
|
|
85808
85952
|
"",
|
|
85809
|
-
"You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using
|
|
85953
|
+
"You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using your file reading, searching and editing tools. Do not use a shell to reach " + "outside it. Do not touch .git, .github, .claude or .metaproject. Your changes are captured as a patch that a " + "human reviews before anything lands, so summarise what you changed in the final message."
|
|
85810
85954
|
].join(`
|
|
85811
85955
|
`);
|
|
85812
85956
|
DIFF_INTRO_COMPLETE = "The operator's uncommitted changes are below. Your worktree is checked out at HEAD and " + "does not contain them, so this diff is the only place they exist for you.";
|
|
@@ -87638,6 +87782,7 @@ async function superviseExternalRun(input, deps) {
|
|
|
87638
87782
|
if (verdict === "drained") {
|
|
87639
87783
|
return build(reportedExitCode ?? exitCodeFromEvents(events), false);
|
|
87640
87784
|
}
|
|
87785
|
+
const exitedBeforeKill = reportedExitCode;
|
|
87641
87786
|
handle.kill();
|
|
87642
87787
|
const grace = afterMs2(input.killGraceMs ?? DEFAULT_KILL_GRACE_MS, undefined);
|
|
87643
87788
|
const code = await Promise.race([exited, grace.promise]);
|
|
@@ -87645,7 +87790,7 @@ async function superviseExternalRun(input, deps) {
|
|
|
87645
87790
|
if (verdict === "timeout") {
|
|
87646
87791
|
return build(code ?? EXTERNAL_TIMEOUT_EXIT_CODE, true);
|
|
87647
87792
|
}
|
|
87648
|
-
return build(
|
|
87793
|
+
return build(exitedBeforeKill ?? exitCodeFromEvents(events), false);
|
|
87649
87794
|
}
|
|
87650
87795
|
function deferred() {
|
|
87651
87796
|
let resolve = () => {
|
|
@@ -87714,7 +87859,7 @@ function describeError(error) {
|
|
|
87714
87859
|
return "unknown error";
|
|
87715
87860
|
}
|
|
87716
87861
|
}
|
|
87717
|
-
var EXTERNAL_TIMEOUT_EXIT_CODE = 124, STDOUT_HEAD_BYTES, STDOUT_TAIL_BYTES, DEFAULT_KILL_GRACE_MS = 2000, DEFAULT_TERMINAL_SETTLE_MS = 2000, SUPERVISION_TICK_DIVISOR = 4, SUPERVISION_TICK_FLOOR_MS = 5;
|
|
87862
|
+
var EXTERNAL_TIMEOUT_EXIT_CODE = 124, STDOUT_HEAD_BYTES, STDOUT_TAIL_BYTES, DEFAULT_KILL_GRACE_MS = 2000, DEFAULT_TERMINAL_SETTLE_MS = 2000, CODEX_TERMINAL_SETTLE_MS = 1e4, SUPERVISION_TICK_DIVISOR = 4, SUPERVISION_TICK_FLOOR_MS = 5;
|
|
87718
87863
|
var init_supervise = __esm(() => {
|
|
87719
87864
|
init_bounded();
|
|
87720
87865
|
init_bun_spawn_port();
|
|
@@ -88338,6 +88483,8 @@ function buildCodexArgv(input) {
|
|
|
88338
88483
|
"--ignore-user-config",
|
|
88339
88484
|
"--skip-git-repo-check"
|
|
88340
88485
|
];
|
|
88486
|
+
if (input.sandbox === "worktree-write")
|
|
88487
|
+
argv.push(...CODEX_WRITE_CONFINEMENT_FLAGS);
|
|
88341
88488
|
if (input.resultSchemaPath !== undefined)
|
|
88342
88489
|
argv.push("--output-schema", input.resultSchemaPath);
|
|
88343
88490
|
if (input.model !== undefined)
|
|
@@ -88345,8 +88492,9 @@ function buildCodexArgv(input) {
|
|
|
88345
88492
|
argv.push(input.prompt);
|
|
88346
88493
|
return argv;
|
|
88347
88494
|
}
|
|
88348
|
-
function buildCodexResumeArgv(sessionRef, message) {
|
|
88349
|
-
|
|
88495
|
+
function buildCodexResumeArgv(sessionRef, message, input) {
|
|
88496
|
+
const confinement = input?.sandbox === "worktree-write" ? ["-c", `sandbox_mode="${CODEX_SANDBOX_MODES["worktree-write"]}"`, ...CODEX_WRITE_CONFINEMENT_FLAGS] : [];
|
|
88497
|
+
return ["codex", "exec", "resume", sessionRef, "--json", "--ignore-user-config", "--skip-git-repo-check", ...confinement, message];
|
|
88350
88498
|
}
|
|
88351
88499
|
function parseCodexEvents(line) {
|
|
88352
88500
|
const record = readJsonObject2(line);
|
|
@@ -88501,12 +88649,21 @@ function brief2(text, max = 200) {
|
|
|
88501
88649
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
88502
88650
|
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
88503
88651
|
}
|
|
88504
|
-
var CODEX_SANDBOX_MODES, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
|
|
88652
|
+
var CODEX_SANDBOX_MODES, CODEX_WRITE_CONFINEMENT_FLAGS, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
|
|
88505
88653
|
var init_codex_cli = __esm(() => {
|
|
88506
88654
|
CODEX_SANDBOX_MODES = {
|
|
88507
88655
|
"read-only": "read-only",
|
|
88508
88656
|
"worktree-write": "workspace-write"
|
|
88509
88657
|
};
|
|
88658
|
+
CODEX_WRITE_CONFINEMENT_FLAGS = [
|
|
88659
|
+
"--ignore-rules",
|
|
88660
|
+
"-c",
|
|
88661
|
+
"sandbox_workspace_write.exclude_slash_tmp=true",
|
|
88662
|
+
"-c",
|
|
88663
|
+
"sandbox_workspace_write.exclude_tmpdir_env_var=true",
|
|
88664
|
+
"-c",
|
|
88665
|
+
"sandbox_workspace_write.network_access=false"
|
|
88666
|
+
];
|
|
88510
88667
|
RECOGNISED_TYPES = new Set([
|
|
88511
88668
|
"thread.started",
|
|
88512
88669
|
"turn.started",
|
|
@@ -88536,7 +88693,7 @@ var init_codex_cli = __esm(() => {
|
|
|
88536
88693
|
parseLine: parseCodexLine,
|
|
88537
88694
|
parseEvents: parseCodexEvents,
|
|
88538
88695
|
classifyFailure: classifyCodexFailure,
|
|
88539
|
-
buildResumeArgv:
|
|
88696
|
+
buildResumeArgv: buildCodexResumeArgv,
|
|
88540
88697
|
isRecognisedLine: isRecognisedCodexLine
|
|
88541
88698
|
};
|
|
88542
88699
|
});
|
|
@@ -88650,20 +88807,42 @@ async function runExternalChild(input, deps) {
|
|
|
88650
88807
|
if (!isAcp && codec === undefined) {
|
|
88651
88808
|
return refuse2("Error", `no codec is registered for external agent "${entry.id}"`);
|
|
88652
88809
|
}
|
|
88810
|
+
let detectedVersion;
|
|
88653
88811
|
if (deps.detect !== undefined) {
|
|
88654
88812
|
const availability = resolveAvailability(entry, await deps.detect(entry.binary, entry.detect));
|
|
88655
88813
|
if (availability.state === "binary-missing") {
|
|
88656
88814
|
return refuse2("Denied", `${entry.label} is not installed (\`${entry.binary}\` not on PATH)`);
|
|
88657
88815
|
}
|
|
88816
|
+
if (availability.state === "available")
|
|
88817
|
+
detectedVersion = availability.version;
|
|
88658
88818
|
if (availability.state === "available" && availability.verdict.state !== "in-range") {
|
|
88659
88819
|
deps.onWarning?.(`${entry.label} version ${availability.version ?? "unknown"} is outside the range this build's fixtures were recorded against`);
|
|
88660
88820
|
}
|
|
88661
88821
|
}
|
|
88822
|
+
const versionRefusal = sandbox === "worktree-write" ? codecWriteVersionRefusal(entry, detectedVersion) : undefined;
|
|
88823
|
+
if (versionRefusal !== undefined)
|
|
88824
|
+
return refuse2("Denied", versionRefusal);
|
|
88662
88825
|
const schemaPrep = await prepareResultSchema();
|
|
88663
88826
|
if (!schemaPrep.ok)
|
|
88664
88827
|
return refuse2("Error", schemaPrep.reason);
|
|
88665
88828
|
const { schema: resultSchema, schemaText: resultSchemaText, schemaPath: resultSchemaPath, schemaDir } = schemaPrep;
|
|
88666
88829
|
const resultSchemaInline = await prepareInlineSchema(resultSchema);
|
|
88830
|
+
const bundledResultSchema = JSON.parse(resultSchemaInline);
|
|
88831
|
+
const codexStrict = entry.id === "codex-cli";
|
|
88832
|
+
let resultSchemaPathForCodec = resultSchemaPath;
|
|
88833
|
+
if (codexStrict) {
|
|
88834
|
+
resultSchemaPathForCodec = path208.join(schemaDir, STRICT_SCHEMA_FILE);
|
|
88835
|
+
try {
|
|
88836
|
+
await writeFile32(resultSchemaPathForCodec, JSON.stringify(toStrictOutputSchema(bundledResultSchema)), "utf8");
|
|
88837
|
+
} catch (error) {
|
|
88838
|
+
await rm15(schemaDir, { recursive: true, force: true }).catch(() => {
|
|
88839
|
+
return;
|
|
88840
|
+
});
|
|
88841
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88842
|
+
return refuse2("Error", `failed to prepare the required result schema for validation: ${message}`);
|
|
88843
|
+
}
|
|
88844
|
+
}
|
|
88845
|
+
const normalize = codexStrict ? (value) => dropOptionalNulls(value, bundledResultSchema) : undefined;
|
|
88667
88846
|
try {
|
|
88668
88847
|
const assembled = buildExternalPrompt({
|
|
88669
88848
|
taskTitle: input.taskTitle,
|
|
@@ -88690,7 +88869,7 @@ async function runExternalChild(input, deps) {
|
|
|
88690
88869
|
write: sandbox === "worktree-write",
|
|
88691
88870
|
worktreePath: created.path,
|
|
88692
88871
|
prompt: assembled.prompt,
|
|
88693
|
-
validate: (built) => validateStructuredResult(built, resultSchema)
|
|
88872
|
+
validate: (built) => validateStructuredResult(built, resultSchema, normalize)
|
|
88694
88873
|
});
|
|
88695
88874
|
}
|
|
88696
88875
|
const runInput = {
|
|
@@ -88700,7 +88879,7 @@ async function runExternalChild(input, deps) {
|
|
|
88700
88879
|
...input.runtime.model === undefined || input.runtime.model === null ? {} : { model: input.runtime.model },
|
|
88701
88880
|
...input.sessionId === undefined ? {} : { sessionId: input.sessionId },
|
|
88702
88881
|
...input.runtime.maxCostUnits === undefined || input.runtime.maxCostUnits === null ? {} : { maxCostUnits: input.runtime.maxCostUnits },
|
|
88703
|
-
resultSchemaPath,
|
|
88882
|
+
resultSchemaPath: resultSchemaPathForCodec,
|
|
88704
88883
|
resultSchema: resultSchemaInline,
|
|
88705
88884
|
printTimeoutSeconds: Math.ceil(input.timeoutMs / 1000)
|
|
88706
88885
|
};
|
|
@@ -88721,6 +88900,7 @@ async function runExternalChild(input, deps) {
|
|
|
88721
88900
|
timeoutMs: input.timeoutMs,
|
|
88722
88901
|
...entry.maxStderrBytes === undefined ? {} : { maxStderrBytes: entry.maxStderrBytes },
|
|
88723
88902
|
stdin: streaming ? "pipe" : "ignore",
|
|
88903
|
+
...entry.id === "codex-cli" ? { terminalSettleMs: CODEX_TERMINAL_SETTLE_MS } : {},
|
|
88724
88904
|
...streaming ? {
|
|
88725
88905
|
initialStdin: [
|
|
88726
88906
|
codec.encodeStdinMessage(assembled.prompt)
|
|
@@ -88736,8 +88916,14 @@ async function runExternalChild(input, deps) {
|
|
|
88736
88916
|
...deps.onSupervisionTrigger === undefined ? {} : { onSupervisionTrigger: deps.onSupervisionTrigger }
|
|
88737
88917
|
});
|
|
88738
88918
|
const cause = outcome.overflow ?? codec.classifyFailure(outcome);
|
|
88739
|
-
const built = buildOutcome({
|
|
88740
|
-
|
|
88919
|
+
const built = buildOutcome({
|
|
88920
|
+
cause,
|
|
88921
|
+
outcome,
|
|
88922
|
+
argv,
|
|
88923
|
+
worktreePath: created.path,
|
|
88924
|
+
finalMessageOnly: entry.id === "codex-cli"
|
|
88925
|
+
});
|
|
88926
|
+
return await validateStructuredResult(built, resultSchema, normalize);
|
|
88741
88927
|
} finally {
|
|
88742
88928
|
await deps.worktree.remove(input.worktreeId).catch(() => {
|
|
88743
88929
|
return;
|
|
@@ -88749,7 +88935,7 @@ async function runExternalChild(input, deps) {
|
|
|
88749
88935
|
});
|
|
88750
88936
|
}
|
|
88751
88937
|
}
|
|
88752
|
-
async function validateStructuredResult(built, resultSchema) {
|
|
88938
|
+
async function validateStructuredResult(built, resultSchema, normalize) {
|
|
88753
88939
|
if (built.status !== "Completed")
|
|
88754
88940
|
return built;
|
|
88755
88941
|
let parsed;
|
|
@@ -88759,12 +88945,13 @@ async function validateStructuredResult(built, resultSchema) {
|
|
|
88759
88945
|
const message = error instanceof Error ? error.message : String(error);
|
|
88760
88946
|
return structuredResultError(`structured result is not valid JSON: ${message}`, built);
|
|
88761
88947
|
}
|
|
88762
|
-
const
|
|
88948
|
+
const normalized = normalize === undefined ? parsed : normalize(parsed);
|
|
88949
|
+
const validationErrors = await validateJson(normalized, resultSchema);
|
|
88763
88950
|
if (validationErrors.length > 0) {
|
|
88764
88951
|
const detail = validationErrors.slice(0, 3).map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
88765
88952
|
return structuredResultError(`structured result failed subagent-result schema validation: ${detail}`, built);
|
|
88766
88953
|
}
|
|
88767
|
-
return built;
|
|
88954
|
+
return normalized === parsed ? built : { ...built, output: JSON.stringify(normalized) };
|
|
88768
88955
|
}
|
|
88769
88956
|
async function runAcpBranch(args) {
|
|
88770
88957
|
const { input, deps, entry, worktreePath } = args;
|
|
@@ -88824,10 +89011,10 @@ async function runAcpBranch(args) {
|
|
|
88824
89011
|
return { ...final, acp: record };
|
|
88825
89012
|
}
|
|
88826
89013
|
function buildOutcome(args) {
|
|
88827
|
-
const { cause, outcome, argv, worktreePath } = args;
|
|
89014
|
+
const { cause, outcome, argv, worktreePath, finalMessageOnly } = args;
|
|
88828
89015
|
const sessionRef = findSessionRef(outcome.events);
|
|
88829
89016
|
const costUnits = findCostUnits(outcome.events);
|
|
88830
|
-
const text = collectAssistantText(outcome.events);
|
|
89017
|
+
const text = collectAssistantText(outcome.events, finalMessageOnly);
|
|
88831
89018
|
const base = {
|
|
88832
89019
|
argv,
|
|
88833
89020
|
worktreePath,
|
|
@@ -88862,13 +89049,21 @@ function findCostUnits(events) {
|
|
|
88862
89049
|
}
|
|
88863
89050
|
return cost;
|
|
88864
89051
|
}
|
|
88865
|
-
function collectAssistantText(events) {
|
|
89052
|
+
function collectAssistantText(events, finalMessageOnly) {
|
|
88866
89053
|
for (let i = events.length - 1;i >= 0; i -= 1) {
|
|
88867
89054
|
const event = events[i];
|
|
88868
89055
|
if (event?.kind === "child_finished" && event.text !== undefined && event.text.trim().length > 0) {
|
|
88869
89056
|
return event.text.trim();
|
|
88870
89057
|
}
|
|
88871
89058
|
}
|
|
89059
|
+
if (finalMessageOnly) {
|
|
89060
|
+
for (let i = events.length - 1;i >= 0; i -= 1) {
|
|
89061
|
+
const event = events[i];
|
|
89062
|
+
if (event?.kind === "assistant_text" && event.text.trim().length > 0)
|
|
89063
|
+
return event.text.trim();
|
|
89064
|
+
}
|
|
89065
|
+
return "";
|
|
89066
|
+
}
|
|
88872
89067
|
const parts = [];
|
|
88873
89068
|
for (const event of events) {
|
|
88874
89069
|
if (event.kind === "assistant_text")
|
|
@@ -88877,10 +89072,11 @@ function collectAssistantText(events) {
|
|
|
88877
89072
|
return parts.join(`
|
|
88878
89073
|
`).trim();
|
|
88879
89074
|
}
|
|
88880
|
-
var DEFAULT_BUDGET_THRESHOLD_FRACTION = 0.8, DEFAULT_NO_PROGRESS_INTERVAL_MS = 30000, DENIED_CAUSE_MARKERS;
|
|
89075
|
+
var DEFAULT_BUDGET_THRESHOLD_FRACTION = 0.8, DEFAULT_NO_PROGRESS_INTERVAL_MS = 30000, DENIED_CAUSE_MARKERS, STRICT_SCHEMA_FILE = "subagent-result.strict.schema.json";
|
|
88881
89076
|
var init_runtime2 = __esm(() => {
|
|
88882
89077
|
init_contracts();
|
|
88883
89078
|
init_dispatch();
|
|
89079
|
+
init_strict_schema();
|
|
88884
89080
|
init_env();
|
|
88885
89081
|
init_prompt2();
|
|
88886
89082
|
init_registry3();
|
|
@@ -92163,7 +92359,7 @@ function describeValue4(value) {
|
|
|
92163
92359
|
return "null";
|
|
92164
92360
|
return typeof value;
|
|
92165
92361
|
}
|
|
92166
|
-
function
|
|
92362
|
+
function resolveRef4(ref, rootSchema) {
|
|
92167
92363
|
if (ref.startsWith("#/$defs/")) {
|
|
92168
92364
|
const name = ref.replace("#/$defs/", "");
|
|
92169
92365
|
const schema = rootSchema.$defs?.[name];
|
|
@@ -92185,7 +92381,7 @@ function validateAgainstSchema3(value, schema, rootSchema) {
|
|
|
92185
92381
|
}
|
|
92186
92382
|
function walk4(value, schema, valuePath, rootSchema, errors) {
|
|
92187
92383
|
if (schema.$ref) {
|
|
92188
|
-
const resolved =
|
|
92384
|
+
const resolved = resolveRef4(schema.$ref, rootSchema);
|
|
92189
92385
|
walk4(value, resolved.schema, valuePath, resolved.root, errors);
|
|
92190
92386
|
return;
|
|
92191
92387
|
}
|
|
@@ -179443,7 +179639,7 @@ async function runCommand4(args, deps, log) {
|
|
|
179443
179639
|
maxExternalDepth: DEFAULT_MAX_EXTERNAL_DEPTH,
|
|
179444
179640
|
onWarning: (warning) => console.error(`warning: ${plain(warning)}`)
|
|
179445
179641
|
};
|
|
179446
|
-
if (write && id
|
|
179642
|
+
if (write && CODEC_WRITE_AGENT_IDS.includes(id)) {
|
|
179447
179643
|
let handle;
|
|
179448
179644
|
let interrupted = false;
|
|
179449
179645
|
const onSigint = () => {
|
|
@@ -179717,7 +179913,7 @@ function printExternalHelp() {
|
|
|
179717
179913
|
{ flag: "--no-probe", desc: "Skip detection entirely; every entry reports `not-probed`." },
|
|
179718
179914
|
{ flag: "--task", desc: "run: what the agent should do. It runs in a disposable worktree; your tree is never touched." },
|
|
179719
179915
|
{ flag: "--unattended", desc: "run: refuse every permission that would need a human (also implied without a TTY)." },
|
|
179720
|
-
{ flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude
|
|
179916
|
+
{ flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude-cli and codex-cli are supported. claude gets Edit and Write inside the worktree and no shell; codex runs under its OS sandbox (writes confined to the worktree, network closed as far as measured) and keeps a shell that can read any file you can read. codex needs version 0.159.2 or newer but older than 0.160.0. antigravity-cli is refused: its edit tool writes outside the worktree." },
|
|
179721
179917
|
{ flag: "--timeout", desc: "run: wall-clock ceiling in ms (default: externalAgents.defaultTimeoutMs)." }
|
|
179722
179918
|
]);
|
|
179723
179919
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.43",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"minLength": 1
|
|
166
166
|
},
|
|
167
167
|
"sandbox": {
|
|
168
|
-
"description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime
|
|
168
|
+
"description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime except for an ACP agent and, through the operator's `--write` command only, `claude-cli` and `codex-cli`, with a reason distinguishable from an unsupported-by-this-agent refusal.",
|
|
169
169
|
"type": "string",
|
|
170
170
|
"enum": ["read-only", "worktree-write"]
|
|
171
171
|
},
|