@sabaiway/agent-workflow-kit 14.20.0 → 14.21.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +38 -50
  5. package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
  8. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
  10. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
  11. package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
  12. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
  13. package/bridges/antigravity-cli-bridge/capability.json +14 -7
  14. package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
  15. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
  16. package/bridges/codex-cli-bridge/SKILL.md +51 -49
  17. package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
  18. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
  19. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
  20. package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
  21. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
  22. package/bridges/codex-cli-bridge/capability.json +22 -8
  23. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
  24. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
  25. package/bridges/codex-cli-bridge/setup/README.md +3 -3
  26. package/capability.json +1 -1
  27. package/package.json +1 -1
  28. package/references/modes/bridge-settings.md +10 -7
  29. package/references/modes/recipes.md +1 -1
  30. package/references/modes/review-state.md +1 -1
  31. package/tools/bridge-catalog.mjs +114 -0
  32. package/tools/bridge-posture.mjs +11 -2
  33. package/tools/bridge-settings-read.mjs +41 -5
  34. package/tools/bridge-settings.mjs +111 -23
  35. package/tools/detect-backends.mjs +6 -6
  36. package/tools/family-registry.mjs +3 -3
  37. package/tools/manifest/schema.md +1 -1
  38. package/tools/manifest/validate.mjs +3 -2
  39. package/tools/procedures.mjs +3 -3
  40. package/tools/recipes.mjs +2 -2
  41. package/tools/review-state.mjs +1 -1
@@ -13,7 +13,7 @@ import { createHash } from 'node:crypto';
13
13
  const HERE = dirname(fileURLToPath(import.meta.url));
14
14
  const WRAPPER = join(HERE, 'codex-exec.sh');
15
15
 
16
- // A hermetic fake `codex`: answers `login status`, captures argv/env/stdin to the
16
+ // A hermetic fake `codex`: answers `login status` and `debug models`, captures argv/env/stdin to the
17
17
  // files named by CODEX_FAKE_*, honours -o by writing a final-message file, and
18
18
  // emits a minimal --json event stream (thread.started carries the session id).
19
19
  // Written with shell double-quotes + a heredoc ONLY (no single-quotes, no
