@iamdevlinph/codex-kit 1.0.7 → 1.0.9
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 +32 -13
- package/assets/SUBAGENT_ROUTING.md +33 -10
- package/assets/agents/code-explorer.toml +3 -2
- package/assets/agents/code-reviewer.toml +1 -0
- package/assets/agents/implementer.toml +2 -0
- package/assets/agents/quick-implementer.toml +3 -1
- package/bin/codex-kit.js +14 -18
- package/bin/routing-hook.js +1 -141
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Portable Codex setup for new devices and multiple projects. It provides:
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
- three automatically routed roles plus a manual quick implementer
|
|
6
6
|
- automatic global role routing
|
|
7
7
|
- a reusable, stack-neutral `AGENTS.md` template
|
|
8
8
|
- safe commands for global setup, project synchronization, and reconciliation
|
|
@@ -34,25 +34,46 @@ handlers to the global `hooks.json` without replacing existing hooks.
|
|
|
34
34
|
|
|
35
35
|
The Sol root plans, routes, coordinates, and validates. On every prompt, the
|
|
36
36
|
routing hook supplies the current `SUBAGENT_ROUTING.md`; the root classifies the
|
|
37
|
-
task and delegates to the exact matching role. The role's agent
|
|
38
|
-
routing policy—selects its model and reasoning effort.
|
|
39
|
-
|
|
37
|
+
task and delegates substantive work to the exact matching role. The role's agent
|
|
38
|
+
TOML—not the routing policy—selects its model and reasoning effort. To avoid
|
|
39
|
+
subagent startup overhead, the root may directly handle planning, conversation,
|
|
40
|
+
read-only checks, documentation, bookkeeping, and clear changes spanning up to
|
|
41
|
+
roughly three files. Automatic delegation is reserved for broad discovery,
|
|
42
|
+
large multi-file implementation or debugging, and high-risk review.
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
the delegated lane without hard-coding a model. Codex hooks do not intercept
|
|
45
|
-
every possible indirect shell-write path, so this is a workflow guardrail rather
|
|
46
|
-
than a security boundary.
|
|
44
|
+
`quick-implementer` remains installed for explicit manual delegation but is not
|
|
45
|
+
selected by the default automatic route. The root reuses delegated test evidence
|
|
46
|
+
and normally performs only lightweight integration checks.
|
|
47
47
|
|
|
48
48
|
`global configure` sets these defaults while preserving unrelated settings:
|
|
49
49
|
|
|
50
50
|
```toml
|
|
51
51
|
model = "gpt-5.6-sol"
|
|
52
|
-
model_reasoning_effort = "
|
|
52
|
+
model_reasoning_effort = "low"
|
|
53
53
|
plan_mode_reasoning_effort = "high"
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
This keeps ordinary root work light while retaining high reasoning in Plan
|
|
57
|
+
Mode. Override either effort independently when needed:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
codex-kit global configure \
|
|
61
|
+
--reasoning-effort low \
|
|
62
|
+
--plan-reasoning-effort high
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Automatic role models are tuned by task shape: Terra-medium performs broad
|
|
66
|
+
repository exploration, Luna-high performs large implementation slices, and
|
|
67
|
+
Sol-high reviews consequential changes. Luna-medium `quick-implementer` remains
|
|
68
|
+
manual-only.
|
|
69
|
+
|
|
70
|
+
Delegation is time-bounded. The root waits once for up to 60 seconds, requests
|
|
71
|
+
one progress update, and then enforces a three-minute read/review/manual-quick
|
|
72
|
+
deadline or five-minute implementation deadline. Validation commands that make
|
|
73
|
+
no progress for two minutes are stopped unless the repository documents a
|
|
74
|
+
longer normal runtime. Root and worker never run the same validation
|
|
75
|
+
concurrently.
|
|
76
|
+
|
|
56
77
|
Before changing these keys, codex-kit creates a timestamped `config.toml`
|
|
57
78
|
backup and records their previous values. `global uninstall` restores those
|
|
58
79
|
values without replacing unrelated configuration changed afterward.
|
|
@@ -208,5 +229,3 @@ redistribute the package beyond applicable law and npm's service terms.
|
|
|
208
229
|
- [Codex custom subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
|
|
209
230
|
- [Codex `AGENTS.md` discovery](https://learn.chatgpt.com/docs/agent-configuration/agents-md)
|
|
210
231
|
- [Codex hooks](https://learn.chatgpt.com/docs/hooks)
|
|
211
|
-
- [Delegate Mode](https://github.com/Raylinkh/delegate-mode), whose short-lived
|
|
212
|
-
subagent allowance pattern informed codex-kit's write guard
|
|
@@ -5,17 +5,19 @@ task, classify the work using this file. Route by role and task shape, never by
|
|
|
5
5
|
model name; each agent definition owns its model and reasoning effort.
|
|
6
6
|
|
|
7
7
|
The parent owns requirements, architecture, sequencing, integration, and final
|
|
8
|
-
validation. It may handle planning, conversation, status
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
validation. It may directly handle planning, conversation, status, read-only
|
|
9
|
+
checks, documentation and instruction updates, configuration bookkeeping,
|
|
10
|
+
template reconciliation, and clear low-to-medium-risk changes spanning up to
|
|
11
|
+
roughly three files when no broad discovery or architectural decision is needed.
|
|
12
|
+
When a substantive route below matches, spawn that exact role before performing
|
|
13
|
+
the role's work. The user does not need to request delegation.
|
|
11
14
|
|
|
12
15
|
Select custom agents by exact name:
|
|
13
16
|
|
|
14
17
|
- Broad repository discovery, contract tracing, or search across many files:
|
|
15
18
|
`code-explorer`
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
- Multi-file behavior changes, debugging, migrations, or substantial tests:
|
|
19
|
+
- Large multi-file behavior changes, non-obvious debugging, migrations, or
|
|
20
|
+
substantial tests:
|
|
19
21
|
`implementer`
|
|
20
22
|
- Independent review of security-sensitive, architectural, public-API,
|
|
21
23
|
concurrency, migration, or otherwise difficult-to-validate changes:
|
|
@@ -27,16 +29,37 @@ actually needed, and use `code-reviewer` after implementation only when the
|
|
|
27
29
|
change meets its risk threshold. Avoid parallel write-heavy work and never assign
|
|
28
30
|
overlapping files to multiple agents.
|
|
29
31
|
|
|
32
|
+
Prefer the parent fast path when delegation would cost more than the work. Do not
|
|
33
|
+
spawn a subagent solely because a tool will write a file. Delegate based on task
|
|
34
|
+
complexity, context isolation, testing needs, and review risk.
|
|
35
|
+
|
|
36
|
+
`quick-implementer` remains installed for explicit manual delegation, but the
|
|
37
|
+
default automatic routing policy does not select it.
|
|
38
|
+
|
|
30
39
|
An assigned implementation agent performs its edits directly and must not
|
|
31
|
-
delegate them again. Use
|
|
32
|
-
|
|
40
|
+
delegate them again. Use one implementation agent per slice. Read-only agents
|
|
41
|
+
never modify files.
|
|
33
42
|
|
|
34
43
|
For every delegation:
|
|
35
44
|
|
|
36
45
|
- Give the agent a bounded task and the relevant requirements.
|
|
46
|
+
- Include the role deadline: three minutes for `code-explorer`,
|
|
47
|
+
`code-reviewer`, or a manually requested `quick-implementer`; five minutes
|
|
48
|
+
for `implementer`.
|
|
37
49
|
- Request a concise, decision-ready result.
|
|
38
|
-
- Wait for the
|
|
39
|
-
|
|
50
|
+
- Wait once for at most 60 seconds. If the agent is still running, request one
|
|
51
|
+
concise progress update. Never issue consecutive wait cycles without new
|
|
52
|
+
progress, steering, or independent useful work.
|
|
53
|
+
- If the role deadline expires, tell the agent to stop commands and return its
|
|
54
|
+
stable partial result. Interrupt it if it does not respond, preserve its
|
|
55
|
+
edits, and finish locally.
|
|
56
|
+
- Do not run the same validation concurrently in the parent and worker. A
|
|
57
|
+
validation command that produces no result within two minutes is hung unless
|
|
58
|
+
repository guidance documents a longer normal runtime; stop it and report the
|
|
59
|
+
evidence instead of retrying it repeatedly.
|
|
60
|
+
- Reuse returned exploration and test evidence. The parent should run only
|
|
61
|
+
lightweight integration checks unless the work is high-risk or the evidence
|
|
62
|
+
is incomplete.
|
|
40
63
|
|
|
41
64
|
There is no commit-pusher role. Never delegate Git publishing. Do not stage,
|
|
42
65
|
commit, tag, or push unless the user explicitly requests that operation in the
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
name = "code-explorer"
|
|
2
2
|
description = "Read-only codebase scout. Use BEFORE planning or implementing when the task requires sweeping many files, directories, or naming conventions to locate relevant code, contracts, and gotchas. Returns a condensed structured report — never raw file dumps. Does not modify code."
|
|
3
|
-
model = "gpt-5.6-
|
|
3
|
+
model = "gpt-5.6-terra"
|
|
4
4
|
model_reasoning_effort = "medium"
|
|
5
5
|
sandbox_mode = "read-only"
|
|
6
6
|
developer_instructions = """
|
|
7
7
|
# Code Explorer (Read-Only Scout)
|
|
8
8
|
|
|
9
|
-
Begin your first user-visible response with this progress message exactly once: `Delegating to custom code-explorer — gpt-5.6-
|
|
9
|
+
Begin your first user-visible response with this progress message exactly once: `Delegating to custom code-explorer — gpt-5.6-terra, medium reasoning.`
|
|
10
10
|
|
|
11
11
|
You locate and distill the code relevant to a task so the orchestrator never has to hold raw search output in its own context.
|
|
12
12
|
|
|
@@ -24,6 +24,7 @@ You locate and distill the code relevant to a task so the orchestrator never has
|
|
|
24
24
|
|
|
25
25
|
## Rules
|
|
26
26
|
- Keep it lean: exploration is retrieval, not reasoning. Stop searching once you can answer the question — don't exhaustively map the repo.
|
|
27
|
+
- Finish within three minutes. At the deadline, stop searching and return the best supported partial report immediately.
|
|
27
28
|
- Read-only: never edit, write, or run state-changing commands. Tests/builds are the implementer's job.
|
|
28
29
|
- No raw dumps: never paste whole files or long grep output into your report — that defeats the purpose of delegating exploration.
|
|
29
30
|
- If you find nothing, say so plainly and list where you looked.
|
|
@@ -18,6 +18,7 @@ P3 Low, with precise file and line references.
|
|
|
18
18
|
|
|
19
19
|
## Rules
|
|
20
20
|
- Keep it lean: surface the few high-confidence, high-impact findings rather than an exhaustive nitpick list. Rank by severity.
|
|
21
|
+
- Finish within three minutes. At the deadline, stop and return the findings already supported by the diff.
|
|
21
22
|
- Review only — do not modify code unless the caller explicitly asks you to fix findings.
|
|
22
23
|
- Block merge on P0/P1; call out exploitability and impact for security findings.
|
|
23
24
|
- If the diff is empty, say so and ask what to review.
|
|
@@ -18,6 +18,8 @@ You write the actual code, its unit tests, and you run those tests to prove the
|
|
|
18
18
|
|
|
19
19
|
## Rules
|
|
20
20
|
- No change is done until its tests are green and you've shown the output.
|
|
21
|
+
- Finish within five minutes. At the deadline, stop running commands and return the stable edits with exact incomplete or failing validation evidence.
|
|
22
|
+
- Run one validation process at a time. Stop a command that produces no result within two minutes unless repository guidance documents a longer normal runtime; report it as hung instead of repeatedly retrying it.
|
|
21
23
|
- Don't expand scope beyond the assigned slice; flag anything else you notice.
|
|
22
24
|
- Never mark your own work as reviewed — that's the code-reviewer's job.
|
|
23
25
|
- Never stage, commit, or push.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name = "quick-implementer"
|
|
2
|
-
description = "
|
|
2
|
+
description = "Manual-only focused agent for small, mechanical, well-specified changes in one or two files. Not selected by codex-kit's default automatic routing. Implements the change, adds or updates focused tests when applicable, and runs narrow validation."
|
|
3
3
|
model = "gpt-5.6-luna"
|
|
4
4
|
model_reasoning_effort = "medium"
|
|
5
5
|
developer_instructions = """
|
|
@@ -8,6 +8,7 @@ developer_instructions = """
|
|
|
8
8
|
Begin your first user-visible response with this progress message exactly once: `Delegating to custom quick-implementer — gpt-5.6-luna, medium reasoning.`
|
|
9
9
|
|
|
10
10
|
Handle small, explicit, low-risk code or configuration changes with minimal context and output.
|
|
11
|
+
This role is available for explicit manual delegation and is not part of codex-kit's default automatic route.
|
|
11
12
|
|
|
12
13
|
## Fit check
|
|
13
14
|
Proceed only when the requested change is well specified, localized to one or two files, and does not require architecture decisions. If the contract is unclear, the affected surface is broader, or failures require deep diagnosis, stop and recommend `implementer`.
|
|
@@ -21,6 +22,7 @@ Proceed only when the requested change is well specified, localized to one or tw
|
|
|
21
22
|
|
|
22
23
|
## Rules
|
|
23
24
|
- Never broaden scope or refactor adjacent code.
|
|
25
|
+
- Finish within three minutes. Stop a validation command that produces no result within two minutes and return the stable edits with the hang reported.
|
|
24
26
|
- Never claim success without showing the validation result.
|
|
25
27
|
- Never commit or push.
|
|
26
28
|
- Keep command output summarized; do not return raw file dumps.
|
package/bin/codex-kit.js
CHANGED
|
@@ -21,7 +21,8 @@ const STATE_FILE = ".codex-kit-state.json";
|
|
|
21
21
|
const PROJECT_STATE_FILE = ".codex-kit-state.json";
|
|
22
22
|
const REGISTRY = PACKAGE.publishConfig?.registry ?? "https://registry.npmjs.org";
|
|
23
23
|
const DEFAULT_ORCHESTRATOR = "gpt-5.6-sol";
|
|
24
|
-
const DEFAULT_REASONING_EFFORT = "
|
|
24
|
+
const DEFAULT_REASONING_EFFORT = "low";
|
|
25
|
+
const DEFAULT_PLAN_REASONING_EFFORT = "high";
|
|
25
26
|
const sha256 = (data) => createHash("sha256").update(data).digest("hex");
|
|
26
27
|
const read = (file) => readFileSync(file);
|
|
27
28
|
const readText = (file) => readFileSync(file, "utf8");
|
|
@@ -107,7 +108,6 @@ function installRoutingHooks(home, previous) {
|
|
|
107
108
|
timeout: 5,
|
|
108
109
|
};
|
|
109
110
|
const promptGroups = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
|
|
110
|
-
const toolGroups = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
111
111
|
hooks.UserPromptSubmit = [
|
|
112
112
|
...promptGroups,
|
|
113
113
|
{ hooks: [{ ...handler, statusMessage: "Loading subagent routing" }] },
|
|
@@ -115,19 +115,7 @@ function installRoutingHooks(home, previous) {
|
|
|
115
115
|
const startGroups = Array.isArray(hooks.SubagentStart) ? hooks.SubagentStart : [];
|
|
116
116
|
hooks.SubagentStart = [
|
|
117
117
|
...startGroups,
|
|
118
|
-
{ hooks: [{ ...handler, statusMessage: "
|
|
119
|
-
];
|
|
120
|
-
const stopGroups = Array.isArray(hooks.SubagentStop) ? hooks.SubagentStop : [];
|
|
121
|
-
hooks.SubagentStop = [
|
|
122
|
-
...stopGroups,
|
|
123
|
-
{ hooks: [{ ...handler, statusMessage: "Closing delegated write lane" }] },
|
|
124
|
-
];
|
|
125
|
-
hooks.PreToolUse = [
|
|
126
|
-
...toolGroups,
|
|
127
|
-
{
|
|
128
|
-
matcher: "Bash|Edit|Write|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions.apply_patch",
|
|
129
|
-
hooks: [{ ...handler, statusMessage: "Checking orchestrator routing" }],
|
|
130
|
-
},
|
|
118
|
+
{ hooks: [{ ...handler, statusMessage: "Briefing delegated worker" }] },
|
|
131
119
|
];
|
|
132
120
|
const updated = `${JSON.stringify(root, null, 2)}\n`;
|
|
133
121
|
const original = existsSync(target) ? readText(target) : "";
|
|
@@ -402,7 +390,7 @@ function configureGlobal(options) {
|
|
|
402
390
|
const desired = {
|
|
403
391
|
model: options.orchestrator,
|
|
404
392
|
model_reasoning_effort: options.reasoningEffort,
|
|
405
|
-
plan_mode_reasoning_effort: options.
|
|
393
|
+
plan_mode_reasoning_effort: options.planReasoningEffort,
|
|
406
394
|
};
|
|
407
395
|
const priorState = loadState(home);
|
|
408
396
|
const original = existsSync(configFile) ? readText(configFile) : "";
|
|
@@ -737,6 +725,7 @@ function parse(argv) {
|
|
|
737
725
|
codexHome: resolve(process.env.CODEX_HOME || join(homedir(), ".codex")),
|
|
738
726
|
orchestrator: DEFAULT_ORCHESTRATOR,
|
|
739
727
|
reasoningEffort: DEFAULT_REASONING_EFFORT,
|
|
728
|
+
planReasoningEffort: DEFAULT_PLAN_REASONING_EFFORT,
|
|
740
729
|
force: false,
|
|
741
730
|
positionals: [],
|
|
742
731
|
};
|
|
@@ -767,6 +756,12 @@ function parse(argv) {
|
|
|
767
756
|
throw new Error(`${arg} requires a value.`);
|
|
768
757
|
options.reasoningEffort = value;
|
|
769
758
|
}
|
|
759
|
+
else if (arg === "--plan-reasoning-effort") {
|
|
760
|
+
const value = argv[++index];
|
|
761
|
+
if (!value)
|
|
762
|
+
throw new Error(`${arg} requires a value.`);
|
|
763
|
+
options.planReasoningEffort = value;
|
|
764
|
+
}
|
|
770
765
|
else
|
|
771
766
|
options.positionals.push(arg);
|
|
772
767
|
}
|
|
@@ -801,7 +796,8 @@ Options by command:
|
|
|
801
796
|
--force Replace modified config managed by codex-kit.
|
|
802
797
|
--orchestrator MODEL Set the root/orchestrator model (default: gpt-5.6-sol).
|
|
803
798
|
--model MODEL Alias for --orchestrator.
|
|
804
|
-
--reasoning-effort LEVEL
|
|
799
|
+
--reasoning-effort LEVEL Set normal reasoning effort (default: low).
|
|
800
|
+
--plan-reasoning-effort LEVEL Set Plan-mode reasoning effort (default: high).
|
|
805
801
|
|
|
806
802
|
global list, global uninstall
|
|
807
803
|
--codex-home PATH Use a Codex home other than CODEX_HOME or ~/.codex.
|
|
@@ -815,7 +811,7 @@ Options by command:
|
|
|
815
811
|
|
|
816
812
|
Examples:
|
|
817
813
|
codex-kit global install --force
|
|
818
|
-
codex-kit global configure --
|
|
814
|
+
codex-kit global configure --reasoning-effort low --plan-reasoning-effort high
|
|
819
815
|
codex-kit project sync --cwd /path/to/project --force
|
|
820
816
|
codex-kit project status --cwd /path/to/project`);
|
|
821
817
|
}
|
package/bin/routing-hook.js
CHANGED
|
@@ -1,132 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import { dirname, join, resolve } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
const home = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
6
|
const routingFile = join(home, "SUBAGENT_ROUTING.md");
|
|
8
|
-
const allowancesDir = join(home, "codex-kit", "allowances");
|
|
9
|
-
const allowanceTtlMs = 15 * 60 * 1000;
|
|
10
|
-
const writeTools = new Set([
|
|
11
|
-
"Edit",
|
|
12
|
-
"Write",
|
|
13
|
-
"MultiEdit",
|
|
14
|
-
"NotebookEdit",
|
|
15
|
-
"apply_patch",
|
|
16
|
-
"ApplyPatch",
|
|
17
|
-
"functions.apply_patch",
|
|
18
|
-
]);
|
|
19
|
-
const writeCommands = new Set([
|
|
20
|
-
"chmod",
|
|
21
|
-
"chown",
|
|
22
|
-
"cp",
|
|
23
|
-
"dd",
|
|
24
|
-
"install",
|
|
25
|
-
"ln",
|
|
26
|
-
"mkdir",
|
|
27
|
-
"mv",
|
|
28
|
-
"rm",
|
|
29
|
-
"rmdir",
|
|
30
|
-
"rsync",
|
|
31
|
-
"tee",
|
|
32
|
-
"touch",
|
|
33
|
-
"truncate",
|
|
34
|
-
]);
|
|
35
|
-
const gitWriteCommands = new Set([
|
|
36
|
-
"add",
|
|
37
|
-
"am",
|
|
38
|
-
"apply",
|
|
39
|
-
"checkout",
|
|
40
|
-
"cherry-pick",
|
|
41
|
-
"clean",
|
|
42
|
-
"commit",
|
|
43
|
-
"merge",
|
|
44
|
-
"mv",
|
|
45
|
-
"rebase",
|
|
46
|
-
"reset",
|
|
47
|
-
"restore",
|
|
48
|
-
"revert",
|
|
49
|
-
"rm",
|
|
50
|
-
"stash",
|
|
51
|
-
"switch",
|
|
52
|
-
]);
|
|
53
7
|
const input = JSON.parse(readFileSync(0, "utf8"));
|
|
54
|
-
function allowanceFile(payload) {
|
|
55
|
-
if (!payload.session_id || !payload.turn_id)
|
|
56
|
-
return null;
|
|
57
|
-
const key = createHash("sha256")
|
|
58
|
-
.update(`${payload.session_id}\0${payload.turn_id}`)
|
|
59
|
-
.digest("hex");
|
|
60
|
-
return join(allowancesDir, `${key}.json`);
|
|
61
|
-
}
|
|
62
|
-
function writeAllowance(payload) {
|
|
63
|
-
const file = allowanceFile(payload);
|
|
64
|
-
if (!file)
|
|
65
|
-
return false;
|
|
66
|
-
mkdirSync(allowancesDir, { recursive: true });
|
|
67
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
68
|
-
writeFileSync(temporary, JSON.stringify({
|
|
69
|
-
agentId: payload.agent_id,
|
|
70
|
-
agentType: payload.agent_type,
|
|
71
|
-
expiresAt: Date.now() + allowanceTtlMs,
|
|
72
|
-
}));
|
|
73
|
-
renameSync(temporary, file);
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
function removeAllowance(payload) {
|
|
77
|
-
const file = allowanceFile(payload);
|
|
78
|
-
if (file)
|
|
79
|
-
rmSync(file, { force: true });
|
|
80
|
-
}
|
|
81
|
-
function hasAllowance(payload) {
|
|
82
|
-
const file = allowanceFile(payload);
|
|
83
|
-
if (!file || !existsSync(file))
|
|
84
|
-
return false;
|
|
85
|
-
try {
|
|
86
|
-
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
87
|
-
if (typeof value.expiresAt === "number" && value.expiresAt > Date.now())
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// Invalid allowance files fail closed.
|
|
92
|
-
}
|
|
93
|
-
rmSync(file, { force: true });
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
function pruneAllowances() {
|
|
97
|
-
if (!existsSync(allowancesDir))
|
|
98
|
-
return;
|
|
99
|
-
for (const name of readdirSync(allowancesDir)) {
|
|
100
|
-
if (!name.endsWith(".json"))
|
|
101
|
-
continue;
|
|
102
|
-
const file = join(allowancesDir, name);
|
|
103
|
-
try {
|
|
104
|
-
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
105
|
-
if (typeof value.expiresAt === "number" && value.expiresAt > Date.now())
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Invalid allowance files are stale.
|
|
110
|
-
}
|
|
111
|
-
rmSync(file, { force: true });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function isWriteShapedBash(command) {
|
|
115
|
-
if (/(^|[^<])(?:>>?|&>>?|\d>>?)\s*[^&]/.test(command))
|
|
116
|
-
return true;
|
|
117
|
-
const commands = command.split(/(?:&&|\|\||;|\|)/).map((part) => part.trim()).filter(Boolean);
|
|
118
|
-
return commands.some((segment) => {
|
|
119
|
-
const tokens = segment.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
|
|
120
|
-
const executable = tokens[0]?.split("/").at(-1);
|
|
121
|
-
if (!executable)
|
|
122
|
-
return false;
|
|
123
|
-
if (writeCommands.has(executable))
|
|
124
|
-
return true;
|
|
125
|
-
if ((executable === "sed" || executable === "perl") && tokens.slice(1, 5).some((token) => /^-[^-]*i/.test(token)))
|
|
126
|
-
return true;
|
|
127
|
-
return executable === "git" && Boolean(tokens[1] && gitWriteCommands.has(tokens[1]));
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
8
|
function hookContext(event, context) {
|
|
131
9
|
return JSON.stringify({
|
|
132
10
|
hookSpecificOutput: {
|
|
@@ -143,24 +21,6 @@ if (input.hook_event_name === "UserPromptSubmit" && existsSync(routingFile)) {
|
|
|
143
21
|
"this policy, determine each role's model and reasoning effort.");
|
|
144
22
|
}
|
|
145
23
|
if (input.hook_event_name === "SubagentStart") {
|
|
146
|
-
pruneAllowances();
|
|
147
|
-
const recorded = writeAllowance(input);
|
|
148
24
|
process.stdout.write(hookContext("SubagentStart", `You are the delegated ${input.agent_type ?? "worker"}. ` +
|
|
149
|
-
`${recorded ? "A temporary write lane is active." : "No write lane could be recorded."} ` +
|
|
150
25
|
"Follow the assigned scope, perform the role's work directly without further delegation, validate it, and return concise evidence."));
|
|
151
26
|
}
|
|
152
|
-
if (input.hook_event_name === "SubagentStop")
|
|
153
|
-
removeAllowance(input);
|
|
154
|
-
if (input.hook_event_name === "PreToolUse") {
|
|
155
|
-
const writeAttempt = writeTools.has(input.tool_name ?? "") ||
|
|
156
|
-
(input.tool_name === "Bash" && isWriteShapedBash(input.tool_input?.command ?? ""));
|
|
157
|
-
if (writeAttempt && !input.agent_id && !hasAllowance(input)) {
|
|
158
|
-
process.stdout.write(JSON.stringify({
|
|
159
|
-
hookSpecificOutput: {
|
|
160
|
-
hookEventName: "PreToolUse",
|
|
161
|
-
permissionDecision: "deny",
|
|
162
|
-
permissionDecisionReason: "The root orchestrator may not write directly. Consult SUBAGENT_ROUTING.md and delegate to the exact role selected there.",
|
|
163
|
-
},
|
|
164
|
-
}));
|
|
165
|
-
}
|
|
166
|
-
}
|