@lyupro/codex-bridge 0.5.3 → 0.5.5
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 +98 -0
- package/README.md +152 -152
- package/bin/codex-bridge.mjs +188 -174
- package/cli/doctor-format.mjs +23 -0
- package/cli/doctor-hooks.mjs +114 -0
- package/cli/doctor-installation.mjs +111 -0
- package/cli/doctor-runs.mjs +55 -0
- package/cli/doctor.mjs +150 -399
- package/cli/host-contract.mjs +135 -0
- package/cli/hosts.mjs +4 -3
- package/cli/install.mjs +81 -11
- package/cli/permissions.mjs +7 -1
- package/cli/probe-contract.mjs +180 -0
- package/package.json +49 -49
- package/scripts/run-tests.mjs +4 -1
- package/src/agents/codex-build.md +265 -265
- package/src/agents/codex-review.md +219 -219
- package/src/agents/codex-scout.md +232 -232
- package/src/home/hooks/shell-write-intent.mjs +44 -15
- package/src/home/lib/brand-home.mjs +39 -0
- package/src/home/lib/meta/reply.mjs +27 -0
- package/src/home/lib/run-config.mjs +18 -6
- package/src/home/lib/runner/codex-args.mjs +154 -0
- package/src/home/lib/runner/codex-cmd.mjs +268 -398
- package/src/home/lib/runner/launcher.mjs +18 -30
- package/src/home/lib/runner/worker-order.mjs +56 -0
- package/src/home/lib/write-meta.mjs +11 -3
|
@@ -19,10 +19,18 @@
|
|
|
19
19
|
import fs from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { BRAND_CONFIG_PATH, BRAND_HOME } from './brand-home.mjs';
|
|
22
23
|
import { readJsonFileSync } from './json-file.mjs';
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
export const CONFIG_PATH = BRAND_CONFIG_PATH;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Every print of the state says which file was read AND why that one. The defect this replaced was
|
|
29
|
+
* invisible precisely because the path was never shown beside its origin: two files named
|
|
30
|
+
* config.json, and no way to tell from the output which of them the run had just used.
|
|
31
|
+
*/
|
|
32
|
+
const ORIGIN =
|
|
33
|
+
BRAND_HOME.source === 'CODEX_BRIDGE_HOME' ? 'from CODEX_BRIDGE_HOME' : 'default location';
|
|
26
34
|
|
|
27
35
|
const BUDGET_KEY = 'budgets';
|
|
28
36
|
const BUDGET_MODES = ['scout', 'build', 'review'];
|
|
@@ -73,7 +81,11 @@ const MODEL_KEYS = ['scout', 'build', 'review'];
|
|
|
73
81
|
* fallback depth the dispatcher happens to pass.
|
|
74
82
|
*/
|
|
75
83
|
const PROFILE_KEYS = ['model', 'effort'];
|
|
76
|
-
|
|
84
|
+
// Exactly the values the service enumerates, and no more. `minimal` sat here until 2026-08-26,
|
|
85
|
+
// passed validation and was rejected by the model itself after the run had started
|
|
86
|
+
// ("Unsupported value: 'minimal' is not supported with the … model"), so an order died four
|
|
87
|
+
// seconds in on quota already spent. A validator that admits a known-dead value is worse than none.
|
|
88
|
+
export const ALLOWED_EFFORTS = Object.freeze(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
77
89
|
/**
|
|
78
90
|
* The language a run answers in. Left to the model it followed the task, the surrounding docs or
|
|
79
91
|
* its own default: an English order came back in Russian, and artifacts of one project ended up in
|
|
@@ -308,13 +320,13 @@ function main(argv) {
|
|
|
308
320
|
|
|
309
321
|
if (!key) {
|
|
310
322
|
const config = readRunConfig();
|
|
311
|
-
console.log([...state(config), `File: ${CONFIG_PATH}`].join('\n'));
|
|
323
|
+
console.log([...state(config), `File: ${CONFIG_PATH} (${ORIGIN})`].join('\n'));
|
|
312
324
|
return 0;
|
|
313
325
|
}
|
|
314
326
|
|
|
315
327
|
if (key === 'reset') {
|
|
316
328
|
writeRunConfig({ ...DEFAULTS });
|
|
317
|
-
console.log([...state(DEFAULTS), `Reset to defaults · ${CONFIG_PATH}`].join('\n'));
|
|
329
|
+
console.log([...state(DEFAULTS), `Reset to defaults · ${CONFIG_PATH} (${ORIGIN})`].join('\n'));
|
|
318
330
|
return 0;
|
|
319
331
|
}
|
|
320
332
|
|
|
@@ -334,7 +346,7 @@ function main(argv) {
|
|
|
334
346
|
|
|
335
347
|
const config = { ...readRunConfig(), [key]: value === 'on' };
|
|
336
348
|
writeRunConfig(config);
|
|
337
|
-
console.log([...state(config), `File: ${CONFIG_PATH}`].join('\n'));
|
|
349
|
+
console.log([...state(config), `File: ${CONFIG_PATH} (${ORIGIN})`].join('\n'));
|
|
338
350
|
return 0;
|
|
339
351
|
}
|
|
340
352
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a run asks Codex to do: the model, the reasoning depth and the flag set per agent.
|
|
3
|
+
*
|
|
4
|
+
* Split out of codex-cmd.mjs when that file outgrew the size limit; the seam is deliberate, not
|
|
5
|
+
* arithmetic — starting a process and deciding what it is asked to do are different jobs, and
|
|
6
|
+
* only this half needs to know the run's configuration.
|
|
7
|
+
*
|
|
8
|
+
* The flag sets are the ones the three agent files carried before, verbatim. Sandboxes especially
|
|
9
|
+
* must not drift: codex-scout and codex-review are read-only through an explicit --sandbox
|
|
10
|
+
* read-only, and codex-build needs workspace-write and must NOT get --ignore-user-config. What
|
|
11
|
+
* --ignore-user-config buys the two read-only agents is isolation from the operator's config,
|
|
12
|
+
* never the sandbox itself — reading it as the read-only guarantee is how the 2026-08-23 breakage
|
|
13
|
+
* stayed misdiagnosed as "read-only is broken" (see sandbox-flags.mjs for what actually stops a
|
|
14
|
+
* run from starting a process on Windows).
|
|
15
|
+
*/
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { CLEAN_ENV, RUN_ENV } from './run-env.mjs';
|
|
18
|
+
import { platformSandboxArgs } from './sandbox-flags.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Which model runs a mode, and how deep it reasons.
|
|
22
|
+
*
|
|
23
|
+
* Three sources, in this order: what the dispatcher asked for this one task, the mode's
|
|
24
|
+
* configured profile, and finally a depth to fall back on. The order is the whole point —
|
|
25
|
+
* pinning a model without its depth leaves every run at the fallback, which is how a mode
|
|
26
|
+
* configured for maximum reasoning would quietly keep working at the shallowest setting.
|
|
27
|
+
*/
|
|
28
|
+
const FALLBACK_EFFORT = 'medium';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A run is one worker, not a team it recruits for itself.
|
|
32
|
+
*
|
|
33
|
+
* Codex can spawn subagents when a prompt asks it to, and their edits land in the same
|
|
34
|
+
* worktree — so they arrive in the snapshot as the run's own work, outside the scope check
|
|
35
|
+
* that grades it, on quota nobody budgeted. The prompts say not to; this is why they cannot.
|
|
36
|
+
*/
|
|
37
|
+
const NO_SUBAGENTS = ['-c', 'agents.enabled=false'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Which configured mode an agent is. One table, because the launcher needs the same answer to
|
|
41
|
+
* pick a run's time budget: a second copy of this mapping is a second place to forget a mode.
|
|
42
|
+
*/
|
|
43
|
+
export const runMode = (agent) =>
|
|
44
|
+
({
|
|
45
|
+
'codex-scout': 'scout',
|
|
46
|
+
'codex-build': 'build',
|
|
47
|
+
'codex-review': 'review',
|
|
48
|
+
})[agent];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Which model and depth this run gets, and — as part of the same answer — where each came from.
|
|
52
|
+
*
|
|
53
|
+
* The provenance is not decoration. A configured profile failed to reach any run for three
|
|
54
|
+
* releases (2026-08-26) and nothing said so: the reply named no model, meta.json recorded none,
|
|
55
|
+
* and a depth silently served by the fallback is indistinguishable from one the operator chose.
|
|
56
|
+
* `model_source` is `config` or `codex default`; `effort_source` is `request`, `config` or
|
|
57
|
+
* `fallback`, in the order the values are consulted above.
|
|
58
|
+
*/
|
|
59
|
+
export function runProfile(opts) {
|
|
60
|
+
const mode = runMode(opts.agent);
|
|
61
|
+
// opts.models is how tests state a configuration; a real run reads the one loaded for it.
|
|
62
|
+
const configured = (opts.models || RUN_ENV?.models || {})[mode] || {};
|
|
63
|
+
return {
|
|
64
|
+
model: configured.model || '',
|
|
65
|
+
model_source: configured.model ? 'config' : 'codex default',
|
|
66
|
+
effort: opts.effort || configured.effort || FALLBACK_EFFORT,
|
|
67
|
+
effort_source: opts.effort ? 'request' : configured.effort ? 'config' : 'fallback',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function codexArgs(opts, runDir, isGitRepo) {
|
|
72
|
+
const schema = path.join(runDir, 'schema.json');
|
|
73
|
+
const profile = runProfile(opts);
|
|
74
|
+
const effort = `model_reasoning_effort=${profile.effort}`;
|
|
75
|
+
const modelArgs = profile.model ? ['-m', profile.model] : [];
|
|
76
|
+
if (opts.agent === 'codex-scout') {
|
|
77
|
+
return [
|
|
78
|
+
'exec',
|
|
79
|
+
'--json',
|
|
80
|
+
...CLEAN_ENV,
|
|
81
|
+
'--ignore-user-config',
|
|
82
|
+
'-c',
|
|
83
|
+
effort,
|
|
84
|
+
...NO_SUBAGENTS,
|
|
85
|
+
...platformSandboxArgs(),
|
|
86
|
+
...modelArgs,
|
|
87
|
+
'--sandbox',
|
|
88
|
+
'read-only',
|
|
89
|
+
'--skip-git-repo-check',
|
|
90
|
+
'-C',
|
|
91
|
+
opts.repo,
|
|
92
|
+
'--output-schema',
|
|
93
|
+
schema,
|
|
94
|
+
'-o',
|
|
95
|
+
path.join(runDir, 'result.json'),
|
|
96
|
+
'-',
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (opts.agent === 'codex-build') {
|
|
100
|
+
// --ignore-user-config is deliberately absent: with it Codex forces read-only and
|
|
101
|
+
// every edit is rejected ("writing is blocked by read-only sandbox").
|
|
102
|
+
return [
|
|
103
|
+
'exec',
|
|
104
|
+
'--json',
|
|
105
|
+
...CLEAN_ENV,
|
|
106
|
+
'-c',
|
|
107
|
+
effort,
|
|
108
|
+
...NO_SUBAGENTS,
|
|
109
|
+
...platformSandboxArgs(),
|
|
110
|
+
...modelArgs,
|
|
111
|
+
'--sandbox',
|
|
112
|
+
'workspace-write',
|
|
113
|
+
'-C',
|
|
114
|
+
opts.repo,
|
|
115
|
+
...(isGitRepo ? [] : ['--skip-git-repo-check']),
|
|
116
|
+
'--output-schema',
|
|
117
|
+
schema,
|
|
118
|
+
'-o',
|
|
119
|
+
path.join(runDir, 'result.json'),
|
|
120
|
+
'-',
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
// codex-review deliberately does NOT use the `review` subcommand. Two of its traits
|
|
124
|
+
// break the contract this design rests on: it rejects a scope flag together with a
|
|
125
|
+
// prompt ("the argument '--uncommitted' cannot be used with '[PROMPT]'"), so task.md
|
|
126
|
+
// was silently discarded and the review rules never arrived; and it ignores
|
|
127
|
+
// --output-schema, writing prose into -o, so the old JSON parsing of review.json could
|
|
128
|
+
// only ever fail — which is what pushed a dispatcher into reviewing the diff itself.
|
|
129
|
+
//
|
|
130
|
+
// Plain `codex exec` honours the schema (proven by codex-scout), so the review comes
|
|
131
|
+
// back as machine-checkable JSON. Scope lives in the prompt as a file list plus the
|
|
132
|
+
// exact diff command computed by reviewScope(). Read-only through
|
|
133
|
+
// --ignore-user-config, same as scout: review never needs write access.
|
|
134
|
+
return [
|
|
135
|
+
'exec',
|
|
136
|
+
'--json',
|
|
137
|
+
...CLEAN_ENV,
|
|
138
|
+
'--ignore-user-config',
|
|
139
|
+
'-c',
|
|
140
|
+
effort,
|
|
141
|
+
...NO_SUBAGENTS,
|
|
142
|
+
...platformSandboxArgs(),
|
|
143
|
+
...modelArgs,
|
|
144
|
+
'--sandbox',
|
|
145
|
+
'read-only',
|
|
146
|
+
'-C',
|
|
147
|
+
opts.repo,
|
|
148
|
+
'--output-schema',
|
|
149
|
+
schema,
|
|
150
|
+
'-o',
|
|
151
|
+
path.join(runDir, 'review.json'),
|
|
152
|
+
'-',
|
|
153
|
+
];
|
|
154
|
+
}
|