@lyupro/codex-bridge 0.5.3 → 0.5.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.
@@ -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
+ }