@lyupro/codex-bridge 0.6.1 → 0.6.4

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 CHANGED
@@ -1,155 +1,155 @@
1
- # codex-bridge
2
-
3
- **Delegate implementation, reconnaissance and review from Claude Code to Codex — six host guards, zero runtime dependencies, zero build steps.**
4
-
5
- [![npm version](https://img.shields.io/npm/v/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
6
- [![Node.js](https://img.shields.io/node/v/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
7
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
-
9
- [![npm downloads](https://img.shields.io/npm/dm/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
10
- [![npm unpacked size](https://img.shields.io/npm/unpacked-size/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
11
- [![dependencies: zero](https://img.shields.io/badge/dependencies-zero-brightgreen)](package.json)
12
-
13
- Agentic coding spends one model subscription on everything, and implementation is the expensive part. codex-bridge moves that work to a separate [Codex CLI](https://developers.openai.com/codex/cli) subscription: Claude Code plans, dispatches and accepts, Codex executes — and every delegated run is guarded, verified and audited on disk.
14
-
15
- ## Install
16
-
17
- ```bash
18
- npm i -g @lyupro/codex-bridge # puts both command names on PATH
19
- codex-bridge install # into ~/.claude and ~/.lyupro/.codex-bridge/
20
- codexb install # the short name, same command
21
- ```
22
-
23
- Two other entry points reach the same command: `npx @lyupro/codex-bridge install` for a one-off run without a global install, and `node bin/codex-bridge.mjs install` from a clone. Every command below works through all three.
24
-
25
- Shipped agents invoke delegated runs through `codex-bridge run`. This is a permission contract,
26
- not a style choice: the stable package command lets host permission rules match the invocation
27
- without exposing an installation-specific absolute path and prompting on every delegation.
28
-
29
- Installing optional permission rules now adds `model speed` rules to `ask`, previously left to the operator, alongside `allow` and `deny`. These narrow prompts outrank the broad command allow rules and are checked before hooks, so an agent must ask the operator before switching tiers even when acting in good faith. Removing the rules takes back these exact strings while preserving other operator settings.
30
-
31
- Use `--scope project` to install the Claude Code-facing files under `<repo>/.claude`; the default user scope uses `~/.claude`.
32
-
33
- Installation has two roots:
34
-
35
- - `~/.lyupro/.codex-bridge/` contains package runtime files, guards, configuration, conventions, and the installation record.
36
- - The selected Claude Code root (`~/.claude/` or `<repo>/.claude/`) contains the three agents, two slash commands, merged hook registrations in `settings.json`, and run artifacts under `codex-runs/`.
37
-
38
- The repository's `src/home/` directory is the literal image copied into the branded root: its
39
- `hooks/`, `lib/`, `config.json`, and `conventions.md` paths are the host paths without remapping.
40
- Only npm's `package.json` is copied into that root from outside the image.
41
-
42
- Before every `settings.json` write, the existing file is backed up. Existing settings and foreign hooks are preserved. `--dry-run` prints the plan without writing.
43
-
44
- If a global install and a clone coexist, you have two package copies. `codex-bridge update`, `codexb update`, and the clone entry point copy files from whichever copy launched the command; using the wrong one can replace a newer host installation with older files.
45
-
46
- `codex-runs/` contains operator data. Uninstall never removes it.
47
-
48
- ## Requirements
49
-
50
- - Node.js >= 24
51
- - An authenticated [Codex CLI](https://developers.openai.com/codex/cli): `codex --version` and `codex login`
52
- - Claude Code, which hosts the agents, commands, and guards
53
-
54
- ## What it gives you
55
-
56
- - **Runs survive the caller.** A detached worker keeps the run and its artifacts alive after the dispatcher exits.
57
- - **Invalid work is rejected before quota is spent.** Scope, order, retry, branch, and continuation checks run before a run folder is created.
58
- - **Verdicts come from artifacts.** CLI events, git state, and the declared report decide the result, not model claims about itself.
59
- - **Identical retries attach safely.** Repeating the same command joins the active run instead of starting another one.
60
- - **6 host guards enforce the boundary.** `reply-guard`, `order-gate`, `worktree-lock`, `prune-guard`, and `stop-guard` cover replies, orders, edits, deletion, and stopping; `worktree-witness` stands behind the lock and names a write that reached the tree anyway.
61
- - **Installation is identifiable and reversible.** Writes to `settings.json` are merged and backed up; uninstall removes recorded package files only.
62
- - **Every run leaves an audit folder.** The verbatim task, scope, before/after git state, events, report, verdict, and reason remain together.
63
- - **Zero runtime dependencies and zero build steps.** The package is plain `.mjs` on the Node.js standard library.
64
-
65
- The current suite contains **963 automated tests: 962 passing and 1 skipped**.
66
-
67
- ## Verify
68
-
69
- ```bash
70
- codex-bridge doctor
71
- ```
72
-
73
- Output on a healthy host (trimmed):
74
-
75
- ```text
76
- [ok] host: ~/.claude (user, exists)
77
- [ok] installation: @lyupro/codex-bridge@0.5.1 (matches package)
78
- [ok] files: 53 installed file(s) present
79
- [ok] rules: ~/.codex/rules/codex-bridge.rules (matches record)
80
- [ok] permissions: installed (24/24 own strings in allow/deny)
81
- [ok] hook:SubagentStop: matcher * -> ~/.lyupro/.codex-bridge/hooks/reply-guard.mjs
82
- [ok] hook:PreToolUse: matcher Agent|Task -> ~/.lyupro/.codex-bridge/hooks/order-gate.mjs
83
- [ok] hook:PreToolUse: matcher Write|Edit|MultiEdit|NotebookEdit -> ~/.lyupro/.codex-bridge/hooks/worktree-lock.mjs
84
- [ok] hook:PreToolUse: matcher Bash|PowerShell -> ~/.lyupro/.codex-bridge/hooks/prune-guard.mjs
85
- [ok] hook:PreToolUse: matcher TaskStop -> ~/.lyupro/.codex-bridge/hooks/stop-guard.mjs
86
- [warn] retention: Automatic cleanup is ON — run transport older than 30 days is removed to reclaim disk space.
87
- [ok] conventions: ~/.lyupro/.codex-bridge/conventions.md (found)
88
- [ok] codex: codex-cli 0.146.1
89
- [ok] node: 24.18.0 (requires >=24)
90
- [ok] runsRoot: ~/.claude/codex-runs
91
- [ok] liveRuns: 0 runs working right now
92
- ```
93
-
94
- `doctor` reports the selected host, package source, installed files and guards, Codex availability, retention policy, permissions, live runs, and the current repository's run folder. The `agents` line is a read, not a file count: each installed definition is parsed and its `name` checked against the dispatcher it claims to be, because a definition the host cannot read is a dispatcher that does not exist while every other line still says `[ok]`. Content that drifts from the packaged definition is a failure, not a warning: `doctor` exits 1 and names `codex-bridge update --force`, because a host running someone else's agent files may be calling the runner in a form no permission rule matches.
95
-
96
- If a session answers `Agent type 'codex-build' not found`, a run can still be started without the dispatcher — the agent is a wrapper that types one command:
97
-
98
- ```bash
99
- codex-bridge run --agent codex-build --repo <repository> --order-id <id> --scope <globs> --task-file <absolute path>
100
- ```
101
-
102
- The trade-off is explicit: `order-gate` and `reply-guard` sit on the dispatcher call, so a direct run is unguarded and its result is judged by the worktree and the suite.
103
-
104
- ## Update
105
-
106
- Update the global package, then refresh the selected host installation:
107
-
108
- ```bash
109
- npm i -g @lyupro/codex-bridge
110
- codex-bridge update
111
- ```
112
-
113
- `update` refreshes unchanged package files, refuses to overwrite operator edits unless `--force` is supplied, and removes obsolete files only when their installed fingerprint is unchanged.
114
-
115
- ## Limitations
116
-
117
- - **Runs are not isolated from one another.** They share one worktree; guards prevent concurrent writers instead of creating sandboxes.
118
- - **A build run has a 25-minute deadline.** Work and artifacts survive a timeout, but the run may need an explicit continuation.
119
- - **A dispatcher verdict is not the work outcome.** Judge completion from the worktree and tests as well as the recorded verdict.
120
-
121
- Transport files from runs at least **30 days** old are pruned automatically when a new run starts. Configuration can change or disable that retention policy; reports, accounting, and worktree snapshots are retained.
122
-
123
- ## Command reference
124
-
125
- | Command | Purpose |
126
- | --- | --- |
127
- | `install [--scope user\|project] [--host <path>] [--dry-run] [--force]` | Install into a Claude Code host. |
128
- | `update [--scope user\|project] [--host <path>] [--dry-run] [--force]` | Refresh a recorded installation. |
129
- | `permissions [add\|remove] [--scope user\|project] [--host <path>]` | Inspect or manage optional shell rules. |
130
- | `uninstall [--scope user\|project] [--host <path>] [--dry-run]` | Remove recorded package files while preserving run artifacts. |
131
- | `doctor [--scope user\|project] [--host <path>] [--probe-contract]` | Diagnose the selected host and Codex connection. `--probe-contract` measures on a live host whether it still honours a hook refusal, and records the answer. |
132
- | `run <runner options> --task-file <abs path>` | Start or attach to a delegated run through the permission-stable package command. The task file carries the statement, the scout questions and the verification command; its path must be absolute. Piping the statement on stdin is the alternative channel, and the two cannot be combined. |
133
- | `model [list\|set\|unset\|speed]` | Show the model, effort, pinned speed tier and provenance of each role, with the config path; `model list` prints the live catalogue from Codex, hidden models included and marked; `model set <role> <model> [effort]` checks the pair and any retained speed tier against that model's catalogue entry, and `model unset <role>` removes the profile. `model speed <role> <tier>` previews a live accelerated tier and its quoted cost description; repeat with trailing `confirm` to pin it. `model speed <role> unset` removes only speed, without confirmation. The profile is machine-wide, shared by every project on this machine. |
134
- | `projects [<name>] [--json]` | List projects or runs in the run store. |
135
- | `prune <project> [<run>] [--purge] [--older-than <age>] [-f] [--json]`<br>`prune --all-projects [--older-than <age>] [-f] [--json]` | Plan or perform operator-confirmed cleanup, for one project or across the whole run store. |
136
- | `unlock [<project>\|--all]` | Close records whose runner is gone. |
137
- | `read <run>` | Render a run's structured event stream. |
138
- | `stop <run>` | Stop a run and record a `FAIL` verdict. |
139
-
140
- Both `codex-bridge` and `codexb` invoke the same command. Run `codex-bridge --help` for exact forms.
141
-
142
- ### Development
143
-
144
- ```bash
145
- git clone https://github.com/lyupro/codex-bridge.git
146
- cd codex-bridge
147
- git config core.hooksPath .githooks
148
- npm test
149
- ```
150
-
151
- The Git hook enforces the 400-line source-file limit.
152
-
153
- ## License
154
-
155
- [MIT](LICENSE)
1
+ # codex-bridge
2
+
3
+ **Delegate implementation, reconnaissance and review from Claude Code to Codex — six host guards, zero runtime dependencies, zero build steps.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
6
+ [![Node.js](https://img.shields.io/node/v/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+
9
+ [![npm downloads](https://img.shields.io/npm/dm/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
10
+ [![npm unpacked size](https://img.shields.io/npm/unpacked-size/@lyupro/codex-bridge)](https://www.npmjs.com/package/@lyupro/codex-bridge)
11
+ [![dependencies: zero](https://img.shields.io/badge/dependencies-zero-brightgreen)](package.json)
12
+
13
+ Agentic coding spends one model subscription on everything, and implementation is the expensive part. codex-bridge moves that work to a separate [Codex CLI](https://developers.openai.com/codex/cli) subscription: Claude Code plans, dispatches and accepts, Codex executes — and every delegated run is guarded, verified and audited on disk.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm i -g @lyupro/codex-bridge # puts both command names on PATH
19
+ codex-bridge install # into ~/.claude and ~/.lyupro/.codex-bridge/
20
+ codexb install # the short name, same command
21
+ ```
22
+
23
+ Two other entry points reach the same command: `npx @lyupro/codex-bridge install` for a one-off run without a global install, and `node bin/codex-bridge.mjs install` from a clone. Every command below works through all three.
24
+
25
+ Shipped agents invoke delegated runs through `codex-bridge run`. This is a permission contract,
26
+ not a style choice: the stable package command lets host permission rules match the invocation
27
+ without exposing an installation-specific absolute path and prompting on every delegation.
28
+
29
+ Installing optional permission rules now adds `model speed` rules to `ask`, previously left to the operator, alongside `allow` and `deny`. These narrow prompts outrank the broad command allow rules and are checked before hooks, so an agent must ask the operator before switching tiers even when acting in good faith. Removing the rules takes back these exact strings while preserving other operator settings.
30
+
31
+ Use `--scope project` to install the Claude Code-facing files under `<repo>/.claude`; the default user scope uses `~/.claude`.
32
+
33
+ Installation has two roots:
34
+
35
+ - `~/.lyupro/.codex-bridge/` contains package runtime files, guards, configuration, conventions, and the installation record.
36
+ - The selected Claude Code root (`~/.claude/` or `<repo>/.claude/`) contains the three agents, two slash commands, merged hook registrations in `settings.json`, and run artifacts under `codex-runs/`.
37
+
38
+ The repository's `src/home/` directory is the literal image copied into the branded root: its
39
+ `hooks/`, `lib/`, `config.json`, and `conventions.md` paths are the host paths without remapping.
40
+ Only npm's `package.json` is copied into that root from outside the image.
41
+
42
+ Before every `settings.json` write, the existing file is backed up. Existing settings and foreign hooks are preserved. `--dry-run` prints the plan without writing.
43
+
44
+ If a global install and a clone coexist, you have two package copies. `codex-bridge update`, `codexb update`, and the clone entry point copy files from whichever copy launched the command; using the wrong one can replace a newer host installation with older files.
45
+
46
+ `codex-runs/` contains operator data. Uninstall never removes it.
47
+
48
+ ## Requirements
49
+
50
+ - Node.js >= 24
51
+ - An authenticated [Codex CLI](https://developers.openai.com/codex/cli): `codex --version` and `codex login`
52
+ - Claude Code, which hosts the agents, commands, and guards
53
+
54
+ ## What it gives you
55
+
56
+ - **Runs survive the caller.** A detached worker keeps the run and its artifacts alive after the dispatcher exits.
57
+ - **Invalid work is rejected before quota is spent.** Scope, order, retry, branch, and continuation checks run before a run folder is created.
58
+ - **Verdicts come from artifacts.** CLI events, git state, and the declared report decide the result, not model claims about itself.
59
+ - **Identical retries attach safely.** Repeating the same command joins the active run instead of starting another one.
60
+ - **6 host guards enforce the boundary.** `reply-guard`, `order-gate`, `worktree-lock`, `prune-guard`, and `stop-guard` cover replies, orders, edits, deletion, and stopping; `worktree-witness` stands behind the lock and names a write that reached the tree anyway.
61
+ - **Installation is identifiable and reversible.** Writes to `settings.json` are merged and backed up; uninstall removes recorded package files only.
62
+ - **Every run leaves an audit folder.** The verbatim task, scope, before/after git state, events, report, verdict, and reason remain together.
63
+ - **Zero runtime dependencies and zero build steps.** The package is plain `.mjs` on the Node.js standard library.
64
+
65
+ The current suite contains **1177 automated tests: 1176 passing and 1 skipped**.
66
+
67
+ ## Verify
68
+
69
+ ```bash
70
+ codex-bridge doctor
71
+ ```
72
+
73
+ Output on a healthy host (trimmed):
74
+
75
+ ```text
76
+ [ok] host: ~/.claude (user, exists)
77
+ [ok] installation: @lyupro/codex-bridge@0.5.1 (matches package)
78
+ [ok] files: 53 installed file(s) present
79
+ [ok] rules: ~/.codex/rules/codex-bridge.rules (matches record)
80
+ [ok] permissions: installed (24/24 own strings in allow/deny)
81
+ [ok] hook:SubagentStop: matcher * -> ~/.lyupro/.codex-bridge/hooks/reply-guard.mjs
82
+ [ok] hook:PreToolUse: matcher Agent|Task -> ~/.lyupro/.codex-bridge/hooks/order-gate.mjs
83
+ [ok] hook:PreToolUse: matcher Write|Edit|MultiEdit|NotebookEdit -> ~/.lyupro/.codex-bridge/hooks/worktree-lock.mjs
84
+ [ok] hook:PreToolUse: matcher Bash|PowerShell -> ~/.lyupro/.codex-bridge/hooks/prune-guard.mjs
85
+ [ok] hook:PreToolUse: matcher TaskStop -> ~/.lyupro/.codex-bridge/hooks/stop-guard.mjs
86
+ [warn] retention: Automatic cleanup is ON — run transport older than 30 days is removed to reclaim disk space.
87
+ [ok] conventions: ~/.lyupro/.codex-bridge/conventions.md (found)
88
+ [ok] codex: codex-cli 0.146.1
89
+ [ok] node: 24.18.0 (requires >=24)
90
+ [ok] runsRoot: ~/.claude/codex-runs
91
+ [ok] liveRuns: 0 runs working right now
92
+ ```
93
+
94
+ `doctor` reports the selected host, package source, installed files and guards, Codex availability, retention policy, permissions, live runs, and the current repository's run folder. The `agents` line is a read, not a file count: each installed definition is parsed and its `name` checked against the dispatcher it claims to be, because a definition the host cannot read is a dispatcher that does not exist while every other line still says `[ok]`. Content that drifts from the packaged definition is a failure, not a warning: `doctor` exits 1 and names `codex-bridge update --force`, because a host running someone else's agent files may be calling the runner in a form no permission rule matches.
95
+
96
+ If a session answers `Agent type 'codex-build' not found`, a run can still be started without the dispatcher — the agent is a wrapper that types one command:
97
+
98
+ ```bash
99
+ codex-bridge run --agent codex-build --repo <repository> --order-id <id> --scope <globs> --task-file <absolute path>
100
+ ```
101
+
102
+ The trade-off is explicit: `order-gate` and `reply-guard` sit on the dispatcher call, so a direct run is unguarded and its result is judged by the worktree and the suite.
103
+
104
+ ## Update
105
+
106
+ Update the global package, then refresh the selected host installation:
107
+
108
+ ```bash
109
+ npm i -g @lyupro/codex-bridge
110
+ codex-bridge update
111
+ ```
112
+
113
+ `update` refreshes unchanged package files, refuses to overwrite operator edits unless `--force` is supplied, and removes obsolete files only when their installed fingerprint is unchanged.
114
+
115
+ ## Limitations
116
+
117
+ - **Runs are not isolated from one another.** They share one worktree; guards prevent concurrent writers instead of creating sandboxes.
118
+ - **A build run has a 25-minute deadline.** Work and artifacts survive a timeout, but the run may need an explicit continuation.
119
+ - **A dispatcher verdict is not the work outcome.** Judge completion from the worktree and tests as well as the recorded verdict.
120
+
121
+ Transport files from runs at least **30 days** old are pruned automatically when a new run starts. Configuration can change or disable that retention policy; reports, accounting, and worktree snapshots are retained.
122
+
123
+ ## Command reference
124
+
125
+ | Command | Purpose |
126
+ | --- | --- |
127
+ | `install [--scope user\|project] [--host <path>] [--dry-run] [--force]` | Install into a Claude Code host. |
128
+ | `update [--scope user\|project] [--host <path>] [--dry-run] [--force]` | Refresh a recorded installation. |
129
+ | `permissions [add\|remove] [--scope user\|project] [--host <path>]` | Inspect or manage optional shell rules. |
130
+ | `uninstall [--scope user\|project] [--host <path>] [--dry-run]` | Remove recorded package files while preserving run artifacts. |
131
+ | `doctor [--scope user\|project] [--host <path>] [--probe-contract]` | Diagnose the selected host and Codex connection. `--probe-contract` measures on a live host whether it still honours a hook refusal, and records the answer. |
132
+ | `run <runner options> --task-file <abs path>` | Start or attach to a delegated run through the permission-stable package command. The task file carries the statement, the scout questions and the verification command; its path must be absolute. Piping the statement on stdin is the alternative channel, and the two cannot be combined. |
133
+ | `model [list\|set\|unset\|speed]` | Show the model, effort, pinned speed tier and provenance of each role, with the config path; `model list` prints the live catalogue from Codex, hidden models included and marked; `model set <role> <model> [effort]` checks the pair and any retained speed tier against that model's catalogue entry, and `model unset <role>` removes the profile. `model speed <role> <tier>` previews a live accelerated tier and its quoted cost description; repeat with trailing `confirm` to pin it. `model speed <role> unset` removes only speed, without confirmation. The profile is machine-wide, shared by every project on this machine. |
134
+ | `projects [<name>] [--json]` | List projects or runs in the run store. |
135
+ | `prune <project> [<run>] [--purge] [--older-than <age>] [-f] [--json]`<br>`prune --all-projects [--older-than <age>] [-f] [--json]` | Plan or perform operator-confirmed cleanup, for one project or across the whole run store. |
136
+ | `unlock [<project>\|--all]` | Close records whose runner is gone. |
137
+ | `read <run>` | Render a run's structured event stream. |
138
+ | `stop <run>` | Stop a run and record a `FAIL` verdict. |
139
+
140
+ Both `codex-bridge` and `codexb` invoke the same command. Run `codex-bridge --help` for exact forms.
141
+
142
+ ### Development
143
+
144
+ ```bash
145
+ git clone https://github.com/lyupro/codex-bridge.git
146
+ cd codex-bridge
147
+ git config core.hooksPath .githooks
148
+ npm test
149
+ ```
150
+
151
+ The Git hook enforces the 400-line source-file limit.
152
+
153
+ ## License
154
+
155
+ [MIT](LICENSE)
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { liveRuns } from '../src/home/hooks/live-runs.mjs';
5
5
  import { readJsonFileSync } from '../src/home/lib/json-file.mjs';
6
+ import { runLiveness } from '../src/home/lib/meta/run-liveness.mjs';
6
7
  import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
7
8
 
8
9
  const record = (value) => value && typeof value === 'object' && !Array.isArray(value);
@@ -108,7 +109,7 @@ function readRun(runDir, livePaths) {
108
109
  return {
109
110
  run: path.basename(runDir),
110
111
  agent: text(facts?.agent),
111
- verdict: live ? 'running' : text(meta ? meta.status : status?.state),
112
+ verdict: live ? 'running' : text(meta ? meta.status : runLiveness({ runDir, status }).state),
112
113
  tokens: meta ? number(meta.tokens) : null,
113
114
  size: recursiveSize(runDir),
114
115
  timestamp: runTimestamp(path.basename(runDir), meta, status),
@@ -131,8 +132,9 @@ function publicRun(run) {
131
132
 
132
133
  function buildProject(projectDir) {
133
134
  const runDirs = directoryEntries(projectDir).map((entry) => path.join(projectDir, entry.name));
134
- // live-runs owns PID and status interpretation after the 2026-08-05 live probe incident;
135
- // this module only maps its result back to the corresponding inventory row.
135
+ // live-runs owns the `live` column (a fresh heartbeat) after the 2026-08-05 live probe incident.
136
+ // A row without a verdict shows what closing it would write — the judge's `abandoned` rather than
137
+ // the raw `running` a dead run keeps in status.json forever (Plan_57 D27).
136
138
  const livePaths = new Set(liveRuns(projectDir).map(({ dir }) => pathKey(dir)));
137
139
  const details = runDirs.map((runDir) => readRun(runDir, livePaths));
138
140
  // Sum what is known instead of blanking the column. One archived run without accounting used to
@@ -18,24 +18,34 @@ export function shortCommandFor(name) {
18
18
  return `codex-bridge hook ${name}`;
19
19
  }
20
20
 
21
- function pathValue(env) {
22
- const key = Object.keys(env).find((entry) => entry.toLowerCase() === 'path');
21
+ function envValue(env, name) {
22
+ const key = Object.keys(env).find((entry) => entry.toLowerCase() === name);
23
23
  return key ? env[key] : '';
24
24
  }
25
25
 
26
- export function commandReachable(name = 'codex-bridge', env = process.env) {
26
+ function pathValue(env) {
27
+ return envValue(env, 'path');
28
+ }
29
+
30
+ function resolveCommand(name, env) {
27
31
  const extensions = process.platform === 'win32'
28
32
  ? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
29
33
  : [''];
30
- return pathValue(env).split(path.delimiter).filter(Boolean).some((directory) =>
31
- extensions.some((extension) => {
32
- const candidate = path.join(directory, `${name}${extension}`);
34
+ for (const directory of pathValue(env).split(path.delimiter).filter(Boolean)) {
35
+ for (const extension of extensions) {
36
+ const candidate = path.join(path.resolve(directory), `${name}${extension}`);
33
37
  try {
34
- return existsSync(candidate) && statSync(candidate).isFile();
38
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
35
39
  } catch {
36
- return false;
40
+ continue;
37
41
  }
38
- }));
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export function commandReachable(name = 'codex-bridge', env = process.env) {
48
+ return Boolean(resolveCommand(name, env));
39
49
  }
40
50
 
41
51
  /**
@@ -47,14 +57,23 @@ export function commandReachable(name = 'codex-bridge', env = process.env) {
47
57
  * could decide anything, and the host refused Bash, PowerShell, file edits and agent launches.
48
58
  */
49
59
  export function reachableCommandVersion(name = 'codex-bridge', env = process.env) {
50
- if (!commandReachable(name, env)) return null;
51
- const result = spawnSync(name, ['--version'], {
52
- encoding: 'utf8',
53
- env,
54
- // The launcher npm writes on Windows is a .cmd shim, which spawn cannot execute without a
55
- // shell. The name is a constant of this module, never operator input.
56
- shell: process.platform === 'win32',
57
- });
60
+ const shimPath = resolveCommand(name, env);
61
+ if (!shimPath) return null;
62
+ // Node 24 warned with DEP0190 during update (2026-09-07): invoke cmd explicitly for npm shims.
63
+ // Live probes covered plain, bin with space, Program Files (x86), weird & name, and caret^dir.
64
+ // /d /s /c with path and --version as separate arguments only passed the plain directory:
65
+ // Node adds quotes, then cmd's /s strips the first and last quote of the whole command line.
66
+ // /d /c without /s failed on & and ^. This single, double-wrapped verbatim command passed all
67
+ // five. Keep native path separators: forward-slash Windows paths do not work through cmd.
68
+ // The interpreter is a property of the machine, not of the PATH handed in: resolving cmd.exe
69
+ // through the caller's PATH would return null on any env whose PATH omits System32, and a null
70
+ // version is exactly the silent failure this function exists to prevent.
71
+ const shell = envValue(env, 'comspec') || process.env.ComSpec || 'cmd.exe';
72
+ const result = process.platform === 'win32'
73
+ ? spawnSync(shell, ['/d', '/s', '/c', `""${shimPath}" --version"`], {
74
+ encoding: 'utf8', env, shell: false, windowsVerbatimArguments: true,
75
+ })
76
+ : spawnSync(shimPath, ['--version'], { encoding: 'utf8', env, shell: false });
58
77
  if (result.error || result.status !== 0) return null;
59
78
  return String(result.stdout || '').trim().split(/\r?\n/).pop()?.trim() || null;
60
79
  }
package/cli/stop.mjs CHANGED
@@ -2,12 +2,12 @@
2
2
  import path from 'node:path';
3
3
  import { stopCodex } from '../src/home/lib/runner/codex-cmd.mjs';
4
4
  import { git, worktreeSnapshot } from '../src/home/lib/runner/git-state.mjs';
5
+ import { runLiveness } from '../src/home/lib/meta/run-liveness.mjs';
5
6
  import {
6
7
  IDENTITY_ALIVE,
7
8
  IDENTITY_FOREIGN,
8
9
  IDENTITY_UNVERIFIED,
9
10
  processAlive,
10
- processIdentity,
11
11
  } from '../src/home/lib/process-identity.mjs';
12
12
  import { markAbandoned, readJson } from '../src/home/lib/write-meta.mjs';
13
13
  import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
@@ -72,7 +72,7 @@ export async function stop({
72
72
  return result(1, `Run ${runDir} has no valid recorded pid; it was not changed.`);
73
73
  }
74
74
 
75
- const identity = processIdentity({ runDir, status, commandRunner });
75
+ const { identity } = runLiveness({ runDir, status, commandRunner });
76
76
  if (identity === IDENTITY_UNVERIFIED || identity === IDENTITY_FOREIGN) {
77
77
  return result(
78
78
  1,
package/cli/unlock.mjs CHANGED
@@ -3,11 +3,11 @@
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { formatSilence, heartbeatAge, HEARTBEAT_STALE_MS } from '../src/home/lib/heartbeat.mjs';
6
+ import { runLiveness } from '../src/home/lib/meta/run-liveness.mjs';
6
7
  import { markAbandoned } from '../src/home/lib/meta/run-state.mjs';
7
8
  import {
8
9
  IDENTITY_ALIVE,
9
10
  IDENTITY_UNVERIFIED,
10
- processIdentity,
11
11
  } from '../src/home/lib/process-identity.mjs';
12
12
  import { git } from '../src/home/lib/runner/git-state.mjs';
13
13
  import { resolveProjectRunsDir } from '../src/home/lib/runner/project-dir.mjs';
@@ -144,7 +144,7 @@ function inspectRuns(project, options) {
144
144
  age: runAge(status.started_at, now),
145
145
  silence: formatSilence(silenceAge),
146
146
  silenceAge,
147
- identity: processIdentity(identityOptions(dir, status, options, now)),
147
+ identity: runLiveness(identityOptions(dir, status, options, now)).identity,
148
148
  state: status.state,
149
149
  });
150
150
  }
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@lyupro/codex-bridge",
3
- "version": "0.6.1",
4
- "description": "Claude Code-to-Codex delegation with 6 guards, 963 tests, zero runtime dependencies, and zero build steps.",
5
- "keywords": [
6
- "claude-code",
7
- "codex",
8
- "codex-cli",
9
- "subagent",
10
- "ai-agents",
11
- "orchestration",
12
- "hooks"
13
- ],
14
- "license": "MIT",
15
- "author": "Lyu Pro <lyupro.dev@gmail.com> (https://lyupro.com)",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/lyupro/codex-bridge.git"
19
- },
20
- "bugs": {
21
- "url": "https://github.com/lyupro/codex-bridge/issues"
22
- },
23
- "homepage": "https://github.com/lyupro/codex-bridge#readme",
24
- "type": "module",
25
- "bin": {
26
- "codex-bridge": "./bin/codex-bridge.mjs",
27
- "codexb": "./bin/codex-bridge.mjs"
28
- },
29
- "engines": {
30
- "node": ">=24.0.0"
31
- },
32
- "files": [
33
- "bin",
34
- "cli",
35
- "src",
36
- "scripts",
37
- "README.md",
38
- "LICENSE",
39
- "CHANGELOG.md"
40
- ],
41
- "scripts": {
42
- "dev:install": "node bin/codex-bridge.mjs update --force",
43
- "test": "node scripts/run-tests.mjs",
44
- "check:size": "node scripts/check-file-size.mjs"
45
- },
46
- "publishConfig": {
47
- "access": "public"
48
- }
49
- }
1
+ {
2
+ "name": "@lyupro/codex-bridge",
3
+ "version": "0.6.4",
4
+ "description": "Claude Code-to-Codex delegation with 6 guards, 1177 tests, zero runtime dependencies, and zero build steps.",
5
+ "keywords": [
6
+ "claude-code",
7
+ "codex",
8
+ "codex-cli",
9
+ "subagent",
10
+ "ai-agents",
11
+ "orchestration",
12
+ "hooks"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "Lyu Pro <lyupro.dev@gmail.com> (https://lyupro.com)",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/lyupro/codex-bridge.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/lyupro/codex-bridge/issues"
22
+ },
23
+ "homepage": "https://github.com/lyupro/codex-bridge#readme",
24
+ "type": "module",
25
+ "bin": {
26
+ "codex-bridge": "./bin/codex-bridge.mjs",
27
+ "codexb": "./bin/codex-bridge.mjs"
28
+ },
29
+ "engines": {
30
+ "node": ">=24.0.0"
31
+ },
32
+ "files": [
33
+ "bin",
34
+ "cli",
35
+ "src",
36
+ "scripts",
37
+ "README.md",
38
+ "LICENSE",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "scripts": {
42
+ "dev:install": "node bin/codex-bridge.mjs update --force",
43
+ "test": "node scripts/run-tests.mjs",
44
+ "check:size": "node scripts/check-file-size.mjs"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }