@lyupro/codex-bridge 0.6.8 → 0.6.10
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/CHANGELOG.md +160 -0
- package/README.md +6 -4
- package/bin/codex-bridge.mjs +35 -177
- package/cli/command-help.mjs +49 -0
- package/cli/command-registry.mjs +284 -0
- package/cli/copy.mjs +42 -5
- package/cli/dispatcher-contract-record.mjs +3 -2
- package/cli/doctor-host-side.mjs +110 -0
- package/cli/doctor.mjs +17 -1
- package/cli/home-inspection.mjs +122 -0
- package/cli/home-plan-execute.mjs +140 -0
- package/cli/home-removal-plan.mjs +61 -0
- package/cli/hook-recognizer.mjs +78 -0
- package/cli/host-contract.mjs +11 -5
- package/cli/host-inspection.mjs +156 -0
- package/cli/host-removal.mjs +91 -0
- package/cli/image-evidence.mjs +44 -0
- package/cli/install-owners.mjs +244 -0
- package/cli/install-record.mjs +71 -9
- package/cli/install.mjs +88 -75
- package/cli/inventory-removal.mjs +84 -0
- package/cli/inventory-transition.mjs +66 -0
- package/cli/lifecycle-lock-clear.mjs +104 -0
- package/cli/lifecycle-lock-inspect.mjs +92 -0
- package/cli/lifecycle-lock.mjs +222 -0
- package/cli/lifecycle-transaction.mjs +57 -0
- package/cli/link-segments.mjs +45 -0
- package/cli/model-set.mjs +4 -3
- package/cli/model-speed.mjs +9 -2
- package/cli/model.mjs +5 -3
- package/cli/owner-record.mjs +40 -0
- package/cli/owner-sync-apply.mjs +108 -0
- package/cli/owner-sync.mjs +115 -0
- package/cli/projects.mjs +5 -2
- package/cli/prune-args.mjs +9 -4
- package/cli/prune.mjs +23 -72
- package/cli/purge-live-runs.mjs +98 -0
- package/cli/purge-preflight.mjs +103 -0
- package/cli/read.mjs +22 -1
- package/cli/record-removal.mjs +43 -0
- package/cli/removal-outcomes.mjs +115 -0
- package/cli/removal-plan.mjs +187 -0
- package/cli/remove-layout.mjs +40 -1
- package/cli/rules-owners.mjs +4 -2
- package/cli/settings-merge.mjs +3 -29
- package/cli/terminal-question.mjs +41 -0
- package/cli/uninstall-purge.mjs +139 -0
- package/cli/uninstall-rules.mjs +72 -0
- package/cli/uninstall.mjs +208 -127
- package/cli/unlock-lifecycle.mjs +102 -0
- package/cli/unlock.mjs +7 -4
- package/cli/update.mjs +102 -27
- package/package.json +4 -3
- package/scripts/home-write-audit.mjs +286 -0
- package/scripts/home-write-inventory.mjs +59 -0
- package/scripts/home-write-lexer.mjs +182 -0
- package/scripts/isolated-roots.mjs +6 -1
- package/scripts/open-work/check.mjs +180 -0
- package/scripts/open-work/live-thresholds.mjs +155 -0
- package/scripts/open-work/register.mjs +56 -0
- package/scripts/root-guard/root-entries.mjs +15 -0
- package/scripts/run-tests.mjs +26 -1
- package/src/agents/codex-advisor.md +23 -10
- package/src/agents/codex-build.md +24 -14
- package/src/agents/codex-review.md +17 -10
- package/src/agents/codex-scout.md +22 -12
- package/src/home/conventions.md +9 -0
- package/src/home/hooks/guard-tries.mjs +6 -4
- package/src/home/hooks/order-gate.mjs +27 -19
- package/src/home/hooks/reply-guard.mjs +2 -7
- package/src/home/hooks/reply-parser.mjs +1 -1
- package/src/home/hooks/worktree-witness.mjs +6 -6
- package/src/home/lib/agents.mjs +1 -1
- package/src/home/lib/atomic-json.mjs +24 -6
- package/src/home/lib/command-path.mjs +32 -0
- package/src/home/lib/config-edit.mjs +25 -10
- package/src/home/lib/dispatcher-command.mjs +4 -2
- package/src/home/lib/dispatcher-state.mjs +25 -10
- package/src/home/lib/file-lock.mjs +41 -10
- package/src/home/lib/handback-witness.mjs +7 -4
- package/src/home/lib/home-registry.mjs +211 -0
- package/src/home/lib/home-write.mjs +189 -0
- package/src/home/lib/hook-diagnostics.mjs +20 -0
- package/src/home/lib/host-observations.mjs +7 -4
- package/src/home/lib/kernel-lock.mjs +171 -0
- package/src/home/lib/meta/advice-status.mjs +2 -2
- package/src/home/lib/meta/advice-verdict.mjs +30 -1
- package/src/home/lib/meta/chain.mjs +19 -8
- package/src/home/lib/meta/context-only.mjs +43 -0
- package/src/home/lib/meta/events.mjs +16 -3
- package/src/home/lib/meta/launch-rows.mjs +23 -0
- package/src/home/lib/meta/paths.mjs +24 -24
- package/src/home/lib/meta/reply.mjs +37 -3
- package/src/home/lib/meta/run-snapshots.mjs +29 -0
- package/src/home/lib/meta/run-state.mjs +3 -0
- package/src/home/lib/meta/snapshot-format.mjs +96 -0
- package/src/home/lib/meta/verdict.mjs +30 -24
- package/src/home/lib/required-inputs.mjs +48 -8
- package/src/home/lib/run-codex.mjs +6 -3
- package/src/home/lib/run-config.mjs +10 -1
- package/src/home/lib/runner/advise-carry.mjs +2 -1
- package/src/home/lib/runner/args.mjs +16 -5
- package/src/home/lib/runner/attach.mjs +21 -14
- package/src/home/lib/runner/build-evidence.mjs +29 -0
- package/src/home/lib/runner/codex-availability.mjs +50 -0
- package/src/home/lib/runner/codex-cmd.mjs +2 -19
- package/src/home/lib/runner/continuation.mjs +60 -8
- package/src/home/lib/runner/exit-codes.mjs +13 -0
- package/src/home/lib/runner/flag-baseline.mjs +105 -0
- package/src/home/lib/runner/flag-scan.mjs +165 -0
- package/src/home/lib/runner/git-paths.mjs +74 -0
- package/src/home/lib/runner/git-state.mjs +37 -66
- package/src/home/lib/runner/launcher.mjs +233 -273
- package/src/home/lib/runner/order-claim.mjs +84 -0
- package/src/home/lib/runner/order-owner.mjs +18 -0
- package/src/home/lib/runner/pass-gate.mjs +109 -0
- package/src/home/lib/runner/preflight.mjs +22 -18
- package/src/home/lib/runner/prompts.mjs +42 -4
- package/src/home/lib/runner/question-kind.mjs +41 -0
- package/src/home/lib/runner/run-env.mjs +2 -1
- package/src/home/lib/runner/sandbox-probe.mjs +1 -1
- package/src/home/lib/runner/schemas.mjs +4 -1
- package/src/home/lib/runner/scope-check.mjs +55 -28
- package/src/home/lib/runner/task-input.mjs +11 -2
- package/src/home/lib/runner/worker-admission.mjs +43 -0
- package/src/home/lib/runner/worker.mjs +19 -18
- package/src/home/lib/task-header.mjs +127 -0
- package/src/home/lib/write-meta.mjs +17 -5
|
@@ -46,9 +46,11 @@ and do not reason about the design yourself.
|
|
|
46
46
|
`order id` as `--order-id`, `task file` as `--task-file`, and `phase` as `--phase`. Use the same
|
|
47
47
|
order id for both phases; never invent or edit it. If the orchestrator did not give a required
|
|
48
48
|
input, do not guess — start the runner without its flag and return its refusal verbatim.
|
|
49
|
-
- `continue` — when the
|
|
50
|
-
bare `--continue` flag even when its run name or reason looks malformed.
|
|
51
|
-
|
|
49
|
+
- `continue` — when the orchestrator's call carries a line beginning with `continue:` or `retry:`,
|
|
50
|
+
pass the bare `--continue` flag even when its run name or reason looks malformed. A `retry:`
|
|
51
|
+
grant lets the orchestrator repeat a failed pass (FAIL, LIMIT, UNAVAILABLE) of the same order
|
|
52
|
+
once; it is not the next pass. Do not inspect, repair, or swallow this grant; pass it through
|
|
53
|
+
and let the runner issue the refusal. A continuation is
|
|
52
54
|
assigned by the orchestrator, never chosen by you. After the verdict, return the exact attaching
|
|
53
55
|
output and stop; do not issue or invent another continuation. If no such grant line is present,
|
|
54
56
|
the flag must not be present. The advise phase requires this grant to continue the scope run.
|
|
@@ -82,9 +84,11 @@ unmatchable by construction. Reaching for one does not rescue the run; it guaran
|
|
|
82
84
|
codex-bridge run --agent codex-advisor --repo "<repository-path or .>" --phase "<phase from the orchestrator>" --slug "<slug>" --order-id "<order id from the orchestrator>" --task-file "<task-file path from the orchestrator>"
|
|
83
85
|
```
|
|
84
86
|
|
|
85
|
-
That is one command line. Add only the bare `--continue` when the
|
|
86
|
-
beginning with `continue:`;
|
|
87
|
-
|
|
87
|
+
That is one command line. Add only the bare `--continue` when the orchestrator's call carries a line
|
|
88
|
+
beginning with `continue:` or `retry:`; the flag itself takes no value (the runner accepts only
|
|
89
|
+
`1/true/yes/0/false/no` there). The grant line names a run folder and a reason; never inspect, repair,
|
|
90
|
+
or swallow it. The runner parses it, so a malformed grant is passed through for the runner to
|
|
91
|
+
refuse. If
|
|
88
92
|
there is no grant line, do not add the flag. Add `--effort "<value>"` only if the orchestrator
|
|
89
93
|
named a depth, and only with one of `none|low|medium|high|xhigh|max`. Never add free text or any
|
|
90
94
|
flags beyond those two conditional flags.
|
|
@@ -141,9 +145,13 @@ path.
|
|
|
141
145
|
- `FAIL` — the result is empty, invalid, the return code is nonzero, or the run was abandoned.
|
|
142
146
|
- `LIMIT` — the result is empty and the log signals a ChatGPT quota limit. The quota is spent and
|
|
143
147
|
the task was not completed; do not restart it.
|
|
148
|
+
- `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
|
|
149
|
+
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
150
|
+
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
144
151
|
|
|
145
|
-
The script return code mirrors the status: `0` / `1` / `3`. A nonzero code is not a reason to
|
|
152
|
+
The script return code mirrors the status: `0` / `1` / `3` / `5`. A nonzero code is not a reason to
|
|
146
153
|
retry, change the command, or investigate on your own.
|
|
154
|
+
Code `5` means `UNAVAILABLE`.
|
|
147
155
|
|
|
148
156
|
For `scope`, `OK — scope: sufficient; …` means the listed paths cover the question; continue with
|
|
149
157
|
`--phase advise` only when the orchestrator supplies that continuation. `OK — scope: insufficient;
|
|
@@ -158,12 +166,17 @@ verbatim; compose nothing.
|
|
|
158
166
|
The run folder contains its status and artifacts. An abandoned run is not a reason to start over
|
|
159
167
|
yourself: the orchestrator decides whether to continue, and without the `--continue` it issued,
|
|
160
168
|
the runner will reject that continuation itself.
|
|
169
|
+
The orchestrator repeats a failed scope or a failed advise with a `retry:` grant naming that failed
|
|
170
|
+
run, under the same order id. A successful scope is never continued as scope:
|
|
171
|
+
the runner refuses a continued scope, because it would spend the continuation that advise needs.
|
|
161
172
|
|
|
162
173
|
## Codex is unavailable
|
|
163
174
|
|
|
164
|
-
The runner
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
|
|
176
|
+
or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
|
|
177
|
+
`UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
|
|
178
|
+
`Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
179
|
+
Performing the task manually instead of Codex is prohibited for any reason it fails.
|
|
167
180
|
|
|
168
181
|
## What a violation looks like
|
|
169
182
|
|
|
@@ -39,9 +39,11 @@ job is to report the run status honestly, including failure.
|
|
|
39
39
|
reuse an order id from another order — the runner chains runs by that label, and a made-up label
|
|
40
40
|
is how a repeat run hides. If the orchestrator did not give a required input, do not guess: start
|
|
41
41
|
the runner without its flag and return the runner's refusal exactly.
|
|
42
|
-
- `continue` — when the
|
|
43
|
-
bare `--continue` flag even when its run name or reason looks malformed.
|
|
44
|
-
|
|
42
|
+
- `continue` — when the orchestrator's call carries a line beginning with `continue:` or `retry:`,
|
|
43
|
+
pass the bare `--continue` flag even when its run name or reason looks malformed. A `retry:`
|
|
44
|
+
grant lets the orchestrator repeat a failed pass (FAIL, LIMIT, UNAVAILABLE) of the same order
|
|
45
|
+
once; it is not the next pass. Do not inspect, repair, or swallow this grant; pass it through
|
|
46
|
+
and let the runner issue the refusal. A continuation is
|
|
45
47
|
assigned by the orchestrator, never chosen by you. After the verdict, return the exact attaching
|
|
46
48
|
output and stop; do not issue or invent another continuation. If no such grant line is present,
|
|
47
49
|
the flag must not be present.
|
|
@@ -51,11 +53,13 @@ job is to report the run status honestly, including failure.
|
|
|
51
53
|
heading, one line. You never read that file and never put the command on the command line: an
|
|
52
54
|
operator's real check command contained `&&`, and a compound operator in an argument makes the
|
|
53
55
|
host stop applying its permission rule, which is how a delegation dies on a refusal.
|
|
54
|
-
- Every build task file must
|
|
56
|
+
- Every build task file must start with a header of consecutive lowercase `label: value` lines
|
|
57
|
+
from the first line: exactly one `advice:` line from the orchestrator and the `continue:` or
|
|
58
|
+
`retry:` grant when there is one, then a blank line. The `advice:` value is an absolute
|
|
55
59
|
path to an existing run folder whose `meta.json` identifies a `codex-advisor` advise pass with
|
|
56
60
|
status `OK`, or exactly
|
|
57
61
|
`mechanical`, `revert`, `docs-only`, or `test-only`. Anything else is refused before the run
|
|
58
|
-
starts. Pass the task file as supplied; the orchestrator owns this
|
|
62
|
+
starts. Pass the task file as supplied and never edit it; the orchestrator owns this header.
|
|
59
63
|
|
|
60
64
|
## When the host refuses the command
|
|
61
65
|
|
|
@@ -90,8 +94,9 @@ Add `--effort "<value>"` only when the orchestrator named a depth, and only with
|
|
|
90
94
|
decides, which is the intended default — a placeholder copied from this template is refused before
|
|
91
95
|
Codex starts.
|
|
92
96
|
|
|
93
|
-
If the
|
|
94
|
-
flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
97
|
+
If the orchestrator's call carries a line beginning with `continue:` or `retry:`, add the bare
|
|
98
|
+
`--continue` flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
99
|
+
swallow the line;
|
|
95
100
|
the runner parses it and issues the refusal. If no such grant line is present, the flag must not be
|
|
96
101
|
present in the command.
|
|
97
102
|
No value
|
|
@@ -191,8 +196,9 @@ anything.
|
|
|
191
196
|
An empty `summary` is FAIL.
|
|
192
197
|
|
|
193
198
|
The `Flags` line in the response lists `TODO`, `FIXME`, `test.skip`/`.only`, and
|
|
194
|
-
`NotImplemented`
|
|
195
|
-
orchestrator.
|
|
199
|
+
`NotImplemented` markers on lines THIS run added, compared with its start content. They are not
|
|
200
|
+
hidden: false completion must be visible to the orchestrator. `Flags coverage: incomplete` means
|
|
201
|
+
some files could not be judged.
|
|
196
202
|
|
|
197
203
|
## The script determines status, not you
|
|
198
204
|
|
|
@@ -214,6 +220,9 @@ orchestrator.
|
|
|
214
220
|
separate
|
|
215
221
|
line, the runner reports whether the worktree was left touched, because a run interrupted
|
|
216
222
|
halfway is more dangerous than one that never happened. Do not restart.
|
|
223
|
+
- `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
|
|
224
|
+
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
225
|
+
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
217
226
|
|
|
218
227
|
A failed verification and a mismatch between the report and worktree cancel `OK` status — the
|
|
219
228
|
orchestrator branches on
|
|
@@ -221,7 +230,8 @@ the first word, so failure must not be buried on the third line. Found placehold
|
|
|
221
230
|
it, but they
|
|
222
231
|
are visible in the `Flags` line — the orchestrator decides what to do with them. The script return
|
|
223
232
|
code mirrors
|
|
224
|
-
the status: `0` / `1` / `3`. A nonzero code is not a reason to retry or finish the code yourself.
|
|
233
|
+
the status: `0` / `1` / `3` / `5`. A nonzero code is not a reason to retry or finish the code yourself.
|
|
234
|
+
Code `5` means `UNAVAILABLE`.
|
|
225
235
|
|
|
226
236
|
The run folder contains `status.json` (`running` / `finished` / `failed` / `abandoned`) and the
|
|
227
237
|
runner pid.
|
|
@@ -231,10 +241,10 @@ without the `--continue` it issued, the runner will reject that repeat run itsel
|
|
|
231
241
|
|
|
232
242
|
## Codex is unavailable
|
|
233
243
|
|
|
234
|
-
The runner
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
output.
|
|
244
|
+
The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
|
|
245
|
+
or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
|
|
246
|
+
`UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
|
|
247
|
+
`Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
238
248
|
Implementing the task instead of Codex is prohibited for any reason it fails.
|
|
239
249
|
|
|
240
250
|
## What a violation looks like
|
|
@@ -38,9 +38,11 @@ opinion, not a verdict.
|
|
|
38
38
|
another order — the runner chains runs by that label, and a made-up label is how a repeat run
|
|
39
39
|
hides. If the orchestrator did not give a required input, do not guess — start the runner without
|
|
40
40
|
its flag and return the runner's refusal verbatim.
|
|
41
|
-
- `continue` — when the
|
|
42
|
-
bare `--continue` flag even when its run name or reason looks malformed.
|
|
43
|
-
|
|
41
|
+
- `continue` — when the orchestrator's call carries a line beginning with `continue:` or `retry:`,
|
|
42
|
+
pass the bare `--continue` flag even when its run name or reason looks malformed. A `retry:`
|
|
43
|
+
grant lets the orchestrator repeat a failed pass (FAIL, LIMIT, UNAVAILABLE) of the same order
|
|
44
|
+
once; it is not the next pass. Do not inspect, repair, or swallow this grant; pass it through
|
|
45
|
+
and let the runner issue the refusal. A continuation is
|
|
44
46
|
assigned by the orchestrator, never chosen by you. After the verdict, return the exact attaching
|
|
45
47
|
output and stop; do not issue or invent another continuation. If no such grant line is present,
|
|
46
48
|
the flag must not be present.
|
|
@@ -90,8 +92,9 @@ If the runner refuses with an order id collision — the id already belongs to a
|
|
|
90
92
|
differs — that refusal is the whole answer: return `FAIL` with the runner's text, which names the
|
|
91
93
|
remedy. Never retry under a different id of your own choosing; the order id is the orchestrator's.
|
|
92
94
|
|
|
93
|
-
If the
|
|
94
|
-
flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
95
|
+
If the orchestrator's call carries a line beginning with `continue:` or `retry:`, add the bare
|
|
96
|
+
`--continue` flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
97
|
+
swallow the line;
|
|
95
98
|
the runner parses it and issues the refusal. If no such grant line is present, the flag must not be
|
|
96
99
|
present in the command at all.
|
|
97
100
|
|
|
@@ -176,9 +179,13 @@ and the wait can be repeated for free, so neither has any reason to restart anyt
|
|
|
176
179
|
- `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
|
|
177
180
|
review was not
|
|
178
181
|
completed; this is not a review failure and not a reason to restart.
|
|
182
|
+
- `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
|
|
183
|
+
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
184
|
+
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
179
185
|
|
|
180
|
-
The script return code mirrors the status: `0` / `1` / `3`. A nonzero code is not a reason to retry,
|
|
186
|
+
The script return code mirrors the status: `0` / `1` / `3` / `5`. A nonzero code is not a reason to retry,
|
|
181
187
|
not a reason to change the command, and not a reason to review it yourself.
|
|
188
|
+
Code `5` means `UNAVAILABLE`.
|
|
182
189
|
|
|
183
190
|
The run folder contains `status.json` (`running` / `finished` / `failed` / `abandoned`) and the
|
|
184
191
|
runner pid.
|
|
@@ -190,10 +197,10 @@ Token spending now goes into `meta.json` (the `review` subcommand did not print
|
|
|
190
197
|
|
|
191
198
|
## Codex is unavailable
|
|
192
199
|
|
|
193
|
-
The runner
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
output.
|
|
200
|
+
The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
|
|
201
|
+
or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
|
|
202
|
+
`UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
|
|
203
|
+
`Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
197
204
|
|
|
198
205
|
## What a violation looks like
|
|
199
206
|
|
|
@@ -38,13 +38,17 @@ files, do not run grep, do not retell the report, and do not reason about the ta
|
|
|
38
38
|
hides. If the orchestrator did not give a required input, do not guess — start the runner without
|
|
39
39
|
its flag and return the runner's refusal verbatim.
|
|
40
40
|
- The sub-questions are NOT yours to pass. They live in the task file the orchestrator wrote, under
|
|
41
|
-
a `Questions` heading, one Markdown list item each.
|
|
42
|
-
|
|
41
|
+
a `Questions` heading, one Markdown list item each. A question may carry the orderer's
|
|
42
|
+
`[context-only]` marker; the dispatcher never adds, removes or repairs it. You never read that
|
|
43
|
+
file, never edit it and never put a question on the command line: a question is free prose, and
|
|
44
|
+
free prose in an argument
|
|
43
45
|
makes the host stop applying the operator's permission rule, which is how a delegation dies on a
|
|
44
46
|
refusal. If the file holds no question, start the runner anyway and return its refusal verbatim.
|
|
45
|
-
- `continue` — when the
|
|
46
|
-
bare `--continue` flag even when its run name or reason looks malformed.
|
|
47
|
-
|
|
47
|
+
- `continue` — when the orchestrator's call carries a line beginning with `continue:` or `retry:`,
|
|
48
|
+
pass the bare `--continue` flag even when its run name or reason looks malformed. A `retry:`
|
|
49
|
+
grant lets the orchestrator repeat a failed pass (FAIL, LIMIT, UNAVAILABLE) of the same order
|
|
50
|
+
once; it is not the next pass. Do not inspect, repair, or swallow this grant; pass it through
|
|
51
|
+
and let the runner issue the refusal. A continuation is
|
|
48
52
|
assigned by the orchestrator, never chosen by you. After the verdict, return the exact attaching
|
|
49
53
|
output and stop; do not issue or invent another continuation. If no such grant line is present,
|
|
50
54
|
the flag must not be present.
|
|
@@ -85,8 +89,9 @@ Add `--effort "<value>"` only when the orchestrator named a depth, and only with
|
|
|
85
89
|
decides, which is the intended default — a placeholder copied from this template is refused before
|
|
86
90
|
Codex starts.
|
|
87
91
|
|
|
88
|
-
If the
|
|
89
|
-
flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
92
|
+
If the orchestrator's call carries a line beginning with `continue:` or `retry:`, add the bare
|
|
93
|
+
`--continue` flag even when its run name or reason looks malformed. Do not inspect, repair, or
|
|
94
|
+
swallow the line;
|
|
90
95
|
the runner parses it and issues the refusal. If no such grant line is present, the flag must not be
|
|
91
96
|
present in the command.
|
|
92
97
|
No value
|
|
@@ -170,6 +175,7 @@ confidence), `unknowns[]`, and `report_markdown`. An uncovered subquestion or an
|
|
|
170
175
|
evidence is FAIL;
|
|
171
176
|
the runner prints the line `Coverage: N/M subquestions`. The runner expands the last field into
|
|
172
177
|
`report.md`.
|
|
178
|
+
A `[context-only]` subquestion is answered with `startup:<source>` evidence.
|
|
173
179
|
An empty `answer` is FAIL with a path to the log, not a reason to choose different flags.
|
|
174
180
|
|
|
175
181
|
## The script determines status, not you
|
|
@@ -180,9 +186,13 @@ An empty `answer` is FAIL with a path to the log, not a reason to choose differe
|
|
|
180
186
|
- `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
|
|
181
187
|
task was not
|
|
182
188
|
completed; this is not a task failure and not a reason to restart.
|
|
189
|
+
- `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
|
|
190
|
+
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
191
|
+
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
183
192
|
|
|
184
|
-
The script return code mirrors the status: `0` / `1` / `3`. A nonzero code is not a reason to retry,
|
|
193
|
+
The script return code mirrors the status: `0` / `1` / `3` / `5`. A nonzero code is not a reason to retry,
|
|
185
194
|
not a reason to change the command, and not a reason to investigate on your own.
|
|
195
|
+
Code `5` means `UNAVAILABLE`.
|
|
186
196
|
|
|
187
197
|
The run folder contains `status.json` (`running` / `finished` / `failed` / `abandoned`) and the
|
|
188
198
|
runner pid.
|
|
@@ -194,10 +204,10 @@ which is the runner telling you the answer is on disk already.
|
|
|
194
204
|
|
|
195
205
|
## Codex is unavailable
|
|
196
206
|
|
|
197
|
-
The runner
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
output.
|
|
207
|
+
The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
|
|
208
|
+
or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
|
|
209
|
+
`UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
|
|
210
|
+
`Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
201
211
|
Performing the task manually instead of Codex is prohibited for any reason it fails.
|
|
202
212
|
|
|
203
213
|
## What a violation looks like
|
package/src/home/conventions.md
CHANGED
|
@@ -42,3 +42,12 @@ boundary is needed.
|
|
|
42
42
|
The 2026-08-05 step 2b acceptance found the abandoned-run check below the commit check. Test the
|
|
43
43
|
ordering invariant itself, and leave the incident in the comment so a later cleanup does not
|
|
44
44
|
silently undo it.
|
|
45
|
+
|
|
46
|
+
## Failing tests are evidence - Plan_73 B3 fixture incident
|
|
47
|
+
|
|
48
|
+
- Never change a fixture or an expectation only to make a failing test pass.
|
|
49
|
+
- When an existing test fails for a reason the task does not describe, leave it failing and name
|
|
50
|
+
it under deviations.
|
|
51
|
+
|
|
52
|
+
The 2026-09-30 Plan_73 B3 run rewrote a witness fixture so that a snapshot codec defect stopped
|
|
53
|
+
showing; that defect would have failed every build started on a clean tree.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
import { BRAND_STATE_DIR } from '../lib/brand-home.mjs';
|
|
2
|
+
import { BRAND_HOME, BRAND_STATE_DIR } from '../lib/brand-home.mjs';
|
|
3
|
+
import { createHomeWriter } from '../lib/home-write.mjs';
|
|
4
4
|
import { readJsonFileSync } from '../lib/json-file.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -49,8 +49,10 @@ export function takeTry(agentId, kind, maxBlocks = kind === STATE ? MAX_STATE_BL
|
|
|
49
49
|
const ids = Object.keys(seen);
|
|
50
50
|
if (ids.length > 200) ids.slice(0, ids.length - 200).forEach((id) => delete seen[id]);
|
|
51
51
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// Plan_65 B4: the budget is the registered guard-tries artifact, so purge can name and remove it.
|
|
53
|
+
const writer = createHomeWriter({ root: BRAND_HOME.root });
|
|
54
|
+
writer.mkdirSync('guard-tries', path.dirname(BLOCKED_FILE), { recursive: true });
|
|
55
|
+
writer.writeFileSync('guard-tries', BLOCKED_FILE, `${JSON.stringify(seen)}\n`);
|
|
54
56
|
} catch {
|
|
55
57
|
return 'untracked';
|
|
56
58
|
}
|
|
@@ -14,23 +14,23 @@
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { AGENTS } from '../lib/agents.mjs';
|
|
17
|
-
import {
|
|
17
|
+
import { recordHookDiagnostic } from '../lib/hook-diagnostics.mjs';
|
|
18
18
|
import { readJsonFileSync } from '../lib/json-file.mjs';
|
|
19
19
|
import {
|
|
20
20
|
diagnoseInput,
|
|
21
21
|
extractValue,
|
|
22
22
|
missingInputs,
|
|
23
|
-
|
|
23
|
+
parseGrant,
|
|
24
24
|
shellUnsafeInputs,
|
|
25
25
|
} from '../lib/required-inputs.mjs';
|
|
26
26
|
import { SUBAGENT_TOOLS } from '../lib/hook-definitions.mjs';
|
|
27
27
|
import { taskFingerprint } from '../lib/meta/chain.mjs';
|
|
28
|
-
import { conflictingOrderOwner } from '../lib/runner/order-owner.mjs';
|
|
28
|
+
import { conflictingOrderOwner, orderOwnerConflictText } from '../lib/runner/order-owner.mjs';
|
|
29
29
|
import { resolveProjectRunsDir } from '../lib/runner/project-dir.mjs';
|
|
30
30
|
import { runsRoot } from '../lib/runner/runs-root.mjs';
|
|
31
31
|
import { parseTaskDocument } from '../lib/runner/task-file.mjs';
|
|
32
|
+
import { parseTaskHeader, taskHeaderRefusal } from '../lib/task-header.mjs';
|
|
32
33
|
|
|
33
|
-
const DIAGNOSTICS_DIR = path.join(BRAND_STATE_DIR, 'diagnostics');
|
|
34
34
|
const GUARDED = new Set(Object.keys(AGENTS));
|
|
35
35
|
/**
|
|
36
36
|
* Both spellings of the subagent-launching tool, from the same list the installer builds its
|
|
@@ -58,12 +58,7 @@ try {
|
|
|
58
58
|
pass();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
fs.mkdirSync(DIAGNOSTICS_DIR, { recursive: true });
|
|
63
|
-
fs.writeFileSync(path.join(DIAGNOSTICS_DIR, 'order-gate.last.json'), `${JSON.stringify(input, null, 2)}\n`);
|
|
64
|
-
} catch {
|
|
65
|
-
// Diagnostics are a convenience, never a reason to fail the turn.
|
|
66
|
-
}
|
|
61
|
+
recordHookDiagnostic('order-gate', input);
|
|
67
62
|
|
|
68
63
|
if (!input || typeof input !== 'object' || Array.isArray(input)) pass();
|
|
69
64
|
if (!SUBAGENT_TOOL_NAMES.has(input.tool_name)) pass();
|
|
@@ -121,11 +116,29 @@ const readStatus = (file) => {
|
|
|
121
116
|
|
|
122
117
|
const orderId = extractValue(toolInput.prompt, 'order id');
|
|
123
118
|
const taskFile = extractValue(toolInput.prompt, 'task file');
|
|
124
|
-
|
|
119
|
+
// Plan_75 D1, TradeForge capacity incident: retries keep their order; the runner owns conflicting-grant refusal.
|
|
120
|
+
const grant = parseGrant(toolInput.prompt);
|
|
121
|
+
if (!orderId || !taskFile || grant?.kind || grant?.error) pass();
|
|
122
|
+
|
|
123
|
+
let rawTask;
|
|
124
|
+
try {
|
|
125
|
+
rawTask = fs.readFileSync(taskFile, 'utf8');
|
|
126
|
+
} catch {
|
|
127
|
+
pass();
|
|
128
|
+
}
|
|
129
|
+
// Plan_75 D5, 2026-10-03 20:42: malformed metadata must not disappear into the disk-state guard.
|
|
130
|
+
const parsed = parseTaskHeader(rawTask);
|
|
131
|
+
const headerRefusal = taskHeaderRefusal(parsed);
|
|
132
|
+
if (headerRefusal) deny(headerRefusal);
|
|
133
|
+
let taskHash;
|
|
134
|
+
try {
|
|
135
|
+
taskHash = taskFingerprint(parseTaskDocument(parsed.body).task);
|
|
136
|
+
} catch {
|
|
137
|
+
// A malformed Questions or Verify section is the runner's refusal to issue, with its own text.
|
|
138
|
+
pass();
|
|
139
|
+
}
|
|
125
140
|
|
|
126
141
|
try {
|
|
127
|
-
const taskDocument = parseTaskDocument(fs.readFileSync(taskFile, 'utf8'));
|
|
128
|
-
const taskHash = taskFingerprint(taskDocument.task);
|
|
129
142
|
const projectRunsDir = resolveProjectRunsDir(runsRoot(), input.cwd, { create: false }).dir;
|
|
130
143
|
// Per folder, not per directory: a run folder created a moment before its status.json, or any
|
|
131
144
|
// leftover beside the runs, would otherwise throw out of the whole check and silently disarm the
|
|
@@ -137,12 +150,7 @@ try {
|
|
|
137
150
|
const owner = conflictingOrderOwner(runs, orderId, taskHash);
|
|
138
151
|
if (owner) {
|
|
139
152
|
const ownerDir = path.join(projectRunsDir, owner.run);
|
|
140
|
-
deny(
|
|
141
|
-
`Order gate denied the Agent call because order id ${JSON.stringify(orderId)} already belongs ` +
|
|
142
|
-
`to run folder ${ownerDir} (slug ${owner.status.slug}, started_at ${owner.status.started_at}) ` +
|
|
143
|
-
'with a different task. Use a new order id, or provide an explicit continuation grant if ' +
|
|
144
|
-
'this is another pass of that order.',
|
|
145
|
-
);
|
|
153
|
+
deny(orderOwnerConflictText(owner, ownerDir, orderId));
|
|
146
154
|
}
|
|
147
155
|
} catch {
|
|
148
156
|
// The 2026-08-15 collision guard is diagnostic: uncertain disk state must not block real work.
|
|
@@ -33,6 +33,7 @@ import fs from 'node:fs';
|
|
|
33
33
|
import path from 'node:path';
|
|
34
34
|
import { AGENTS } from '../lib/agents.mjs';
|
|
35
35
|
import { BRAND_STATE_DIR } from '../lib/brand-home.mjs';
|
|
36
|
+
import { recordHookDiagnostic } from '../lib/hook-diagnostics.mjs';
|
|
36
37
|
import { runOrderMismatch, transcriptOrderId } from '../lib/dispatcher-order.mjs';
|
|
37
38
|
import { decideDispatcherStop, transcriptToolUses } from '../lib/dispatcher-stop.mjs';
|
|
38
39
|
import { recognizeHostRefusal } from '../lib/host-refusal.mjs';
|
|
@@ -111,13 +112,7 @@ try {
|
|
|
111
112
|
pass();
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
const diagnosticsDir = path.join(BRAND_STATE_DIR, 'diagnostics');
|
|
116
|
-
fs.mkdirSync(diagnosticsDir, { recursive: true });
|
|
117
|
-
fs.writeFileSync(path.join(diagnosticsDir, 'reply-guard.last.json'), `${JSON.stringify(input, null, 2)}\n`);
|
|
118
|
-
} catch {
|
|
119
|
-
// Diagnostics are a convenience, never a reason to fail the turn.
|
|
120
|
-
}
|
|
115
|
+
recordHookDiagnostic('reply-guard', input);
|
|
121
116
|
|
|
122
117
|
// A host that stops sending agent_type would let every dispatcher past the gate and this
|
|
123
118
|
// audit without a word (Plan_66 D2 (c)); say so instead of passing silently.
|
|
@@ -15,7 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
import { SHELL_TOOLS } from '../lib/hook-definitions.mjs';
|
|
16
16
|
import { parseJsonText } from '../lib/json-file.mjs';
|
|
17
17
|
import { splitRunChanges } from '../lib/meta/environment.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import { compareSnapshots } from '../lib/meta/snapshot-format.mjs';
|
|
19
19
|
import { outOfScope } from '../lib/meta/verdict.mjs';
|
|
20
20
|
import { git, worktreeSnapshot } from '../lib/runner/git-state.mjs';
|
|
21
21
|
import { runsRoot } from '../lib/runner/runs-root.mjs';
|
|
@@ -64,16 +64,16 @@ if (beforeText === null || scopeText === null) pass();
|
|
|
64
64
|
|
|
65
65
|
let outside;
|
|
66
66
|
try {
|
|
67
|
-
// changedPaths tolerates malformed rows; a hook must instead fail open on a damaged baseline.
|
|
68
|
-
if (beforeText.split(/\r?\n/).filter(Boolean).some((row) =>
|
|
69
|
-
!/^(?:\d+\t\d+|-\t-|U\t\d+)\t\S.*$/.test(row))) pass();
|
|
70
67
|
const patterns = scopeText.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
71
68
|
if (!patterns.length) pass();
|
|
72
69
|
// The shared snapshot returns empty output on git errors; do not call that a restored tree.
|
|
73
70
|
const repository = git(status.repo, ['rev-parse', '--is-inside-work-tree']);
|
|
74
71
|
if (repository.error || repository.status !== 0 || repository.stdout?.trim() !== 'true') pass();
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
// Plan_73 / 2026-09-30: a damaged or incompatible baseline proves neither a clean
|
|
73
|
+
// tree nor a stray edit. The advisory witness stays silent when the codec refuses it.
|
|
74
|
+
const comparison = compareSnapshots(beforeText, worktreeSnapshot(status.repo));
|
|
75
|
+
if (!comparison.ok) pass();
|
|
76
|
+
const { work } = splitRunChanges(dir, comparison.changed);
|
|
77
77
|
outside = outOfScope(work, patterns);
|
|
78
78
|
} catch {
|
|
79
79
|
pass();
|
package/src/home/lib/agents.mjs
CHANGED
|
@@ -12,7 +12,7 @@ export const AGENTS = {
|
|
|
12
12
|
'codex-build': { role: 'build', budget: 25, writes: true, result: 'result.json' },
|
|
13
13
|
'codex-review': { role: 'review', budget: 20, writes: false, result: 'review.json' },
|
|
14
14
|
// Plan_59: design advice needs a bounded scope pass before the continued decision pass.
|
|
15
|
-
'codex-advisor': { role: 'advisor', budget: { scope:
|
|
15
|
+
'codex-advisor': { role: 'advisor', budget: { scope: 10, advise: 25 }, writes: false, result: 'result.json' },
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export const agentRole = (agent) => AGENTS[agent]?.role;
|
|
@@ -6,16 +6,34 @@
|
|
|
6
6
|
* (`config-edit.mjs` keeps its own compare-and-swap publish for that reason).
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
|
-
import fs from 'node:fs';
|
|
10
9
|
import path from 'node:path';
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
function removeIfPresent(remove, temporary) {
|
|
12
|
+
try {
|
|
13
|
+
remove(temporary);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function publishJsonAtomic(file, record, operations) {
|
|
14
20
|
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
15
21
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
operations.mkdir(path.dirname(file), { recursive: true });
|
|
23
|
+
operations.write(temporary, `${JSON.stringify(record, null, 2)}\n`, { flag: 'wx' });
|
|
24
|
+
operations.rename(temporary, file);
|
|
18
25
|
} finally {
|
|
19
|
-
|
|
26
|
+
removeIfPresent(operations.unlink, temporary);
|
|
20
27
|
}
|
|
21
28
|
}
|
|
29
|
+
|
|
30
|
+
export function writeHomeJsonAtomic(writer, id, file, record) {
|
|
31
|
+
// Plan_65 B2: `state/` is shared by artifacts, so validate the file before mkdir can create it.
|
|
32
|
+
writer.assertArtifact(id, file);
|
|
33
|
+
return publishJsonAtomic(file, record, {
|
|
34
|
+
mkdir(directory, options) { return writer.mkdirSync(id, directory, options); },
|
|
35
|
+
write(temporary, contents, options) { return writer.writeFileSync(id, temporary, contents, options); },
|
|
36
|
+
rename(temporary, target) { return writer.renameSync(id, temporary, target); },
|
|
37
|
+
unlink(temporary) { return writer.unlinkSync(id, temporary); },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds a command on PATH the way the host shell would, without starting it.
|
|
3
|
+
* The runner and CLI share this home module because the home never imports cli/ (Plan_60 D2).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, statSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
export function envValue(env, name) {
|
|
9
|
+
const key = Object.keys(env).find((entry) => entry.toLowerCase() === name);
|
|
10
|
+
return key ? env[key] : '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function pathValue(env) {
|
|
14
|
+
return envValue(env, 'path');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveCommandOnPath(name, env = process.env) {
|
|
18
|
+
const extensions = process.platform === 'win32'
|
|
19
|
+
? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
|
|
20
|
+
: [''];
|
|
21
|
+
for (const directory of pathValue(env).split(path.delimiter).filter(Boolean)) {
|
|
22
|
+
for (const extension of extensions) {
|
|
23
|
+
const candidate = path.join(path.resolve(directory), `${name}${extension}`);
|
|
24
|
+
try {
|
|
25
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
26
|
+
} catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|