@@ -22,6 +22,10 @@ const FAKE_CODEX = [
22
22
  '#!/usr/bin/env bash',
23
23
  'set -u',
24
24
  'if [[ "${1:-}" == "login" ]]; then echo "${CODEX_FAKE_LOGIN:-Logged in using ChatGPT}"; exit 0; fi',
25
+ 'if [[ "${1:-}" == "debug" ]]; then cat <<EOF',
26
+ '{"models":[{"slug":"gpt-6.1-sol","priority":1,"visibility":"list","default_reasoning_level":"low","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"}]},{"slug":"gpt-6-astra","priority":2,"visibility":"list","default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"medium"},{"effort":"high"}]}]}',
27
+ 'EOF',
28
+ 'exit 0; fi',
25
29
  'if [[ -n "${CODEX_FAKE_STDERR:-}" ]]; then echo "$CODEX_FAKE_STDERR" >&2; fi',
26
30
  ': "${CODEX_FAKE_ARGV:=/dev/null}"',
27
31
  ': "${CODEX_FAKE_ENV:=/dev/null}"',
@@ -182,31 +186,14 @@ const run = ({ repo, bin }, { args = ['-'], input = 'do the thing', env = {}, pa
182
186
  // run() IS that twin now, so the twin is one name pointing at it — two spawn paths could only drift.
183
187
  const runAsync = run;
184
188
 
185
- describe('codex-exec.sh — quality-first model/effort guard (1.1)', { concurrency: 2 }, () => {
186
- it('refuses a non-default CODEX_MODEL and never spends a run', async () => {
187
- const sb = makeSandbox();
188
- const r = await run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
189
- rmSync(sb.root, { recursive: true, force: true });
190
- assert.notEqual(r.status, 0);
191
- assert.match(r.stderr, /not the pinned model/);
192
- assert.equal(r.capStdin, '', 'codex must not be invoked when the guard fires');
193
- });
194
-
195
- it('refuses a non-default CODEX_EFFORT', async () => {
189
+ describe('codex-exec.sh — the probe one-off (1.1)', { concurrency: 2 }, () => {
190
+ it('CODEX_PROBE=1 runs a one-off model and warns loudly', async () => {
196
191
  const sb = makeSandbox();
197
- const r = await run(sb, { env: { CODEX_EFFORT: 'xhigh' } });
198
- rmSync(sb.root, { recursive: true, force: true });
199
- assert.notEqual(r.status, 0);
200
- assert.match(r.stderr, /not the pinned effort/);
201
- });
202
-
203
- it('CODEX_PROBE=1 allows a non-default model and warns loudly', async () => {
204
- const sb = makeSandbox();
205
- const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5.4-mini' } });
192
+ const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-6-astra' } });
206
193
  rmSync(sb.root, { recursive: true, force: true });
207
194
  assert.equal(r.status, 0, r.stderr);
208
195
  assert.match(r.stderr, /THROWAWAY PROBE MODE/);
209
- assert.match(r.argv, /gpt-5\.4-mini/, 'the probe model must reach codex');
196
+ assert.match(r.argv, /gpt-6-astra/, 'the probe model must reach codex');
210
197
  });
211
198
  });
212
199
 
@@ -650,7 +637,7 @@ describe('codex-exec.sh — preflight (unchanged invariants)', { concurrency: 2
650
637
 
651
638
  describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', { concurrency: 2 }, () => {
652
639
  const RESUME_INVARIANTS = [
653
- /(^|\n)resume(\n|$)/, /(^|\n)--ignore-user-config(\n|$)/, /(^|\n)gpt-6-astra(\n|$)/,
640
+ /(^|\n)resume(\n|$)/, /(^|\n)--ignore-user-config(\n|$)/, /(^|\n)gpt-6\.1-sol(\n|$)/,
654
641
  /model_reasoning_effort=high/, /sandbox_mode=workspace-write/,
655
642
  /approval_policy=never/, /sandbox_workspace_write\.network_access=false/,
656
643
  ];
@@ -1216,23 +1203,8 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
1216
1203
  }
1217
1204
  });
1218
1205
 
1219
- it('the catalog claims CODEX_PROBE over the resume modes because the guard really precedes resume parsing', async () => {
1220
- // Verified in source: the quality guard runs BEFORE the resume dispatch, so a resume run is
1221
- // relaxed too — the catalog must say so, and this pins the ORDER the claim rests on.
1222
- const lines = executableLines(source);
1223
- const guardAt = lines.findIndex((l) => /\$\{?CODEX_PROBE/.test(l));
1224
- const resumeAt = lines.findIndex((l) => /^\s*--resume-last\)/.test(l));
1225
- assert.ok(guardAt !== -1 && resumeAt !== -1, 'both the probe guard and the resume arm must exist');
1226
- assert.ok(guardAt < resumeAt, 'the probe guard must precede resume parsing — else the resume parents are a false claim');
1227
- const hook = catalog.find((e) => e.key === 'CODEX_PROBE');
1228
- for (const key of ['exec.resume-last', 'exec.resume']) {
1229
- assert.ok(hook.parents.includes(key), `${key} is relaxed by the hook, so the catalog must declare it a parent`);
1230
- }
1231
- });
1232
-
1233
- it('CODEX_PROBE really relaxes the guard on EVERY execute parent the catalog claims (behavioural)', async () => {
1234
- // Source ORDER alone is not the claim: a branch bug after resume parsing would keep it green.
1235
- // Drive each claimed parent for real — the guard must stop the dispatch without the hook, and
1206
+ it('a one-off is refused on EVERY execute parent and CODEX_PROBE=1 runs it (behavioural)', async () => {
1207
+ // Drive each claimed parent for real — the one-off must stop the dispatch without the hook, and
1236
1208
  // codex must really be reached with it (r.argv is non-empty only on a real invocation).
1237
1209
  const hook = catalog.find((e) => e.key === 'CODEX_PROBE');
1238
1210
  const drive = {
@@ -1244,20 +1216,21 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
1244
1216
  'exec.resume': () => ({ args: ['--resume', 'sess-xyz', '-'], input: 'continue' }),
1245
1217
  };
1246
1218
  const execParents = hook.parents.filter((p) => execEntries.some((x) => x.key === p));
1247
- assert.ok(execParents.length > 0, 'CODEX_PROBE must claim at least one execute parent');
1219
+ assert.deepEqual(execParents, ['exec', 'exec.resume-last', 'exec.resume'], 'CODEX_PROBE claims every execute parent');
1248
1220
  for (const parent of execParents) {
1249
1221
  assert.ok(drive[parent], `no behavioural drive for claimed parent "${parent}" — add one`);
1250
1222
 
1251
1223
  const guarded = makeSandbox();
1252
- const off = await run(guarded, { ...drive[parent](guarded), env: { CODEX_MODEL: 'not-the-pinned-model' } });
1224
+ const off = await run(guarded, { ...drive[parent](guarded), env: { CODEX_MODEL: 'gpt-6-astra' } });
1253
1225
  rmSync(guarded.root, { recursive: true, force: true });
1254
- assert.equal(off.status, 2, `${parent}: the quality guard must refuse an off-pin model without the hook`);
1255
- assert.equal(off.argv, '', `${parent}: the guard must refuse BEFORE spending a run`);
1226
+ assert.equal(off.status, 2, `${parent}: a one-off must be refused without the hook`);
1227
+ assert.match(off.stderr, /one-off/, `${parent}: the refusal names the one-off`);
1228
+ assert.equal(off.argv, '', `${parent}: the one-off must refuse BEFORE spending a run`);
1256
1229
 
1257
1230
  const probed = makeSandbox();
1258
- const on = await run(probed, { ...drive[parent](probed), env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
1231
+ const on = await run(probed, { ...drive[parent](probed), env: { CODEX_MODEL: 'gpt-6-astra', CODEX_PROBE: '1' } });
1259
1232
  rmSync(probed.root, { recursive: true, force: true });
1260
- assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really relax the guard — the catalog claims it does`);
1233
+ assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really run the one-off — the catalog claims it does`);
1261
1234
  assert.notEqual(on.argv, '', `${parent}: CODEX_PROBE=1 must really reach codex, not merely exit 0`);
1262
1235
  }
1263
1236
  });
@@ -1489,7 +1462,7 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
1489
1462
  const help = runHelp('--help').stdout;
1490
1463
  const section = helpSection(help, SETTINGS_HEADER);
1491
1464
  const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
1492
- const want = (MANIFEST.settings ?? []).filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1465
+ const want = (MANIFEST.settings ?? []).filter((s) => s.kind !== 'posture' && s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1493
1466
  assert.ok(want.length > 0, 'the manifest must declare settings for this wrapper');
1494
1467
  setEq(got, want, 'help Settings keys ⟷ manifest settings.appliesTo');
1495
1468
  assert.ok(section.some((l) => l.includes('agent-workflow/bridge-settings.conf')), 'the section names the settings file');
@@ -1507,7 +1480,7 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
1507
1480
  it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', async () => {
1508
1481
  const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
1509
1482
  assert.ok(m, 'AW_SETTINGS_APPLIED not found');
1510
- const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1483
+ const want = ALL_SETTINGS.filter((s) => s.kind !== 'posture' && s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1511
1484
  assert.ok(want.length > 0);
1512
1485
  setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
1513
1486
  });
@@ -1516,8 +1489,8 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
1516
1489
  const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
1517
1490
  assert.ok(body, 'aw_settings_valid not found');
1518
1491
  const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
1519
- setEq(armKeys, ALL_SETTINGS.map((s) => s.key), 'validation arms ⟷ manifest keys');
1520
- for (const s of ALL_SETTINGS) {
1492
+ setEq(armKeys, ALL_SETTINGS.filter((s) => s.kind !== 'posture').map((s) => s.key), 'validation arms ⟷ manifest keys (a posture key is read by aw_read_posture, never validated here)');
1493
+ for (const s of ALL_SETTINGS.filter((x) => x.kind !== 'posture')) {
1521
1494
  const arm = body[0].match(new RegExp(`^ ${s.key}\\) (.*) ;;$`, 'm'));
1522
1495
  assert.ok(arm, `no validation arm for ${s.key}`);
1523
1496
  if (s.kind === 'enum') for (const v of s.values) assert.ok(arm[1].includes(`"${v}"`), `${s.key}: enum value '${v}' not pinned`);
@@ -1546,7 +1519,7 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', { concurren
1546
1519
  const lines = banners(r.stderr);
1547
1520
  assert.equal(lines.length, 1, 'EXACTLY ONE banner line per run');
1548
1521
  assert.equal(lines[0],
1549
- 'exec posture: model=gpt-6-astra effort=high tier=standard sandbox=workspace-write session=fresh timeout=3600s');
1522
+ 'exec posture: model=gpt-6.1-sol effort=high tier=standard sandbox=workspace-write session=fresh source=model:default,effort:default timeout=3600s');
1550
1523
  });
1551
1524
 
1552
1525
  it('an ARMED Fast tier rides the banner (tier=priority)', async () => {
@@ -1642,11 +1615,11 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', { concurren
1642
1615
 
1643
1616
  it('a banner field carrying CONTROL BYTES refuses pre-spend (model / effort / tier / timeout / DEL)', async () => {
1644
1617
  const cases = [
1645
- { CODEX_MODEL: `gpt-6-astra${String.fromCharCode(1)}` },
1618
+ { CODEX_MODEL: `gpt-6.1-sol${String.fromCharCode(1)}` },
1646
1619
  { CODEX_EFFORT: `high${String.fromCharCode(2)}` },
1647
1620
  { CODEX_SERVICE_TIER: `priority${String.fromCharCode(3)}` },
1648
1621
  { CODEX_HARD_TIMEOUT: `3600${String.fromCharCode(4)}` },
1649
- { CODEX_MODEL: `gpt-6-astra${String.fromCharCode(127)}` },
1622
+ { CODEX_MODEL: `gpt-6.1-sol${String.fromCharCode(127)}` },
1650
1623
  ];
1651
1624
  for (const env of cases) {
1652
1625
  const sb = makeSandbox();
@@ -2014,7 +1987,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', { concurrency: 2
2014
1987
  assert.match(plain.stderr, /running codex WITHOUT a hard wall-clock cap/, 'the nonce-less lane is unchanged');
2015
1988
  });
2016
1989
 
2017
- it('a PREFLIGHT refusal leaves NO reservation (login guard, missing AGENTS.md, off-pin model)', async () => {
1990
+ it('a PREFLIGHT refusal leaves NO reservation (login guard, missing AGENTS.md, an unoffered model)', async () => {
2018
1991
  const cases = [
2019
1992
  { label: 'login', env: { CODEX_FAKE_LOGIN: 'Not logged in' } },
2020
1993
  { label: 'model', env: { CODEX_MODEL: 'gpt-5.4-mini' } },
@@ -2047,7 +2020,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', { concurrency: 2
2047
2020
  assert.equal(r.status, 71, r.stderr);
2048
2021
  assert.equal(held.state, 'reserved');
2049
2022
  assert.equal(held.wrapperVersion, MANIFEST.version, 'the receipt stamps the bridge version version-sync bumps');
2050
- assert.deepEqual(held.posture, { model: 'gpt-6-astra', effort: 'high', tier: null });
2023
+ assert.deepEqual(held.posture, { model: 'gpt-6.1-sol', effort: 'high', tier: null });
2051
2024
  assert.equal(held.capS, 3600);
2052
2025
  assert.equal(held.killGraceS, 15);
2053
2026
  assert.match(held.contractDigest, /^[0-9a-f]{64}$/);
@@ -0,0 +1,371 @@
1
+ import { describe, it, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync, cpSync, readdirSync, symlinkSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { spawnSync } from 'node:child_process';
8
+
9
+ const HERE = dirname(fileURLToPath(import.meta.url));
10
+ const EXEC = join(HERE, 'codex-exec.sh');
11
+ const REVIEW = join(HERE, 'codex-review.sh');
12
+ const MANIFEST = JSON.parse(readFileSync(join(HERE, '..', 'capability.json'), 'utf8'));
13
+ const REAL_TIMEOUT = spawnSync('bash', ['-c', 'type -P timeout'], { encoding: 'utf8' }).stdout.trim();
14
+ const ESC = String.fromCharCode(27);
15
+
16
+ const FAKE_CODEX = [
17
+ '#!/usr/bin/env bash',
18
+ 'set -u',
19
+ 'if [[ "${1:-}" == "login" ]]; then echo "Logged in using ChatGPT"; exit 0; fi',
20
+ 'if [[ "${1:-}" == "debug" ]]; then',
21
+ ' echo "catalog argv=[$*] home=${CODEX_HOME:-} keys=${OPENAI_API_KEY:-unset},${FOO_API_KEY:-unset},${OPENAI_BASE_URL:-unset}" >>"$CODEX_FAKE_LOG"',
22
+ ' if [[ -n "${CODEX_FAKE_CATALOG_SLEEP:-}" ]]; then sleep "$CODEX_FAKE_CATALOG_SLEEP"; fi',
23
+ ' if [[ -n "${CODEX_FAKE_CATALOG_EXIT:-}" ]]; then exit "$CODEX_FAKE_CATALOG_EXIT"; fi',
24
+ ' if grep -q model_provider "${CODEX_HOME:-$HOME/.codex}/config.toml" 2>/dev/null && [[ "$*" != *model_provider=openai* ]]; then',
25
+ ' cat "$CODEX_FAKE_USER_CATALOG"; exit 0',
26
+ ' fi',
27
+ ' cat "$CODEX_FAKE_CATALOG"; exit 0',
28
+ 'fi',
29
+ 'echo "run argv=[$*]" >>"$CODEX_FAKE_LOG"',
30
+ 'cat >/dev/null',
31
+ 'out=""; prev=""',
32
+ 'for a in "$@"; do if [[ "$prev" == "-o" ]]; then out="$a"; fi; prev="$a"; done',
33
+ 'if [[ -n "$out" ]]; then printf "fake answer\\nVerdict: ship\\n" >"$out"; fi',
34
+ 'echo "{\\"type\\":\\"thread.started\\",\\"thread_id\\":\\"sess-fake\\"}"',
35
+ '',
36
+ ].join('\n');
37
+
38
+ const FAKE_TIMEOUT = [
39
+ '#!/usr/bin/env bash',
40
+ 'if [[ " $* " == *" debug models "* ]]; then',
41
+ ' while [[ $# -gt 0 && "$1" != "codex" ]]; do shift; done',
42
+ ` exec "${REAL_TIMEOUT}" 1 "$@"`,
43
+ 'fi',
44
+ `exec "${REAL_TIMEOUT}" "$@"`,
45
+ '',
46
+ ].join('\n');
47
+
48
+ const entry = (slug, priority, efforts, level = 'medium', visibility = 'list') => ({
49
+ slug, priority, visibility, default_reasoning_level: level, supported_reasoning_levels: efforts.map((effort) => ({ effort })),
50
+ });
51
+ const ALL = ['low', 'medium', 'high', 'xhigh'];
52
+ const STANDARD = { models: [entry('gpt-6.1-sol', 1, ALL, 'low'), entry('gpt-6-astra', 2, ALL), entry('gpt-6-luna', 3, ['low', 'medium', 'high'])] };
53
+ const HOST_SETTING = 'CODEX_MODEL=gpt-6-luna\nCODEX_EFFORT=medium\n';
54
+
55
+ const SHARED = mkdtempSync(join(tmpdir(), 'codex-posture-'));
56
+ after(() => rmSync(SHARED, { recursive: true, force: true }));
57
+ const TEMPLATE = (() => {
58
+ const home = join(SHARED, 'template');
59
+ const repo = join(home, 'repo');
60
+ mkdirSync(join(home, 'bin'), { recursive: true });
61
+ mkdirSync(repo);
62
+ writeFileSync(join(home, 'bin', 'codex'), FAKE_CODEX, { mode: 0o755 });
63
+ const git = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
64
+ git('init', '-q');
65
+ git('config', 'user.email', 'probe@example.com');
66
+ git('config', 'user.name', 'probe');
67
+ writeFileSync(join(repo, 'AGENTS.md'), '# AGENTS\n\nHard Constraints: none (test fixture).\n');
68
+ git('add', '-A');
69
+ git('commit', '-qm', 'base');
70
+ writeFileSync(join(repo, 'plan.md'), '# a plan under review\n');
71
+ return home;
72
+ })();
73
+
74
+ let sequence = 0;
75
+ const sandbox = ({ settings, catalog = STANDARD, catalogText, userCatalog, fakeTimeout = false } = {}) => {
76
+ const home = join(SHARED, `sb-${++sequence}`);
77
+ cpSync(TEMPLATE, home, { recursive: true });
78
+ const sb = {
79
+ home, repo: join(home, 'repo'), bin: join(home, 'bin'), log: join(home, 'calls.log'),
80
+ catalog: join(home, 'catalog.json'), userCatalog: join(home, 'user-catalog.json'),
81
+ settingsFile: join(home, '.config', 'agent-workflow', 'bridge-settings.conf'),
82
+ };
83
+ writeFileSync(sb.catalog, catalogText ?? JSON.stringify(catalog));
84
+ writeFileSync(sb.userCatalog, JSON.stringify(userCatalog ?? { models: [] }));
85
+ if (settings !== undefined) {
86
+ mkdirSync(dirname(sb.settingsFile), { recursive: true });
87
+ writeFileSync(sb.settingsFile, settings);
88
+ }
89
+ if (fakeTimeout) writeFileSync(join(sb.bin, 'timeout'), FAKE_TIMEOUT, { mode: 0o755 });
90
+ return sb;
91
+ };
92
+
93
+ const drive = (sb, [wrapper, args], env = {}) => {
94
+ const r = spawnSync('bash', [wrapper, ...args], {
95
+ cwd: sb.repo, input: 'do the thing', encoding: 'utf8', timeout: 60000,
96
+ env: {
97
+ PATH: `${sb.bin}:${process.env.PATH}`, HOME: sb.home, TMPDIR: process.env.TMPDIR ?? '/tmp',
98
+ CODEX_FAKE_LOG: sb.log, CODEX_FAKE_CATALOG: sb.catalog, CODEX_FAKE_USER_CATALOG: sb.userCatalog, ...env,
99
+ },
100
+ });
101
+ const log = existsSync(sb.log) ? readFileSync(sb.log, 'utf8') : '';
102
+ return { status: r.status, stderr: r.stderr, log, ran: log.includes('run argv='), runArgv: (log.match(/^run argv=\[(.*)\]$/m) ?? [])[1] ?? '' };
103
+ };
104
+
105
+ const FRESH_EXEC = [EXEC, ['-']];
106
+ const PLAN_REVIEW = [REVIEW, ['plan', 'plan.md']];
107
+ const PARENTS = {
108
+ exec: () => FRESH_EXEC,
109
+ 'exec.resume-last': (sb) => {
110
+ writeFileSync(join(sb.repo, '.codex-last-session'), 'sess-1\n');
111
+ return [EXEC, ['--resume-last', '-']];
112
+ },
113
+ 'exec.resume': () => [EXEC, ['--resume', 'sess-1', '-']],
114
+ 'review.plan': () => PLAN_REVIEW,
115
+ 'review.code': (sb) => {
116
+ writeFileSync(join(sb.repo, 'pending.txt'), 'an uncommitted change\n');
117
+ return [REVIEW, ['code']];
118
+ },
119
+ };
120
+
121
+ const quote = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
122
+ const remedyRe = (sb, model, effort) => new RegExp(
123
+ `remedy: set these lines in ${quote(sb.settingsFile)}:\\s+CODEX_MODEL=${quote(model)}\\s+CODEX_EFFORT=${quote(effort)}\\s+and unset CODEX_MODEL and CODEX_EFFORT in the environment`);
124
+ const bannerOf = (stderr) => stderr.split('\n').find((line) => /^(exec|review) posture: /.test(line)) ?? '';
125
+ const assertRan = (r, model, effort) => {
126
+ assert.equal(r.status, 0, r.stderr);
127
+ assert.match(r.runArgv, new RegExp(`(^| )-m ${quote(model)}( |$)`), r.log);
128
+ assert.match(r.runArgv, new RegExp(`model_reasoning_effort=${quote(effort)}( |$)`), r.log);
129
+ };
130
+ const assertRefusedUnspent = (r) => {
131
+ assert.equal(r.status, 2, r.stderr);
132
+ assert.equal(r.ran, false, 'refused before any run is spent');
133
+ };
134
+
135
+ describe('a fresh host runs the built-in default — spec:bridge-model/S1', () => {
136
+ it('codex-exec and codex-review run gpt-6.1-sol at effort high and the banner names source default', () => {
137
+ const exec = drive(sandbox(), FRESH_EXEC);
138
+ assertRan(exec, 'gpt-6.1-sol', 'high');
139
+ assert.equal(bannerOf(exec.stderr),
140
+ 'exec posture: model=gpt-6.1-sol effort=high tier=standard sandbox=workspace-write session=fresh source=model:default,effort:default timeout=3600s');
141
+ const review = drive(sandbox(), PLAN_REVIEW);
142
+ assertRan(review, 'gpt-6.1-sol', 'high');
143
+ assert.equal(bannerOf(review.stderr),
144
+ 'review posture: model=gpt-6.1-sol effort=high tier=standard source=model:default,effort:default timeout=1800s');
145
+ });
146
+ });
147
+
148
+ describe('the setting is the host posture of every codex wrapper — spec:bridge-model/S2', () => {
149
+ for (const [label, parent] of [['codex-exec', FRESH_EXEC], ['codex-review', PLAN_REVIEW]]) {
150
+ it(`${label} runs the set model and effort without a probe flag, the banner naming source setting`, () => {
151
+ const r = drive(sandbox({ settings: HOST_SETTING }), parent);
152
+ assertRan(r, 'gpt-6-luna', 'medium');
153
+ assert.match(bannerOf(r.stderr), / model=gpt-6-luna effort=medium .*source=model:setting,effort:setting timeout=/);
154
+ assert.doesNotMatch(r.stderr, /unknown key/, 'the model keys are recognised beside the registry');
155
+ });
156
+ }
157
+ });
158
+
159
+ describe('an environment value equal to the host posture is no one-off — spec:bridge-model/S3', () => {
160
+ const cases = [
161
+ ['the setting restated in the env', HOST_SETTING, { CODEX_MODEL: 'gpt-6-luna', CODEX_EFFORT: 'medium' }, 'gpt-6-luna', 'medium', 'model:environment,effort:environment'],
162
+ ['env gpt-6.1-sol on a fresh host', undefined, { CODEX_MODEL: 'gpt-6.1-sol' }, 'gpt-6.1-sol', 'high', 'model:environment,effort:default'],
163
+ ['explicitly empty env keys on a fresh host', undefined, { CODEX_MODEL: '', CODEX_EFFORT: '' }, 'gpt-6.1-sol', 'high', 'model:environment,effort:environment'],
164
+ ];
165
+ for (const [label, settings, env, model, effort, source] of cases) {
166
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
167
+ it(`${label}: ${parent[0] === EXEC ? 'codex-exec' : 'codex-review'} runs without a probe flag, source environment`, () => {
168
+ const r = drive(sandbox({ settings }), parent, env);
169
+ assertRan(r, model, effort);
170
+ assert.match(bannerOf(r.stderr), new RegExp(` source=${source} timeout=`));
171
+ });
172
+ }
173
+ }
174
+ });
175
+
176
+ const ONE_OFFS = [
177
+ ['env gpt-6.1-sol', { CODEX_MODEL: 'gpt-6.1-sol' }, 'gpt-6.1-sol', 'medium'],
178
+ ['env CODEX_EFFORT=high alone', { CODEX_EFFORT: 'high' }, 'gpt-6-luna', 'high'],
179
+ ['an explicitly empty CODEX_MODEL=', { CODEX_MODEL: '' }, 'gpt-6.1-sol', 'medium'],
180
+ ['an explicitly empty CODEX_EFFORT=', { CODEX_EFFORT: '' }, 'gpt-6-luna', 'high'],
181
+ ];
182
+
183
+ describe('a one-off is refused pre-spend unless it is a probe — spec:bridge-model/S4', () => {
184
+ it('the probe hook names exactly the parents driven here', () => {
185
+ const hook = MANIFEST.modeCatalog.find((mode) => mode.key === 'CODEX_PROBE');
186
+ assert.deepEqual([...hook.parents].sort(), Object.keys(PARENTS).sort());
187
+ });
188
+ for (const [label, env, model, effort] of ONE_OFFS) {
189
+ for (const [parent, argsOf] of Object.entries(PARENTS)) {
190
+ it(`${label} on ${parent}: refused with the remedy, run under CODEX_PROBE=1`, () => {
191
+ const refusedSb = sandbox({ settings: HOST_SETTING });
192
+ const refused = drive(refusedSb, argsOf(refusedSb), env);
193
+ assertRefusedUnspent(refused);
194
+ assert.match(refused.stderr, /one-off/);
195
+ assert.match(refused.stderr, remedyRe(refusedSb, model, effort));
196
+ const probeSb = sandbox({ settings: HOST_SETTING });
197
+ const probed = drive(probeSb, argsOf(probeSb), { ...env, CODEX_PROBE: '1' });
198
+ assertRan(probed, model, effort);
199
+ assert.match(bannerOf(probed.stderr), / source=model:(environment|setting),effort:(environment|setting) timeout=/);
200
+ });
201
+ }
202
+ }
203
+ });
204
+
205
+ const CONTRACT = '# dispatch\n\n```aw-dispatch-contract\n{"nonce":"n1","scope":"probe"}\n```\n';
206
+ describe('a nonced probe one-off is refused; a probe review keeps its nonce — spec:bridge-model/S6', () => {
207
+ const nonced = [
208
+ ['AW_DISPATCH_NONCE', ['task.md'], { AW_DISPATCH_NONCE: 'n1' }],
209
+ ['--nonce', ['--nonce', 'n1', 'task.md'], {}],
210
+ ['--nonce on a resume', ['--resume', 'sess-1', '--nonce', 'n1', 'task.md'], {}],
211
+ ];
212
+ for (const [label, args, env] of nonced) {
213
+ it(`${label}: a probe one-off is refused before any reservation`, () => {
214
+ const sb = sandbox({ settings: HOST_SETTING });
215
+ writeFileSync(join(sb.repo, 'task.md'), CONTRACT);
216
+ const r = drive(sb, [EXEC, args], { ...env, CODEX_PROBE: '1', CODEX_MODEL: 'gpt-6.1-sol' });
217
+ assertRefusedUnspent(r);
218
+ assert.match(r.stderr, /cannot carry a dispatch nonce/);
219
+ assert.equal(existsSync(join(sb.repo, '.git', 'agent-workflow-exec-receipt-5-codex-n1.json')), false);
220
+ });
221
+ }
222
+ it('a nonced probe on the host posture runs and mints its receipt', () => {
223
+ const sb = sandbox({ settings: HOST_SETTING });
224
+ writeFileSync(join(sb.repo, 'task.md'), CONTRACT);
225
+ const r = drive(sb, [EXEC, ['--nonce', 'n1', 'task.md']], { CODEX_PROBE: '1' });
226
+ assertRan(r, 'gpt-6-luna', 'medium');
227
+ const receipt = JSON.parse(readFileSync(join(sb.repo, '.git', 'agent-workflow-exec-receipt-5-codex-n1.json'), 'utf8'));
228
+ assert.deepEqual(receipt.posture, { model: 'gpt-6-luna', effort: 'medium', tier: null });
229
+ });
230
+ it('a probe review one-off keeps its --nonce', () => {
231
+ const r = drive(sandbox({ settings: HOST_SETTING }), [REVIEW, ['plan', 'plan.md', '--nonce', 'n1']], { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-6.1-sol' });
232
+ assertRan(r, 'gpt-6.1-sol', 'medium');
233
+ });
234
+ });
235
+
236
+ describe('a model or effort flag after -- is refused pre-spend — spec:bridge-model/S7', () => {
237
+ for (const flag of [['-m', 'gpt-6-luna'], ['--model=gpt-6-luna'], ['-c', 'model_reasoning_effort=low'], ['--config', 'model=x'], ['-p', 'fast']]) {
238
+ it(`codex-exec refuses ${flag[0]} on the host posture`, () => {
239
+ const r = drive(sandbox(), [EXEC, ['-', '--', ...flag]]);
240
+ assertRefusedUnspent(r);
241
+ assert.match(r.stderr, /is not allowed/);
242
+ });
243
+ }
244
+ });
245
+
246
+ describe('a model the catalog does not offer is refused with the offered list — spec:bridge-model/S8', () => {
247
+ const offered = 'offered: gpt-6.1-sol, gpt-6-astra, gpt-6-luna';
248
+ const sources = [['the setting', { settings: 'CODEX_MODEL=gpt-unknown\n' }, {}], ['the environment', {}, { CODEX_MODEL: 'gpt-unknown' }]];
249
+ for (const [label, options, env] of sources) {
250
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
251
+ it(`a model from ${label} on ${parent[0] === EXEC ? 'codex-exec' : 'codex-review'}: the built-in default lines, whatever was refused`, () => {
252
+ const sb = sandbox(options);
253
+ const r = drive(sb, parent, env);
254
+ assertRefusedUnspent(r);
255
+ assert.match(r.stderr, /CODEX_MODEL='gpt-unknown' .*not a model the installed codex offers/);
256
+ assert.ok(r.stderr.includes(offered), r.stderr);
257
+ assert.match(r.stderr, remedyRe(sb, 'gpt-6.1-sol', 'high'));
258
+ });
259
+ }
260
+ }
261
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
262
+ it(`a dash-leading model on ${parent[0] === EXEC ? 'codex-exec' : 'codex-review'} is refused as not offered, never read as a node option`, () => {
263
+ const r = drive(sandbox({ settings: 'CODEX_MODEL=--version\n' }), parent);
264
+ assertRefusedUnspent(r);
265
+ assert.match(r.stderr, /CODEX_MODEL='--version' \(setting\) is not a model the installed codex offers/);
266
+ });
267
+ }
268
+ it('the default is restored even when another offered slug has a smaller priority number', () => {
269
+ const sb = sandbox({ settings: 'CODEX_MODEL=gpt-unknown\n', catalog: { models: [entry('gpt-6-luna', 0, ALL), entry('gpt-6.1-sol', 1, ALL, 'low')] } });
270
+ assert.match(drive(sb, FRESH_EXEC).stderr, remedyRe(sb, 'gpt-6.1-sol', 'high'));
271
+ });
272
+ it('the default itself is refused when not offered; the remedy is the smallest-priority entry whole', () => {
273
+ const sb = sandbox({ catalog: { models: [entry('gpt-6-astra', 5, ALL), entry('gpt-6-luna', 2, ['low', 'medium'], 'low')] } });
274
+ const r = drive(sb, FRESH_EXEC);
275
+ assertRefusedUnspent(r);
276
+ assert.match(r.stderr, /CODEX_MODEL='gpt-6\.1-sol' \(default\)/);
277
+ assert.match(r.stderr, remedyRe(sb, 'gpt-6-luna', 'low'));
278
+ });
279
+ it('on a mixed catalog only visibility=list entries count — a hidden slug is refused and never named', () => {
280
+ const mixed = { models: [entry('gpt-secret', 0, ALL, 'xhigh', 'hide'), entry('gpt-6-luna', 2, ALL), entry('gpt-6.1-sol', 3, ['low', 'medium'], 'low')] };
281
+ const sb = sandbox({ settings: 'CODEX_MODEL=gpt-secret\n', catalog: mixed });
282
+ const r = drive(sb, FRESH_EXEC);
283
+ assertRefusedUnspent(r);
284
+ const afterRefusal = r.stderr.slice(r.stderr.indexOf('offered:'));
285
+ assert.ok(afterRefusal.includes('offered: gpt-6-luna, gpt-6.1-sol'), r.stderr);
286
+ assert.doesNotMatch(afterRefusal, /gpt-secret/);
287
+ assert.match(r.stderr, remedyRe(sb, 'gpt-6-luna', 'medium'));
288
+ });
289
+ });
290
+
291
+ describe('an effort the model does not offer is refused the same way — spec:bridge-model/S9', () => {
292
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
293
+ it(`${parent[0] === EXEC ? 'codex-exec' : 'codex-review'} names the model's efforts and the built-in default lines`, () => {
294
+ const sb = sandbox({ settings: 'CODEX_MODEL=gpt-6-luna\nCODEX_EFFORT=xhigh\n' });
295
+ const r = drive(sb, parent);
296
+ assertRefusedUnspent(r);
297
+ assert.match(r.stderr, /CODEX_EFFORT='xhigh' \(setting\) is not an effort the installed codex offers for gpt-6-luna/);
298
+ assert.ok(r.stderr.includes('offered: low, medium, high'), r.stderr);
299
+ assert.match(r.stderr, remedyRe(sb, 'gpt-6.1-sol', 'high'));
300
+ });
301
+ }
302
+ });
303
+
304
+ describe('an unreadable catalog is one stderr line and the run proceeds — spec:bridge-model/S10', () => {
305
+ const unreadable = [
306
+ ['a failing read', {}, { CODEX_FAKE_CATALOG_EXIT: '3' }],
307
+ ['a timed-out read', { fakeTimeout: true }, { CODEX_FAKE_CATALOG_SLEEP: '5' }],
308
+ ['output that is not JSON', { catalogText: 'not json at all' }, {}],
309
+ ['JSON without models[]', { catalog: { data: [] } }, {}],
310
+ ['a catalog that offers no model', { catalog: { models: [entry('gpt-6.1-sol', 1, ALL, 'low', 'hide')] } }, {}],
311
+ ['an offered entry without a slug', { catalog: { models: [{ ...entry('', 0, ALL), slug: undefined }, entry('gpt-6-luna', 2, ALL)] } }, {}],
312
+ ['the effective entry without supported_reasoning_levels', { catalog: { models: [{ slug: 'gpt-6.1-sol', priority: 1, visibility: 'list', default_reasoning_level: 'low' }] } }, {}],
313
+ ];
314
+ for (const [label, options, env] of unreadable) {
315
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
316
+ it(`${label} on ${parent[0] === EXEC ? 'codex-exec' : 'codex-review'}`, () => {
317
+ const r = drive(sandbox(options), parent, env);
318
+ assertRan(r, 'gpt-6.1-sol', 'high');
319
+ assert.equal(r.stderr.split('\n').filter((line) => /catalog/.test(line)).length, 1, r.stderr);
320
+ assert.match(r.stderr, /^warning: the codex model catalog is unreadable \(.+\) — the run proceeds/m);
321
+ });
322
+ }
323
+ }
324
+ it('without a timeout binary the nonce-less codex-exec still reads the catalog, unbounded', () => {
325
+ const sb = sandbox();
326
+ const farm = join(sb.home, 'farm');
327
+ mkdirSync(farm);
328
+ const linked = new Set(['timeout', 'gtimeout']);
329
+ for (const dir of process.env.PATH.split(':').filter(Boolean)) {
330
+ for (const name of existsSync(dir) ? readdirSync(dir) : []) {
331
+ if (!linked.has(name)) symlinkSync(resolve(dir, name), join(farm, name));
332
+ linked.add(name);
333
+ }
334
+ }
335
+ const r = drive(sb, FRESH_EXEC, { PATH: `${sb.bin}:${farm}` });
336
+ assertRan(r, 'gpt-6.1-sol', 'high');
337
+ assert.match(r.log, /^catalog /m);
338
+ assert.match(bannerOf(r.stderr), / timeout=uncapped$/);
339
+ });
340
+ });
341
+
342
+ describe('the catalog read follows the subscription guard and ignores a user config — spec:bridge-model/S11', () => {
343
+ for (const parent of [FRESH_EXEC, PLAN_REVIEW]) {
344
+ it(`${parent[0] === EXEC ? 'codex-exec' : 'codex-review'} reads the run's CODEX_HOME catalog with the built-in provider, keys cleared`, () => {
345
+ const sb = sandbox({ userCatalog: { models: [entry('their-model', 1, ALL)] } });
346
+ const codexHome = join(sb.home, 'codex-home');
347
+ mkdirSync(codexHome);
348
+ writeFileSync(join(codexHome, 'config.toml'), 'model_provider = "their-proxy"\n');
349
+ const r = drive(sb, parent, { CODEX_HOME: codexHome, OPENAI_API_KEY: 'sk-live', FOO_API_KEY: 'foo', OPENAI_BASE_URL: 'http://proxy' });
350
+ assertRan(r, 'gpt-6.1-sol', 'high');
351
+ assert.match(r.log, new RegExp(`^catalog argv=\\[.*\\] home=${quote(codexHome)} keys=unset,unset,unset$`, 'm'));
352
+ });
353
+ }
354
+ });
355
+
356
+ describe('a settings-file model value with a control byte or an empty one falls back — spec:bridge-model/S17', () => {
357
+ const values = [
358
+ ['a control byte', `CODEX_MODEL=gpt-6-luna${ESC}[31m\nCODEX_EFFORT=medium${ESC}\n`],
359
+ ['an empty value', 'CODEX_MODEL=\nCODEX_EFFORT=\n'],
360
+ ];
361
+ for (const [label, settings] of values) {
362
+ it(`${label}: each key warns without the raw byte and the run takes the built-in default`, () => {
363
+ const r = drive(sandbox({ settings }), FRESH_EXEC);
364
+ assertRan(r, 'gpt-6.1-sol', 'high');
365
+ assert.equal(r.stderr.includes(ESC), false, 'no raw control byte reaches stderr');
366
+ assert.match(r.stderr, /warning: CODEX_MODEL in bridge settings file .* built-in default/);
367
+ assert.match(r.stderr, /warning: CODEX_EFFORT in bridge settings file .* built-in default/);
368
+ assert.match(bannerOf(r.stderr), / source=model:default,effort:default timeout=/);
369
+ });
370
+ }
371
+ });