@sabaiway/agent-workflow-kit 14.19.1 → 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.
- package/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +45 -50
- package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
- package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
- package/bridges/antigravity-cli-bridge/capability.json +14 -7
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
- package/bridges/codex-cli-bridge/SKILL.md +57 -49
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
- package/bridges/codex-cli-bridge/capability.json +22 -8
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
- package/bridges/codex-cli-bridge/setup/README.md +3 -3
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/bridge-settings.md +10 -7
- package/references/modes/jev.md +3 -2
- package/references/modes/recipes.md +1 -1
- package/references/modes/recommendations.md +2 -1
- package/references/modes/review-state.md +1 -1
- package/references/vendor/typesafe-ai/LICENSE +21 -0
- package/references/vendor/typesafe-ai/SKILL.md.pinned +149 -0
- package/tools/ack-store.mjs +2 -0
- package/tools/bridge-catalog.mjs +114 -0
- package/tools/bridge-posture.mjs +11 -2
- package/tools/bridge-settings-read.mjs +41 -5
- package/tools/bridge-settings.mjs +111 -23
- package/tools/detect-backends.mjs +6 -6
- package/tools/family-registry.mjs +3 -3
- package/tools/jev-facts.mjs +101 -4
- package/tools/jev-guide.mjs +31 -12
- package/tools/jev-skill.mjs +231 -0
- package/tools/manifest/schema.md +1 -1
- package/tools/manifest/validate.mjs +3 -2
- package/tools/procedures.mjs +3 -3
- package/tools/recipes.mjs +2 -2
- package/tools/recommendations.mjs +49 -4
- 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 —
|
|
186
|
-
it('
|
|
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: {
|
|
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-
|
|
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-
|
|
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('
|
|
1220
|
-
//
|
|
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.
|
|
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: '
|
|
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}:
|
|
1255
|
-
assert.
|
|
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: '
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
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-
|
|
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
|
+
});
|