@iamdevlinph/codex-kit 1.0.6 → 1.0.8
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 +15 -13
- package/assets/SUBAGENT_ROUTING.md +11 -4
- package/bin/codex-kit.js +38 -24
- package/bin/routing-hook.js +1 -141
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,25 +34,29 @@ 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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
write-shaped shell commands. `SubagentStart` opens a 15-minute write allowance
|
|
43
|
-
for that worker's `session_id + turn_id`; `SubagentStop` closes it. This identifies
|
|
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.
|
|
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, configuration bookkeeping, template
|
|
41
|
+
reconciliation, and explicit low-risk one-file edits.
|
|
47
42
|
|
|
48
43
|
`global configure` sets these defaults while preserving unrelated settings:
|
|
49
44
|
|
|
50
45
|
```toml
|
|
51
46
|
model = "gpt-5.6-sol"
|
|
52
|
-
model_reasoning_effort = "
|
|
47
|
+
model_reasoning_effort = "medium"
|
|
53
48
|
plan_mode_reasoning_effort = "high"
|
|
54
49
|
```
|
|
55
50
|
|
|
51
|
+
This keeps ordinary root work lighter while retaining high reasoning in Plan
|
|
52
|
+
Mode. Override either effort independently when needed:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
codex-kit global configure \
|
|
56
|
+
--reasoning-effort medium \
|
|
57
|
+
--plan-reasoning-effort high
|
|
58
|
+
```
|
|
59
|
+
|
|
56
60
|
Before changing these keys, codex-kit creates a timestamped `config.toml`
|
|
57
61
|
backup and records their previous values. `global uninstall` restores those
|
|
58
62
|
values without replacing unrelated configuration changed afterward.
|
|
@@ -208,5 +212,3 @@ redistribute the package beyond applicable law and npm's service terms.
|
|
|
208
212
|
- [Codex custom subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
|
|
209
213
|
- [Codex `AGENTS.md` discovery](https://learn.chatgpt.com/docs/agent-configuration/agents-md)
|
|
210
214
|
- [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,15 +5,18 @@ 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 explicit low-risk edits isolated to one file.
|
|
11
|
+
When a substantive route below matches, spawn that exact role before performing
|
|
12
|
+
the role's work. The user does not need to request delegation.
|
|
11
13
|
|
|
12
14
|
Select custom agents by exact name:
|
|
13
15
|
|
|
14
16
|
- Broad repository discovery, contract tracing, or search across many files:
|
|
15
17
|
`code-explorer`
|
|
16
|
-
-
|
|
18
|
+
- Well-specified implementation that benefits from isolated editing or focused
|
|
19
|
+
tests, usually localized to a few files:
|
|
17
20
|
`quick-implementer`
|
|
18
21
|
- Multi-file behavior changes, debugging, migrations, or substantial tests:
|
|
19
22
|
`implementer`
|
|
@@ -27,6 +30,10 @@ actually needed, and use `code-reviewer` after implementation only when the
|
|
|
27
30
|
change meets its risk threshold. Avoid parallel write-heavy work and never assign
|
|
28
31
|
overlapping files to multiple agents.
|
|
29
32
|
|
|
33
|
+
Prefer the parent fast path when delegation would cost more than the work. Do not
|
|
34
|
+
spawn a subagent solely because a tool will write a file. Delegate based on task
|
|
35
|
+
complexity, context isolation, testing needs, and review risk.
|
|
36
|
+
|
|
30
37
|
An assigned implementation agent performs its edits directly and must not
|
|
31
38
|
delegate them again. Use either `quick-implementer` or `implementer` for one
|
|
32
39
|
implementation slice, not both. Read-only agents never modify files.
|
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 = "medium";
|
|
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
|
}
|
|
@@ -791,15 +786,34 @@ Commands:
|
|
|
791
786
|
--help Show this help.
|
|
792
787
|
--version Print the installed version.
|
|
793
788
|
|
|
794
|
-
Options:
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
789
|
+
Options by command:
|
|
790
|
+
global install
|
|
791
|
+
--codex-home PATH Use a Codex home other than CODEX_HOME or ~/.codex.
|
|
792
|
+
--force Replace modified files managed by codex-kit.
|
|
793
|
+
|
|
794
|
+
global configure
|
|
795
|
+
--codex-home PATH Use a Codex home other than CODEX_HOME or ~/.codex.
|
|
796
|
+
--force Replace modified config managed by codex-kit.
|
|
797
|
+
--orchestrator MODEL Set the root/orchestrator model (default: gpt-5.6-sol).
|
|
798
|
+
--model MODEL Alias for --orchestrator.
|
|
799
|
+
--reasoning-effort LEVEL Set normal reasoning effort (default: medium).
|
|
800
|
+
--plan-reasoning-effort LEVEL Set Plan-mode reasoning effort (default: high).
|
|
801
|
+
|
|
802
|
+
global list, global uninstall
|
|
803
|
+
--codex-home PATH Use a Codex home other than CODEX_HOME or ~/.codex.
|
|
804
|
+
|
|
805
|
+
project init, project sync
|
|
806
|
+
--cwd PATH Use a project directory other than the current directory.
|
|
807
|
+
--force Replace modified files managed by codex-kit.
|
|
808
|
+
|
|
809
|
+
project status, project mark-applied
|
|
810
|
+
--cwd PATH Use a project directory other than the current directory.
|
|
801
811
|
|
|
802
|
-
|
|
812
|
+
Examples:
|
|
813
|
+
codex-kit global install --force
|
|
814
|
+
codex-kit global configure --reasoning-effort medium --plan-reasoning-effort high
|
|
815
|
+
codex-kit project sync --cwd /path/to/project --force
|
|
816
|
+
codex-kit project status --cwd /path/to/project`);
|
|
803
817
|
}
|
|
804
818
|
export function main(argv = process.argv.slice(2)) {
|
|
805
819
|
const options = parse(argv);
|
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
|
-
}
|