@lyupro/codex-bridge 0.6.12 → 0.6.14
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 +105 -0
- package/README.md +10 -8
- package/cli/command-registry.mjs +59 -18
- package/cli/doctor-installation.mjs +132 -111
- package/cli/doctor-load-witness.mjs +75 -0
- package/cli/doctor-runs.mjs +41 -6
- package/cli/doctor-sandbox.mjs +64 -0
- package/cli/doctor.mjs +26 -8
- package/cli/guidance.mjs +42 -0
- package/cli/host-inspection.mjs +1 -0
- package/cli/hosts.mjs +4 -3
- package/cli/install-record.mjs +4 -2
- package/cli/manifest.mjs +6 -3
- package/cli/read.mjs +1 -2
- package/cli/removal-outcomes.mjs +5 -2
- package/cli/removal-plan.mjs +4 -1
- package/cli/remove-layout.mjs +4 -4
- package/cli/run-lookup.mjs +14 -2
- package/cli/runs-inventory.mjs +10 -2
- package/cli/runs-move-copy.mjs +123 -0
- package/cli/runs-move-history.mjs +78 -0
- package/cli/runs-move-remove.mjs +92 -0
- package/cli/runs-move.mjs +96 -0
- package/cli/stop.mjs +1 -2
- package/cli/uninstall-purge.mjs +1 -0
- package/cli/uninstall.mjs +2 -0
- package/cli/update.mjs +12 -7
- package/package.json +4 -2
- package/scripts/home-write-inventory.mjs +17 -2
- package/scripts/isolated-roots.mjs +2 -1
- package/src/{agents → claude/agents}/codex-advisor.md +8 -7
- package/src/{agents → claude/agents}/codex-build.md +7 -6
- package/src/{agents → claude/agents}/codex-review.md +6 -5
- package/src/{agents → claude/agents}/codex-scout.md +7 -6
- package/src/{commands → claude/commands}/usage.md +9 -5
- package/src/claude/rules/core.md +29 -0
- package/src/{rules → codex/rules}/codex-bridge.rules +1 -1
- package/src/home/guidance/advisor.md +42 -0
- package/src/home/guidance/budget.md +37 -0
- package/src/home/guidance/concurrency.md +17 -0
- package/src/home/guidance/order.md +66 -0
- package/src/home/guidance/records.md +32 -0
- package/src/home/hooks/live-runs.mjs +2 -1
- package/src/home/hooks/load-witness.mjs +23 -0
- package/src/home/lib/claude-layout.mjs +1 -0
- package/src/home/lib/codex-home.mjs +8 -0
- package/src/home/lib/home-registry.mjs +21 -0
- package/src/home/lib/hook-definitions.mjs +2 -0
- package/src/home/lib/host-version.mjs +33 -14
- package/src/home/lib/load-witness.mjs +113 -0
- package/src/home/lib/meta/reply.mjs +1 -1
- package/src/home/lib/runner/codex-availability.mjs +11 -2
- package/src/home/lib/runner/exit-codes.mjs +1 -1
- package/src/home/lib/runner/launcher.mjs +10 -4
- package/src/home/lib/runner/preflight.mjs +12 -4
- package/src/home/lib/runner/retired-roots.mjs +81 -0
- package/src/home/lib/runner/run-autocommit.mjs +109 -0
- package/src/home/lib/runner/runs-root.mjs +47 -7
- package/src/home/lib/runner/sandbox-diagnosis.mjs +180 -0
- package/src/home/lib/runner/sandbox-probe.mjs +29 -1
- package/src/home/lib/runner/task-input.mjs +30 -0
- package/src/home/lib/runner/worker.mjs +5 -0
- /package/src/{commands → claude/commands}/env.md +0 -0
- /package/src/{commands → claude/commands}/model.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codex-build
|
|
3
|
-
description: Имплементация ВНЕ подписки Claude — правки кода, новые модули, рефакторинг по образцу, починка тестов. Работу выполняет Codex CLI (подписка ChatGPT) с правом записи в репозиторий строго в пределах обязательного `scope`, Claude платит только за постановку задачи и ≤5 строк выжимки. Отчёт и полный лог кладёт в
|
|
3
|
+
description: Имплементация ВНЕ подписки Claude — правки кода, новые модули, рефакторинг по образцу, починка тестов. Работу выполняет Codex CLI (подписка ChatGPT) с правом записи в репозиторий строго в пределах обязательного `scope`, Claude платит только за постановку задачи и ≤5 строк выжимки. Отчёт и полный лог кладёт в корень прогонов (`codex-bridge doctor` → `runsRoot`; по умолчанию `~/.lyupro/.codex-bridge/runs/`), в чат — короткая выжимка + путь. Ревью и приёмку делает отдельный агент Claude, не этот. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
|
|
4
4
|
model: haiku
|
|
5
5
|
tools: Bash
|
|
6
6
|
---
|
|
@@ -102,7 +102,7 @@ some files could not be judged.
|
|
|
102
102
|
separate
|
|
103
103
|
line, the runner reports whether the worktree was left touched, because a run interrupted
|
|
104
104
|
halfway is more dangerous than one that never happened. Do not restart.
|
|
105
|
-
- `UNAVAILABLE` — Codex is
|
|
105
|
+
- `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
|
|
106
106
|
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
107
107
|
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
108
108
|
|
|
@@ -123,10 +123,11 @@ without the orchestrator's `continue:` or `retry:` grant in the task-file header
|
|
|
123
123
|
|
|
124
124
|
## Codex is unavailable
|
|
125
125
|
|
|
126
|
-
The runner looks `codex` up on PATH and
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`
|
|
126
|
+
The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
|
|
127
|
+
an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
|
|
128
|
+
When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
|
|
129
|
+
a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
|
|
130
|
+
probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
130
131
|
Implementing the task instead of Codex is prohibited for any reason it fails.
|
|
131
132
|
|
|
132
133
|
## What a violation looks like
|
|
@@ -80,7 +80,7 @@ and the wait can be repeated for free, so neither has any reason to restart anyt
|
|
|
80
80
|
- `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
|
|
81
81
|
review was not
|
|
82
82
|
completed; this is not a review failure and not a reason to restart.
|
|
83
|
-
- `UNAVAILABLE` — Codex is
|
|
83
|
+
- `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
|
|
84
84
|
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
85
85
|
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
86
86
|
|
|
@@ -98,10 +98,11 @@ Token spending now goes into `meta.json` (the `review` subcommand did not print
|
|
|
98
98
|
|
|
99
99
|
## Codex is unavailable
|
|
100
100
|
|
|
101
|
-
The runner looks `codex` up on PATH and
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`
|
|
101
|
+
The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
|
|
102
|
+
an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
|
|
103
|
+
When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
|
|
104
|
+
a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
|
|
105
|
+
probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
105
106
|
|
|
106
107
|
## What a violation looks like
|
|
107
108
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codex-scout
|
|
3
|
-
description: Разведка и диагностика ВНЕ подписки Claude — исследование кодовой базы, поиск причины бага, ревью, сбор фактов. Работу выполняет Codex CLI (подписка ChatGPT), Claude платит только за постановку задачи и ≤5 строк выжимки. Строго read-only: писать в репозиторий физически не может. Подробный отчёт кладёт в
|
|
3
|
+
description: Разведка и диагностика ВНЕ подписки Claude — исследование кодовой базы, поиск причины бага, ревью, сбор фактов. Работу выполняет Codex CLI (подписка ChatGPT), Claude платит только за постановку задачи и ≤5 строк выжимки. Строго read-only: писать в репозиторий физически не может. Подробный отчёт кладёт в корень прогонов (`codex-bridge doctor` → `runsRoot`; по умолчанию `~/.lyupro/.codex-bridge/runs/`), в чат — короткая выжимка + путь. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
|
|
4
4
|
model: haiku
|
|
5
5
|
tools: Bash
|
|
6
6
|
---
|
|
@@ -67,7 +67,7 @@ An empty `answer` is FAIL with a path to the log, not a reason to choose differe
|
|
|
67
67
|
- `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
|
|
68
68
|
task was not
|
|
69
69
|
completed; this is not a task failure and not a reason to restart.
|
|
70
|
-
- `UNAVAILABLE` — Codex is
|
|
70
|
+
- `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
|
|
71
71
|
sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
|
|
72
72
|
not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
|
|
73
73
|
|
|
@@ -85,10 +85,11 @@ which is the runner telling you the answer is on disk already.
|
|
|
85
85
|
|
|
86
86
|
## Codex is unavailable
|
|
87
87
|
|
|
88
|
-
The runner looks `codex` up on PATH and
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`
|
|
88
|
+
The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
|
|
89
|
+
an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
|
|
90
|
+
When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
|
|
91
|
+
a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
|
|
92
|
+
probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
|
|
92
93
|
Performing the task manually instead of Codex is prohibited for any reason it fails.
|
|
93
94
|
|
|
94
95
|
## What a violation looks like
|
|
@@ -11,9 +11,9 @@ allowed-tools: Bash
|
|
|
11
11
|
Count how much ChatGPT quota the runs delegated to Codex burned. This is spending on the Codex side,
|
|
12
12
|
not on the Claude side.
|
|
13
13
|
|
|
14
|
-
The root of the run folders comes from
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
The root of the run folders comes from the runner's own resolver, so usage counts exactly where
|
|
15
|
+
runs are written: the `CODEX_RUNS_ROOT` override, `~/.lyupro/.codex-bridge/runs/` after the move,
|
|
16
|
+
or `~/.claude/codex-runs/` while the move is pending.
|
|
17
17
|
|
|
18
18
|
The source is the `meta.json` in each run folder: it holds the spending, the model and the sandbox.
|
|
19
19
|
Transport files are not kept in git and may be deleted, so parsing them is not allowed — the
|
|
@@ -33,11 +33,14 @@ Run exactly this command and recompute nothing by hand:
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
node -e "
|
|
36
|
-
const fs=require('fs'),path=require('path');
|
|
37
|
-
const
|
|
36
|
+
const fs=require('fs'),path=require('path'),os=require('os');
|
|
37
|
+
const home=(process.env.CODEX_BRIDGE_HOME||'').trim()||path.join(os.homedir(),'.lyupro','.codex-bridge');
|
|
38
|
+
import(require('url').pathToFileURL(path.join(home,'lib','runner','runs-root.mjs')).href).then(m=>{
|
|
39
|
+
const root=m.runsRoot();
|
|
38
40
|
if(!fs.existsSync(root)){console.log('No runs yet.');process.exit(0)}
|
|
39
41
|
const runs=[],unknown=[];
|
|
40
42
|
for(const proj of fs.readdirSync(root)){
|
|
43
|
+
if(proj==='.git')continue;
|
|
41
44
|
const pdir=path.join(root,proj);
|
|
42
45
|
if(!fs.statSync(pdir).isDirectory())continue;
|
|
43
46
|
let repo=null;
|
|
@@ -81,6 +84,7 @@ if(runs.length){
|
|
|
81
84
|
console.log('');console.log('Most expensive runs:');
|
|
82
85
|
runs.sort((a,b)=>b.tokens-a.tokens).slice(0,5).forEach(r=>console.log(' '+fmt(r.tokens).padStart(11)+' '+r.proj+'/'+r.run));
|
|
83
86
|
}
|
|
87
|
+
}).catch(e=>{console.error(e.message);process.exit(1)});
|
|
84
88
|
"
|
|
85
89
|
```
|
|
86
90
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# codex-bridge — delegation core
|
|
2
|
+
|
|
3
|
+
Installed and removed by the `codex-bridge` package; `codex-bridge update` keeps it equal to the package. Change the
|
|
4
|
+
package, not this file.
|
|
5
|
+
|
|
6
|
+
Four dispatcher agents run work on a Codex CLI subscription: `codex-scout` (read-only research), `codex-build`
|
|
7
|
+
(writes code inside its `scope`), `codex-review` (second opinion on a diff) and `codex-advisor` (design advice before
|
|
8
|
+
code: a `scope` phase, then `advise`).
|
|
9
|
+
|
|
10
|
+
- The call is one line, `task file: <absolute path>`. Labels go in the task-file header, free text in its body. A
|
|
11
|
+
refused call costs nothing: read the refusal, fix the task file, call again.
|
|
12
|
+
- Before writing a task file, run `codex-bridge guidance <topic>` and follow it: `order` (header labels, scope),
|
|
13
|
+
`advisor` (two phases, the `continue:` grant), `budget` (run deadline, slicing), `records` (finding and reading
|
|
14
|
+
runs), `concurrency` (parallel and writing runs). `codex-bridge guidance` lists the topics. If it fails, stop and
|
|
15
|
+
show its output to the operator — never guess the contract.
|
|
16
|
+
- Every `codex-build` task file carries exactly one `advice:` line.
|
|
17
|
+
- A writing run has a hard deadline: slice the task before the launch, not after a failure. After a deadline `FAIL`,
|
|
18
|
+
look at `git status` before deciding anything.
|
|
19
|
+
- One writing run per working tree; read-only runs may go alongside it.
|
|
20
|
+
- `LIMIT` means the ChatGPT quota window is spent, `UNAVAILABLE` means Codex is signed out on this host. Neither is a
|
|
21
|
+
task error: do not retry the same bridge.
|
|
22
|
+
A refusal starting `Codex CLI readiness unconfirmed` is not `UNAVAILABLE`: do not skip the bridge; follow its second
|
|
23
|
+
line — check `codex --version` for up to ~2 minutes, then repeat the same order exactly once if it answers, otherwise
|
|
24
|
+
hand it to the next executor and name that in the summary. If the repeat gets the same refusal, hand it to the next
|
|
25
|
+
executor too; no second repeat.
|
|
26
|
+
- `OK — scope: insufficient` from `codex-advisor` is a success: it names the paths the `advise` phase needs.
|
|
27
|
+
- Stop a live run with `codex-bridge stop <run>` before `TaskStop`; `TaskStop` alone leaves the run writing.
|
|
28
|
+
- A run's folder is the `ATTACH=` path in the dispatcher's reply. Earlier runs: `codex-bridge projects [<project>]
|
|
29
|
+
--json`; render one with `codex-bridge read <run>`. Never derive the run root from where the package is installed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# codex-bridge installs this file to constrain delegated Codex runs.
|
|
2
2
|
# It exists because a delegated run must not rewrite the operator's repository history or branches.
|
|
3
|
-
# Check it with: codex execpolicy check --rules src/rules/codex-bridge.rules -- <command> <args>.
|
|
3
|
+
# Check it with: codex execpolicy check --rules src/codex/rules/codex-bridge.rules -- <command> <args>.
|
|
4
4
|
# Codex combines this file with other active rule files, and the strictest matching decision wins.
|
|
5
5
|
# Known limit: prefix rules cannot detect shell redirection into .git/HEAD or equivalent indirect writes.
|
|
6
6
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# advisor
|
|
2
|
+
|
|
3
|
+
Use a scope pass and a granted advise pass under one order id to obtain an evidence-backed design recommendation.
|
|
4
|
+
|
|
5
|
+
## Two phases, one order
|
|
6
|
+
|
|
7
|
+
Start with `phase: scope`. This read-only pass checks whether the supplied paths cover the question and names missing paths and expected risks.
|
|
8
|
+
|
|
9
|
+
Continue with `phase: advise` under the same order id. Its task-file header must contain a `continue:` grant naming the successful scope run by its bare run-folder name (the last segment of the `RUN=`/`ATTACH=` path, never a path), with a reason for proceeding: `continue: <run folder> — <reason>`. The grant authorizes the second phase; a successful scope result alone does not grant it.
|
|
10
|
+
|
|
11
|
+
`OK — scope: sufficient` means the supplied context covers the question. `OK — scope: insufficient; N paths named` with a `Missing:` line is also success: it identifies the context advise needs. It is not FAIL or LIMIT. The advise boundary includes the original paths and that scope result's `missing_paths`.
|
|
12
|
+
|
|
13
|
+
Never continue a successful scope as another scope: that would consume the continuation intended for advise. A failed scope or advise pass is repeated only with a `retry:` grant naming that failed run, under the same order id. A changed question needs a new scope pass.
|
|
14
|
+
|
|
15
|
+
## Task-file bodies
|
|
16
|
+
|
|
17
|
+
Both phases use the following body form after the lowercase header and a blank line:
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
## Question
|
|
21
|
+
Which boundary should own the validation contract?
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
- local-boundary: Keep validation within the owning component.
|
|
25
|
+
- shared-contract: Share validation between the consuming components.
|
|
26
|
+
|
|
27
|
+
## Paths
|
|
28
|
+
- src/component.mjs
|
|
29
|
+
- tests/component.test.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`## Question` contains the design question. `## Options` contains at least two unique option ids matching `[a-z0-9][a-z0-9-]*`, each on a `- <option_id>: <description>` line. Keep options neutral: no preference markers or wording such as `recommend`, `prefer`, `рекоменд`, `предпочт`, `(✓)` or `★`. Biased or malformed options are refused before quota is spent.
|
|
33
|
+
|
|
34
|
+
`## Paths` contains at least one repository-relative path, one per list line. In scope, these are the read boundary. In advise, the successful scope run's `missing_paths` are also available. Citations must be real `path:line` addresses within that boundary; invented or out-of-list addresses fail validation.
|
|
35
|
+
|
|
36
|
+
For the scope task, declare the scope phase in the header and supply this question, neutral options and initial paths. For the advise task, declare the advise phase and the continuation grant in the header, preserving the question and options and incorporating the scope findings.
|
|
37
|
+
|
|
38
|
+
## Use the answer
|
|
39
|
+
|
|
40
|
+
An advise answer recommends one option id and explains the choice, rejects alternatives with their costs, gives the strongest counterargument and a pre-mortem of risks. Its short form begins `OK — recommend <option_id>: …` and includes `Rejected:`, `Counter:` and `Risks:`. The full structured result and report stay in the run folder.
|
|
41
|
+
|
|
42
|
+
To cite it in a build task, use exactly one `advice:` line whose value is the absolute folder of the successful advise run. Its `meta.json` must identify `codex-advisor`, phase advise, status OK. A scope folder, report filename, failed advise or recommendation text is not a valid advice reference.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# budget
|
|
2
|
+
|
|
3
|
+
Slice each order before launch so its work and verification fit the agent's hard wall-clock deadline.
|
|
4
|
+
|
|
5
|
+
## Budgets
|
|
6
|
+
|
|
7
|
+
The agent registry defines these budgets:
|
|
8
|
+
|
|
9
|
+
| Agent | Phase | Minutes |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| codex-scout | — | 15 |
|
|
12
|
+
| codex-build | — | 25 |
|
|
13
|
+
| codex-review | — | 20 |
|
|
14
|
+
| codex-advisor | scope | 10 |
|
|
15
|
+
| codex-advisor | advise | 25 |
|
|
16
|
+
|
|
17
|
+
Advisor phases have separate deadlines; scope and advise are not one combined writing budget. The deadline is enforced by the runner, not an estimate to extend while work is in progress.
|
|
18
|
+
|
|
19
|
+
## Slice a writing run
|
|
20
|
+
|
|
21
|
+
Keep a build order to one layer and a few writing files, normally 1–3 plus their targeted tests. Split cross-layer changes into separately verifiable orders before launch. Account for reading, implementation and verification within the same 25 minutes.
|
|
22
|
+
|
|
23
|
+
The task file must say:
|
|
24
|
+
|
|
25
|
+
- The concrete result and exact write scope, including any new files.
|
|
26
|
+
- The budget, the small set of sources to read, and an instruction to implement immediately without exploring beyond those sources.
|
|
27
|
+
- The behavior to preserve and what remains outside this slice.
|
|
28
|
+
- The targeted check under `## Verify`, as one command line.
|
|
29
|
+
- No full suite, build or smoke inside the run; leave broad integration checks outside this slice.
|
|
30
|
+
|
|
31
|
+
Do not turn a small implementation order into a repository audit. Narrow the order rather than hoping the deadline will spare unfinished work.
|
|
32
|
+
|
|
33
|
+
## After a deadline FAIL
|
|
34
|
+
|
|
35
|
+
Inspect `git status` and the working-tree diff first: deadline termination can leave useful or incomplete changes. Then inspect the available result, report and events, using `codex-bridge read <run>` to render the event stream. Check which requested work and verification actually completed before deciding the next order.
|
|
36
|
+
|
|
37
|
+
Never resend the same order unchanged. Preserve the existing work and define a smaller remaining slice with an appropriate `continue:` or `retry:` grant and its reason. A deadline FAIL is not evidence that nothing was written or that the work is complete.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# concurrency
|
|
2
|
+
|
|
3
|
+
Run readers in parallel while keeping every writing run exclusive to its working tree.
|
|
4
|
+
|
|
5
|
+
## Readers and writers
|
|
6
|
+
|
|
7
|
+
Read-only `codex-scout`, `codex-review` and both `codex-advisor` phases may run in parallel freely, including alongside a writing run.
|
|
8
|
+
|
|
9
|
+
Only one writing run may be active in a working tree. A second writing run in that tree is refused, even when the two orders name different files. Parallel writers require separate worktrees, each with its own working tree and write boundary.
|
|
10
|
+
|
|
11
|
+
The writing runner snapshots the tree before and after its run, then compares the actual changes with the reported files and order scope. Another writer's edits enter that same snapshot and appear to belong to this run. Disjoint file lists therefore do not isolate writers in one tree. Keep other file-changing operations out of a live writer's tree as well.
|
|
12
|
+
|
|
13
|
+
## Stop a live run
|
|
14
|
+
|
|
15
|
+
Use the run path from its receipt and call `codex-bridge stop <run>` before `TaskStop`. The bridge stop terminates the Codex run and records FAIL. `TaskStop` alone stops the dispatcher task while the Codex process can keep writing.
|
|
16
|
+
|
|
17
|
+
Inspect the resulting status and working-tree changes before launching another writer. Stopping a run does not undo the files it already changed.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# order
|
|
2
|
+
Write a task file that gives each dispatcher the complete order and the build's file boundary.
|
|
3
|
+
|
|
4
|
+
## Call and header
|
|
5
|
+
|
|
6
|
+
The call is exactly one line: `task file: <task-file path from the orchestrator>`.
|
|
7
|
+
The path must be absolute and name a file the orchestrator wrote with its file tool.
|
|
8
|
+
The dispatcher never creates, reads or rewrites it. The whole order lives in that file's header.
|
|
9
|
+
The runner checks the header, never the dispatcher. Free text belongs in the task file.
|
|
10
|
+
|
|
11
|
+
Start at the first line with consecutive lowercase `label: value` lines, then a blank line and the task body.
|
|
12
|
+
Labels must use their exact spelling, start at column one and have a non-empty value; duplicates are refused.
|
|
13
|
+
The header ends at the first line that is not a recognized label line, including a blank line.
|
|
14
|
+
Do not put grants or advice below the header, even in a code fence; quote examples with `>` or reword them inside a sentence.
|
|
15
|
+
|
|
16
|
+
| Agent | Required labels | Conditional labels | Optional labels |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| codex-scout | `order id` | `continue`, `retry` | `repository`, `slug`, `effort` |
|
|
19
|
+
| codex-build | `order id`, `scope`, `advice` | `continue`, `retry` | `repository`, `scope new`, `slug`, `effort` |
|
|
20
|
+
| codex-review | `order id` | `continue`, `retry` | `repository`, `slug`, `effort`, `changeset` |
|
|
21
|
+
| codex-advisor | `order id`, `phase` | `continue`, `retry` | `repository`, `slug`, `effort` |
|
|
22
|
+
|
|
23
|
+
Both grant labels are conditional when this pass continues or repeats a named run. Keep exactly one grant label:
|
|
24
|
+
`continue` names the continued run; `retry` repeats a failed pass and does not authorize the next pass.
|
|
25
|
+
Each value names a bare run folder (not a path, `.` or `..`) and a non-placeholder reason, separated by ` — `, ` - ` or `:`.
|
|
26
|
+
For advisor, pass `scope` first, then `advise` with a `continue:` grant naming the scope run of the same order.
|
|
27
|
+
|
|
28
|
+
`repository` is the repository path; `slug` names this pass without replacing the order id.
|
|
29
|
+
The runner checks accepted `effort` values. `changeset` is `uncommitted`, `base:<branch>` or `commit:<sha>`.
|
|
30
|
+
Keep header values short and free of forbidden shell sequences. Empty or placeholder values are refused;
|
|
31
|
+
`phase: scope` and `effort: none` are real values.
|
|
32
|
+
|
|
33
|
+
## File boundary
|
|
34
|
+
|
|
35
|
+
`scope`: Comma-separated globs relative to the repository root, listing every file the run may touch — including each caller of what changes, not only the file being edited. Anything outside the list fails the run.
|
|
36
|
+
|
|
37
|
+
`scope new`: Comma-separated globs for new files this build may create, relative to the repository root.
|
|
38
|
+
`scope` must declare at least one existing path pattern; `scope new` does not replace it.
|
|
39
|
+
Never name service folders in scope: `.git/`, `.claude/`, `.codex/`, `.omx/`, `node_modules/`.
|
|
40
|
+
|
|
41
|
+
Example build header:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
order id: guidance-order-text
|
|
45
|
+
scope: tests/guidance-topics.test.mjs
|
|
46
|
+
scope new: src/home/guidance/order.md
|
|
47
|
+
advice: docs-only
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Advice
|
|
52
|
+
|
|
53
|
+
Every build task header contains exactly one `advice:` line. Its value is an absolute path to an existing run folder
|
|
54
|
+
whose `meta.json` identifies a `codex-advisor` advise pass with status `OK`, or exactly `mechanical`, `revert`,
|
|
55
|
+
`docs-only`, or `test-only`. Anything else is refused before the run starts.
|
|
56
|
+
|
|
57
|
+
## Verify
|
|
58
|
+
|
|
59
|
+
Put the verification command in the task file under a `## Verify` heading, one line.
|
|
60
|
+
The dispatcher never reads that file and never puts the command on the command line.
|
|
61
|
+
|
|
62
|
+
## Free refusals
|
|
63
|
+
|
|
64
|
+
A pre-start refusal creates no run folder and spends no quota; the same order id remains reusable for the corrected order.
|
|
65
|
+
A refusal is the dispatcher's whole answer, not a reason to change the call or task file and retry itself.
|
|
66
|
+
Repeating the same call for a run already in flight joins that run and costs no quota; a different piece of work needs a new label.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# records
|
|
2
|
+
|
|
3
|
+
Find run artifacts through runner receipts and the project inventory without naming or deriving a run-store root.
|
|
4
|
+
|
|
5
|
+
## Find a run
|
|
6
|
+
|
|
7
|
+
Use the absolute run-folder path printed in the runner's `RUN=` or `ATTACH=` line. Keep that returned path as the run identity; do not reconstruct it from a timestamp, project name, package installation, home directory or another run.
|
|
8
|
+
|
|
9
|
+
For earlier runs, use `codex-bridge projects [<project>] --json`. Use the run object's `path` field, which is the absolute run-folder path. If no project is supplied, first find the project in the inventory; do not concatenate names to invent a path.
|
|
10
|
+
|
|
11
|
+
Render the selected run with `codex-bridge read <run>`, passing the returned run path. This renders its structured event stream. The inventory and receipt are the lookup contract; diagnostics are not a substitute for them.
|
|
12
|
+
|
|
13
|
+
## Read the artifacts
|
|
14
|
+
|
|
15
|
+
A started run records its lifecycle and output in its run folder:
|
|
16
|
+
|
|
17
|
+
- `meta.json`: agent, phase and final status; use it to identify an advisor advise pass before citing it in `advice:`.
|
|
18
|
+
- `status.json`: lifecycle state (`running`, `finished`, `failed` or `abandoned`) and runner PID.
|
|
19
|
+
- `result.json`: structured scout, build or advisor result; build includes `summary`, `changes`, `verify_command`, `verify_passed`, `leftovers` and `report_markdown`.
|
|
20
|
+
- `review.json`: the review agent's structured result instead of `result.json`.
|
|
21
|
+
- `report.md`: the full report; the runner expands a build result's `report_markdown` into this file.
|
|
22
|
+
- `events.jsonl` and `stderr.log`: transport evidence for progress, deadline or startup failures.
|
|
23
|
+
|
|
24
|
+
A failed or interrupted run may have no complete result. Read the recorded status and available evidence rather than treating a missing report as an empty successful run.
|
|
25
|
+
|
|
26
|
+
## Availability verdicts
|
|
27
|
+
|
|
28
|
+
`LIMIT` means the ChatGPT quota window is exhausted, not a task defect. For a writing run, also check the runner's indication of whether the tree was touched.
|
|
29
|
+
|
|
30
|
+
`UNAVAILABLE` means Codex is signed out on this host; the task could not run. A pre-start availability refusal may leave no run folder. Neither verdict is a reason to retry the same bridge. Preserve the verdict and any existing artifacts.
|
|
31
|
+
|
|
32
|
+
A refusal starting `Codex CLI readiness unconfirmed:` is not `UNAVAILABLE`: it leaves no run folder and costs no quota. Do not skip the bridge; follow its second line — check `codex --version` for up to ~2 minutes. When it answers, repeat the same order exactly once; if it does not, hand the order to the next executor and name that in the summary. If the repeat gets the same refusal, hand the order to the next executor as well; no second repeat.
|
|
@@ -107,7 +107,8 @@ export function allLiveRuns(runsRoot, options = {}) {
|
|
|
107
107
|
}
|
|
108
108
|
const result = [];
|
|
109
109
|
for (const project of projects) {
|
|
110
|
-
|
|
110
|
+
// Plan_77 B5b makes the moved store a git repository; its metadata is not a project.
|
|
111
|
+
if (!project.isDirectory() || project.name === '.git') continue;
|
|
111
112
|
result.push(...liveRuns(path.join(runsRoot, project.name), options));
|
|
112
113
|
}
|
|
113
114
|
return result;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Plan_64 D11: only InstructionsLoaded witnesses the host loading the rules core.
|
|
4
|
+
* The 2026-10-10 live run fired before the transcript existed, so persist now and resolve later.
|
|
5
|
+
* Observation must never refuse a host action, including doctor's empty-stdin dry run.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { BRAND_STATE_DIR } from '../lib/brand-home.mjs';
|
|
9
|
+
import { parseJsonText } from '../lib/json-file.mjs';
|
|
10
|
+
import { recordCoreLoad } from '../lib/load-witness.mjs';
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
14
|
+
if (raw.trim()) {
|
|
15
|
+
const input = parseJsonText('stdin', raw);
|
|
16
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
17
|
+
await recordCoreLoad({ stateDir: BRAND_STATE_DIR, input });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// D11: lost evidence is not a refusal; preserve corrupt state for inspection.
|
|
22
|
+
}
|
|
23
|
+
process.exit(0);
|
|
@@ -8,6 +8,7 @@ export function claudePaths(root) {
|
|
|
8
8
|
return {
|
|
9
9
|
agentsDir: path.join(root, 'agents', 'codex-bridge'),
|
|
10
10
|
commandsDir: path.join(root, 'commands', 'codex-bridge'),
|
|
11
|
+
rulesDir: path.join(root, 'rules', 'codex-bridge'),
|
|
11
12
|
legacyAgentsDir: path.join(root, 'agents', 'codex'),
|
|
12
13
|
legacyCommandsDir: path.join(root, 'commands', 'codex'),
|
|
13
14
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Keeps one Codex home resolver for readers and the path currently built inline in cli/hosts.mjs:26. */
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function resolveCodexHome({ env = process.env, homedir = os.homedir() } = {}) {
|
|
6
|
+
return typeof env.CODEX_HOME === 'string' && env.CODEX_HOME.length > 0
|
|
7
|
+
? env.CODEX_HOME : path.join(homedir, '.codex');
|
|
8
|
+
}
|
|
@@ -90,6 +90,13 @@ const entries = [
|
|
|
90
90
|
primary: ['state/dispatcher-model.json'],
|
|
91
91
|
sides: ['lock', 'atomic-temporary'],
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
id: 'runs-move-record',
|
|
95
|
+
removal: 'purge-only',
|
|
96
|
+
consequence: 'The package forgets that run records moved, and new runs write to the old store again while it still exists.',
|
|
97
|
+
primary: ['state/runs-root.json'],
|
|
98
|
+
sides: ['atomic-temporary'],
|
|
99
|
+
},
|
|
93
100
|
{
|
|
94
101
|
id: 'host-observations',
|
|
95
102
|
removal: 'purge-only',
|
|
@@ -97,6 +104,13 @@ const entries = [
|
|
|
97
104
|
primary: ['state/host-observations.json'],
|
|
98
105
|
sides: ['lock', 'atomic-temporary'],
|
|
99
106
|
},
|
|
107
|
+
{
|
|
108
|
+
id: 'load-witness',
|
|
109
|
+
removal: 'purge-only',
|
|
110
|
+
consequence: 'Doctor forgets which sessions witnessed the rules core loading until the next session start.',
|
|
111
|
+
primary: ['state/load-witness.json'],
|
|
112
|
+
sides: ['lock', 'atomic-temporary'],
|
|
113
|
+
},
|
|
100
114
|
{
|
|
101
115
|
id: 'guard-tries',
|
|
102
116
|
removal: 'purge-only',
|
|
@@ -124,6 +138,13 @@ export const HOME_ARTIFACTS = Object.freeze(entries.map((entry) => Object.freeze
|
|
|
124
138
|
sides: Object.freeze([...entry.sides]),
|
|
125
139
|
})));
|
|
126
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Plan_77 D1: run records live in this folder of the home once they move out of ~/.claude. They are
|
|
143
|
+
* operator data, not an artifact of the package: the folder is never walked or removed, and naming it
|
|
144
|
+
* here keeps the resolver and the removal plan on one spelling.
|
|
145
|
+
*/
|
|
146
|
+
export const RUN_RECORDS_DIR = 'runs';
|
|
147
|
+
|
|
127
148
|
export const HOME_DIRECTORIES = Object.freeze([
|
|
128
149
|
'state',
|
|
129
150
|
'state/dispatchers',
|
|
@@ -67,6 +67,8 @@ export const LOCKED_TOOL_MATCHER = [...WRITE_TOOLS, ...SHELL_TOOLS].join('|');
|
|
|
67
67
|
* dispatcher must consume this one list instead of guessing a name from a path at its call site.
|
|
68
68
|
*/
|
|
69
69
|
export const HOOK_DEFINITIONS = Object.freeze([
|
|
70
|
+
// Plan_64 D11: loading evidence observes only; this hook can never refuse anything.
|
|
71
|
+
Object.freeze({ name: 'load-witness', event: 'InstructionsLoaded', matcher: '*', file: 'load-witness.mjs' }),
|
|
70
72
|
Object.freeze({ name: 'reply-guard', event: 'SubagentStop', matcher: '*', file: 'reply-guard.mjs' }),
|
|
71
73
|
Object.freeze({ name: 'order-gate', event: 'PreToolUse', matcher: SUBAGENT_TOOL_MATCHER, file: 'order-gate.mjs' }),
|
|
72
74
|
Object.freeze({ name: 'worktree-lock', event: 'PreToolUse', matcher: LOCKED_TOOL_MATCHER, file: 'worktree-lock.mjs' }),
|
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
/** Reads the host version from
|
|
1
|
+
/** Reads the host version from a session transcript after the 2026-09-25 PATH host identity incident. */
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import { parseJsonText } from './json-file.mjs';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const VERSION = /^\d+\.\d+\.\d+/;
|
|
6
|
+
|
|
7
|
+
function versionIn(transcriptPath, lines, sessionId, newestFirst) {
|
|
8
|
+
const order = newestFirst ? [...lines.keys()].reverse() : lines.keys();
|
|
9
|
+
for (const index of order) {
|
|
10
|
+
try {
|
|
11
|
+
const entry = parseJsonText(transcriptPath, lines[index]);
|
|
12
|
+
// Plan_64 D11: a neighboring session's version cannot identify this load witness.
|
|
13
|
+
if (sessionId && entry?.sessionId !== sessionId) continue;
|
|
14
|
+
if (typeof entry?.version === 'string' && VERSION.test(entry.version)) return entry.version;
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function slice(handle, position, length) {
|
|
21
|
+
const buffer = Buffer.alloc(length);
|
|
22
|
+
await handle.read(buffer, 0, length, position);
|
|
23
|
+
return buffer.toString('utf8').split(/\r?\n/);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A live session's newest lines name its host, so the tail answers the shell-command observers. A finished session
|
|
28
|
+
* may end in a 135 KB attachment line and metadata without `version` (B6b live run, 2026-10-10), so a named session is
|
|
29
|
+
* also looked up from the head: one session runs on one host, and its first user line already carries the version.
|
|
30
|
+
*/
|
|
31
|
+
export async function transcriptHostVersion(transcriptPath, { tailBytes = 65536, headBytes = 1048576, sessionId } = {}) {
|
|
32
|
+
const session = typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : null;
|
|
6
33
|
try {
|
|
7
34
|
const handle = await fs.open(transcriptPath, 'r');
|
|
8
35
|
try {
|
|
9
36
|
const { size } = await handle.stat();
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
15
|
-
try {
|
|
16
|
-
const entry = parseJsonText(transcriptPath, lines[index]);
|
|
17
|
-
if (typeof entry?.version === 'string' && /^\d+\.\d+\.\d+/.test(entry.version)) {
|
|
18
|
-
return entry.version;
|
|
19
|
-
}
|
|
20
|
-
} catch {}
|
|
21
|
-
}
|
|
37
|
+
const tailLength = Math.min(size, tailBytes);
|
|
38
|
+
const fromTail = versionIn(transcriptPath, await slice(handle, size - tailLength, tailLength), session, true);
|
|
39
|
+
if (fromTail !== null || !session) return fromTail;
|
|
40
|
+
return versionIn(transcriptPath, await slice(handle, 0, Math.min(size, headBytes)), session, false);
|
|
22
41
|
} finally {
|
|
23
42
|
await handle.close();
|
|
24
43
|
}
|