@sabaiway/agent-workflow-kit 14.22.0 → 14.23.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 +49 -0
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
- package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
- package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
- package/bridges/antigravity-cli-bridge/capability.json +3 -3
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
- package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
- package/bridges/codex-cli-bridge/SKILL.md +44 -24
- package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
- package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
- package/bridges/codex-cli-bridge/capability.json +10 -10
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
- package/bridges/codex-cli-bridge/setup/README.md +8 -5
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +1 -1
- package/references/modes/velocity.md +1 -1
- package/tools/detect-backends.mjs +3 -3
- package/tools/manifest/schema.md +2 -2
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// codex-exec-jev.test.mjs — spec jev-every-run on codex-exec: the shared key rule, the posture the wrapper chooses
|
|
2
|
+
// (the jev permissions profile or today's flags), the loud failure, the banner, the directive and the probe
|
|
3
|
+
// passthrough. Standalone harness with its own fake codex and a fake uname.
|
|
4
|
+
import { describe, it, after } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdirSync, mkdtempSync, writeFileSync, readFileSync, existsSync, rmSync, cpSync, chmodSync, realpathSync, statSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const EXEC = join(HERE, 'codex-exec.sh');
|
|
14
|
+
const HOSTS = 'api.typesafe.ai,docs.typesafe.ai';
|
|
15
|
+
// The repositories sit outside every temp root: under the package's node_modules, TMPDIR their sibling, never an ancestor.
|
|
16
|
+
const FIXTURES = join(HERE, '..', 'node_modules', '.aw-fixtures');
|
|
17
|
+
const TMP = join(FIXTURES, 'tmp');
|
|
18
|
+
mkdirSync(TMP, { recursive: true });
|
|
19
|
+
mkdirSync(join(FIXTURES, 'repos'), { recursive: true });
|
|
20
|
+
const ROOT = mkdtempSync(join(FIXTURES, 'repos', 'exec-jev-'));
|
|
21
|
+
after(() => rmSync(ROOT, { recursive: true, force: true }));
|
|
22
|
+
const REAL_UNAME = spawnSync('bash', ['-c', 'type -P uname'], { encoding: 'utf8' }).stdout.trim();
|
|
23
|
+
|
|
24
|
+
// Every spawn of the CLI appends one line: its kind and the state of each key ([value] or unset).
|
|
25
|
+
const FAKE_CODEX = [
|
|
26
|
+
'#!/usr/bin/env bash',
|
|
27
|
+
'set -u',
|
|
28
|
+
'st() { if [[ -n "${!1+x}" ]]; then printf "[%s]" "${!1}"; else printf unset; fi; }',
|
|
29
|
+
'rec() { echo "$1 key=$(st TYPESAFE_API_KEY) openai=$(st OPENAI_API_KEY) codex=$(st CODEX_API_KEY) foo=$(st FOO_API_KEY) base=$(st OPENAI_BASE_URL)" >>"$CODEX_FAKE_LOG"; }',
|
|
30
|
+
'case "${1:-}" in',
|
|
31
|
+
' login) rec login; echo "Logged in using ChatGPT"; exit 0 ;;',
|
|
32
|
+
' --version) rec version; echo "${CODEX_FAKE_VERSION-codex-cli 0.160.0}"; exit 0 ;;',
|
|
33
|
+
' debug) rec catalog; cat <<EOF',
|
|
34
|
+
'{"models":[{"slug":"gpt-6.1-sol","priority":1,"visibility":"list","default_reasoning_level":"low","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"}]}]}',
|
|
35
|
+
'EOF',
|
|
36
|
+
' exit 0 ;;',
|
|
37
|
+
'esac',
|
|
38
|
+
'rec run',
|
|
39
|
+
'{ for a in "$@"; do echo "$a"; done; } >"$CODEX_FAKE_ARGV"',
|
|
40
|
+
'cat >"$CODEX_FAKE_STDIN"',
|
|
41
|
+
'out=""; prev=""; for a in "$@"; do if [[ "$prev" == "-o" ]]; then out="$a"; fi; prev="$a"; done',
|
|
42
|
+
'if [[ -n "$out" ]]; then echo "${CODEX_FAKE_FINAL:-FAKE_FINAL}" >"$out"; fi',
|
|
43
|
+
'echo "{\\"type\\":\\"thread.started\\",\\"thread_id\\":\\"sess-jev\\"}"',
|
|
44
|
+
'if [[ -n "${CODEX_FAKE_PRE:-}" ]]; then echo "$CODEX_FAKE_PRE"; fi',
|
|
45
|
+
'echo "{\\"type\\":\\"turn.started\\"}"',
|
|
46
|
+
'if [[ -n "${CODEX_FAKE_POST:-}" ]]; then echo "$CODEX_FAKE_POST"; fi',
|
|
47
|
+
'echo "{\\"type\\":\\"turn.completed\\",\\"usage\\":{}}"',
|
|
48
|
+
'exit "${CODEX_FAKE_EXIT:-0}"',
|
|
49
|
+
'',
|
|
50
|
+
].join('\n');
|
|
51
|
+
const FAKE_UNAME = ['#!/usr/bin/env bash', 'if [[ $# -eq 0 || "$1" == "-s" ]]; then echo "${CODEX_FAKE_UNAME:-Linux}"; exit 0; fi',
|
|
52
|
+
`exec "${REAL_UNAME}" "$@"`, ''].join('\n');
|
|
53
|
+
const CONTRACT = '# dispatch\n\n```aw-dispatch-contract\n{"nonce":"n1","scope":"probe"}\n```\n';
|
|
54
|
+
|
|
55
|
+
const initRepo = (repo) => {
|
|
56
|
+
mkdirSync(repo, { recursive: true });
|
|
57
|
+
const git = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
|
|
58
|
+
git('init', '-q');
|
|
59
|
+
git('config', 'user.email', 'probe@example.com');
|
|
60
|
+
git('config', 'user.name', 'probe');
|
|
61
|
+
writeFileSync(join(repo, 'AGENTS.md'), '# AGENTS\n\nHard Constraints: none (test fixture).\n');
|
|
62
|
+
git('add', '-A');
|
|
63
|
+
git('commit', '-qm', 'base');
|
|
64
|
+
writeFileSync(join(repo, 'task.md'), CONTRACT);
|
|
65
|
+
};
|
|
66
|
+
const TEMPLATE = join(ROOT, 'template');
|
|
67
|
+
mkdirSync(join(TEMPLATE, 'bin'), { recursive: true });
|
|
68
|
+
mkdirSync(join(TEMPLATE, 'home'));
|
|
69
|
+
writeFileSync(join(TEMPLATE, 'bin', 'codex'), FAKE_CODEX);
|
|
70
|
+
writeFileSync(join(TEMPLATE, 'bin', 'uname'), FAKE_UNAME);
|
|
71
|
+
initRepo(join(TEMPLATE, 'repo'));
|
|
72
|
+
|
|
73
|
+
let sequence = 0;
|
|
74
|
+
const sandbox = () => {
|
|
75
|
+
const dir = join(ROOT, `sb-${++sequence}`);
|
|
76
|
+
cpSync(TEMPLATE, dir, { recursive: true });
|
|
77
|
+
for (const name of ['codex', 'uname']) chmodSync(join(dir, 'bin', name), 0o755);
|
|
78
|
+
return { dir, bin: join(dir, 'bin'), home: join(dir, 'home'), repo: join(dir, 'repo'),
|
|
79
|
+
state: join(dir, 'home', '.local', 'state', 'agent-workflow', 'codex-jev-profile-refused') };
|
|
80
|
+
};
|
|
81
|
+
const drive = (sb, args, env = {}, cwd = sb.repo) => {
|
|
82
|
+
const cap = (name) => join(sb.dir, name);
|
|
83
|
+
for (const name of ['log', 'argv', 'stdin']) rmSync(cap(name), { force: true });
|
|
84
|
+
const r = spawnSync('bash', [EXEC, ...args], {
|
|
85
|
+
cwd, input: 'do the thing', encoding: 'utf8', timeout: 60000,
|
|
86
|
+
env: { PATH: `${sb.bin}:${process.env.PATH}`, HOME: sb.home, TMPDIR: TMP, CODEX_FAKE_LOG: cap('log'),
|
|
87
|
+
CODEX_FAKE_ARGV: cap('argv'), CODEX_FAKE_STDIN: cap('stdin'), ...env },
|
|
88
|
+
});
|
|
89
|
+
const read = (name) => (existsSync(cap(name)) ? readFileSync(cap(name), 'utf8') : '');
|
|
90
|
+
return { status: r.status, stdout: r.stdout, stderr: r.stderr, log: read('log'), argv: read('argv').split('\n').slice(0, -1),
|
|
91
|
+
prompt: read('stdin').replace(/\s+/g, ' ') };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// -c key=value pairs of the run's argv, and a reader for the TOML inline tables a profile override carries.
|
|
95
|
+
const overrides = (argv) => Object.fromEntries(argv.flatMap((arg, i) => (argv[i - 1] === '-c'
|
|
96
|
+
? [[arg.slice(0, arg.indexOf('=')), arg.slice(arg.indexOf('=') + 1)]] : [])));
|
|
97
|
+
const inline = (text) => {
|
|
98
|
+
let at = 0;
|
|
99
|
+
const eat = (re) => {
|
|
100
|
+
const m = re.exec(text.slice(at));
|
|
101
|
+
assert.ok(m, `an inline table: ${text}`);
|
|
102
|
+
at += m[0].length;
|
|
103
|
+
return m;
|
|
104
|
+
};
|
|
105
|
+
const value = () => {
|
|
106
|
+
if (eat(/^\s*(\{?)/)[1] === '') return eat(/^"([^"]*)"|^([\w.:-]+)/).slice(1).find((part) => part !== undefined);
|
|
107
|
+
const table = {};
|
|
108
|
+
while (eat(/^\s*(\}?)/)[1] === '') {
|
|
109
|
+
const key = value();
|
|
110
|
+
eat(/^\s*=/);
|
|
111
|
+
table[key] = value();
|
|
112
|
+
eat(/^\s*,?/);
|
|
113
|
+
}
|
|
114
|
+
return table;
|
|
115
|
+
};
|
|
116
|
+
const parsed = value();
|
|
117
|
+
assert.equal(text.slice(at).trim(), '', `nothing after the inline table: ${text}`);
|
|
118
|
+
return parsed;
|
|
119
|
+
};
|
|
120
|
+
const EXEC_FS = { ':root': 'read', ':tmpdir': 'write', ':slash_tmp': 'write', ':workspace_roots': { '.': 'write' } };
|
|
121
|
+
const PROFILE_KEYS = ['default_permissions', 'permissions.jev.filesystem', 'permissions.jev.network.domains', 'permissions.jev.network.enabled'];
|
|
122
|
+
const profileKeys = (c) => Object.keys(c).filter((key) => key === 'default_permissions' || key.startsWith('permissions.')).sort();
|
|
123
|
+
const assertProfile = (r) => {
|
|
124
|
+
assert.equal(r.status, 0, r.stderr);
|
|
125
|
+
const c = overrides(r.argv);
|
|
126
|
+
assert.deepEqual(profileKeys(c), PROFILE_KEYS, r.argv.join(' '));
|
|
127
|
+
assert.equal(c.default_permissions.replace(/^"(.*)"$/, '$1'), 'jev');
|
|
128
|
+
assert.equal(c['permissions.jev.network.enabled'], 'true');
|
|
129
|
+
assert.deepEqual(inline(c['permissions.jev.network.domains']), { 'api.typesafe.ai': 'allow', 'docs.typesafe.ai': 'allow' });
|
|
130
|
+
assert.deepEqual(inline(c['permissions.jev.filesystem']), EXEC_FS);
|
|
131
|
+
assert.equal(r.argv.includes('--sandbox') || 'sandbox_mode' in c, false, 'neither --sandbox nor sandbox_mode beside the profile');
|
|
132
|
+
};
|
|
133
|
+
const assertFallback = (r) => {
|
|
134
|
+
assert.equal(r.status, 0, r.stderr);
|
|
135
|
+
const c = overrides(r.argv);
|
|
136
|
+
assert.deepEqual(profileKeys(c), [], r.argv.join(' '));
|
|
137
|
+
assert.ok(r.argv[r.argv.indexOf('--sandbox') + 1] === 'workspace-write' || c.sandbox_mode === 'workspace-write', r.argv.join(' '));
|
|
138
|
+
assert.equal(c['sandbox_workspace_write.network_access'], 'false');
|
|
139
|
+
};
|
|
140
|
+
const bannerOf = (r) => r.stderr.split('\n').find((line) => line.startsWith('exec posture: ')) ?? '';
|
|
141
|
+
const banner = (sandboxValue, jev, session = 'fresh') => 'exec posture: model=gpt-6.1-sol effort=high tier=standard '
|
|
142
|
+
+ `sandbox=${sandboxValue} session=${session} jev=${jev} source=model:default,effort:default timeout=3600s`;
|
|
143
|
+
const unreachable = (reason) => banner('workspace-write', `unreachable (${reason})`);
|
|
144
|
+
const KEYS = { TYPESAFE_API_KEY: 'jev-key', OPENAI_API_KEY: 'sk-live', CODEX_API_KEY: 'ck', FOO_API_KEY: 'foo', OPENAI_BASE_URL: 'http://proxy' };
|
|
145
|
+
const PASSED = 'key=[jev-key] openai=unset codex=unset foo=unset base=unset';
|
|
146
|
+
const spawnsOf = (r) => r.log.split('\n').filter(Boolean).map((line) => [line.slice(0, line.indexOf(' ')), line.slice(line.indexOf(' ') + 1)]);
|
|
147
|
+
const runEnv = (r) => (spawnsOf(r).find(([kind]) => kind === 'run') ?? [])[1];
|
|
148
|
+
const item = (body) => JSON.stringify({ type: 'item.completed', item: body });
|
|
149
|
+
const P13_TEXT = 'Configured filesystem path `:slash_tmp` is not recognized by this version of Codex and will be ignored. Upgrade Codex if this path is required.';
|
|
150
|
+
const P13 = item({ id: 'item_0', type: 'error', message: P13_TEXT });
|
|
151
|
+
const P1 = item({ id: 'item_0', type: 'error', message: 'Permissions profile `jev` does not define any recognized filesystem entries for this version of Codex. Filesystem access will remain restricted.' });
|
|
152
|
+
const OTHER_SHAPE = item({ id: 'item_0', type: 'error', message: 'Unknown configuration key `permissions.jev.network.mode`.' });
|
|
153
|
+
const command = (output, exitCode, status) => item({ id: 'item_1', type: 'command_execution', command: 'bash -lc ls', aggregated_output: output, exit_code: exitCode, status });
|
|
154
|
+
const BWRAP = 'bwrap: execvp /usr/lib/node_modules/@openai/codex/vendor/x86_64-unknown-linux-musl/bin/codex: No such file or directory\n';
|
|
155
|
+
const receiptOf = (sb) => JSON.parse(readFileSync(join(sb.repo, '.git', 'agent-workflow-exec-receipt-5-codex-n1.json'), 'utf8'));
|
|
156
|
+
const RECEIPT_KEYS = ['schema', 'kind', 'state', 'backend', 'nonce', 'owner', 'contractDigest', 'wrapperVersion', 'posture', 'capS',
|
|
157
|
+
'killGraceS', 'sessionId', 'exitStatus', 'outcome', 'reportDigest', 'reportLength', 'timestamp'];
|
|
158
|
+
const stateLines = (sb) => (existsSync(sb.state) ? readFileSync(sb.state, 'utf8').split('\n').filter(Boolean) : []);
|
|
159
|
+
|
|
160
|
+
describe('the key rule on codex-exec — spec:jev-every-run/S1', () => {
|
|
161
|
+
it('a fresh run hands codex only TYPESAFE_API_KEY of the keys, and the login, version and catalog spawns see the same env', () => {
|
|
162
|
+
const r = drive(sandbox(), ['-'], KEYS);
|
|
163
|
+
assert.equal(r.status, 0, r.stderr);
|
|
164
|
+
assert.deepEqual([...new Set(spawnsOf(r).map(([kind]) => kind))].sort(), ['catalog', 'login', 'run', 'version'], r.log);
|
|
165
|
+
for (const [kind, env] of spawnsOf(r)) assert.equal(env, PASSED, `${kind} spawn`);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('a resumed run — spec:jev-every-run/S2', () => {
|
|
170
|
+
it('keeps the key rule and carries the profile overrides with no sandbox_mode', () => {
|
|
171
|
+
const r = drive(sandbox(), ['--resume', 'sess-1', '-'], KEYS);
|
|
172
|
+
assertProfile(r);
|
|
173
|
+
assert.deepEqual(r.argv.slice(0, 3), ['exec', 'resume', 'sess-1']);
|
|
174
|
+
for (const [kind, env] of spawnsOf(r)) assert.equal(env, PASSED, `${kind} spawn`);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('an empty key and an unset key — spec:jev-every-run/S5', () => {
|
|
179
|
+
it('an empty TYPESAFE_API_KEY reaches codex empty, an unset one stays unset, and the argv is the same with the key set', () => {
|
|
180
|
+
const runs = ['jev-key', '', undefined].map((value) => drive(sandbox(), ['-'], { TYPESAFE_API_KEY: value }));
|
|
181
|
+
assert.deepEqual(runs.map((r) => runEnv(r)?.split(' ')[0]), ['key=[jev-key]', 'key=[]', 'key=unset']);
|
|
182
|
+
const normalized = runs.map((r) => r.argv.map((arg, i) => (r.argv[i - 1] === '-o' ? '<out>' : arg)).join(' '));
|
|
183
|
+
assert.equal(normalized[1], normalized[0]);
|
|
184
|
+
assert.equal(normalized[2], normalized[0]);
|
|
185
|
+
assertProfile(runs[0]);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('the exec profile on Linux from codex-cli 0.160.0 — spec:jev-every-run/S7', () => {
|
|
190
|
+
for (const version of ['0.160.0', '0.161.0', '1.0.0']) {
|
|
191
|
+
it(`codex-cli ${version}: the exec profile exactly, neither --sandbox nor sandbox_mode`, () => {
|
|
192
|
+
assertProfile(drive(sandbox(), ['-'], { CODEX_FAKE_VERSION: `codex-cli ${version}` }));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('the fallback by OS and by temp root — spec:jev-every-run/S9', () => {
|
|
198
|
+
it('a uname other than Linux runs today\'s flags with the key passed and names the reason', () => {
|
|
199
|
+
const r = drive(sandbox(), ['-'], { ...KEYS, CODEX_FAKE_UNAME: 'Darwin' });
|
|
200
|
+
assertFallback(r);
|
|
201
|
+
assert.equal(runEnv(r), PASSED);
|
|
202
|
+
assert.equal(bannerOf(r), unreachable('not linux (Darwin)'));
|
|
203
|
+
});
|
|
204
|
+
it('a repository root under the realpath of TMPDIR falls back; a sibling sharing its name prefix does not', () => {
|
|
205
|
+
const sb = sandbox();
|
|
206
|
+
const root = join(sb.dir, 'pfx');
|
|
207
|
+
cpSync(sb.repo, join(root, 'repo'), { recursive: true });
|
|
208
|
+
const under = drive(sb, ['-'], { ...KEYS, TMPDIR: root }, join(root, 'repo'));
|
|
209
|
+
assertFallback(under);
|
|
210
|
+
assert.equal(runEnv(under), PASSED);
|
|
211
|
+
assert.equal(bannerOf(under), unreachable(`repository under ${realpathSync(root)}`));
|
|
212
|
+
cpSync(sb.repo, join(sb.dir, 'pfx-repo'), { recursive: true });
|
|
213
|
+
assertProfile(drive(sb, ['-'], { TMPDIR: root }, join(sb.dir, 'pfx-repo')));
|
|
214
|
+
});
|
|
215
|
+
it('a worktree whose git common dir is under TMPDIR falls back', () => {
|
|
216
|
+
const sb = sandbox();
|
|
217
|
+
const root = join(sb.dir, 'tmproot');
|
|
218
|
+
cpSync(sb.repo, join(root, 'main'), { recursive: true });
|
|
219
|
+
spawnSync('git', ['worktree', 'add', '-q', join(sb.dir, 'wt')], { cwd: join(root, 'main'), encoding: 'utf8' });
|
|
220
|
+
writeFileSync(join(sb.dir, 'wt', 'task.md'), CONTRACT);
|
|
221
|
+
const r = drive(sb, ['-'], { TMPDIR: root }, join(sb.dir, 'wt'));
|
|
222
|
+
assertFallback(r);
|
|
223
|
+
assert.equal(bannerOf(r), unreachable(`repository under ${realpathSync(root)}`));
|
|
224
|
+
// A git older than 2.31 echoes an unknown --path-format=… as its own line before the answer.
|
|
225
|
+
const realGit = spawnSync('bash', ['-c', 'command -v git'], { encoding: 'utf8' }).stdout.trim();
|
|
226
|
+
writeFileSync(join(sb.bin, 'git'), ['#!/usr/bin/env bash', 'args=()', 'for a in "$@"; do',
|
|
227
|
+
' if [[ "$a" == --path-format=* ]]; then echo "$a"; else args+=("$a"); fi', 'done', `exec ${realGit} "\${args[@]}"`, ''].join('\n'));
|
|
228
|
+
chmodSync(join(sb.bin, 'git'), 0o755);
|
|
229
|
+
const old = drive(sb, ['-'], { TMPDIR: root }, join(sb.dir, 'wt'));
|
|
230
|
+
assertFallback(old);
|
|
231
|
+
assert.equal(bannerOf(old), unreachable(`repository under ${realpathSync(root)}`));
|
|
232
|
+
});
|
|
233
|
+
it('a repository under /tmp falls back naming /tmp', () => {
|
|
234
|
+
const repo = mkdtempSync(join(tmpdir(), 'exec-jev-tmp-'));
|
|
235
|
+
try {
|
|
236
|
+
cpSync(join(TEMPLATE, 'repo'), repo, { recursive: true });
|
|
237
|
+
const underTmp = realpathSync(repo).startsWith(`${realpathSync('/tmp')}/`);
|
|
238
|
+
const r = drive(sandbox(), ['-'], { TMPDIR: underTmp ? TMP : tmpdir() }, repo);
|
|
239
|
+
assertFallback(r);
|
|
240
|
+
assert.equal(bannerOf(r), unreachable(`repository under ${underTmp ? '/tmp' : realpathSync(tmpdir())}`));
|
|
241
|
+
} finally {
|
|
242
|
+
rmSync(repo, { recursive: true, force: true });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('the fallback by version and by the refused record — spec:jev-every-run/S10', () => {
|
|
248
|
+
const cases = [['codex-cli 0.159.0', 'codex 0.159.0 below 0.160.0'], ['codex-cli 0.160.0-alpha.1', 'codex 0.160.0-alpha.1 below 0.160.0'],
|
|
249
|
+
['codex-cli', 'codex version unreadable']];
|
|
250
|
+
for (const [line, reason] of cases) {
|
|
251
|
+
it(`a version line '${line}' runs today's flags with the key passed: ${reason}`, () => {
|
|
252
|
+
const r = drive(sandbox(), ['-'], { ...KEYS, CODEX_FAKE_VERSION: line });
|
|
253
|
+
assertFallback(r);
|
|
254
|
+
assert.equal(runEnv(r), PASSED);
|
|
255
|
+
assert.equal(bannerOf(r), unreachable(reason));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
it('a listed version with this run\'s digest falls back naming the file; another digest or another version runs the profile', () => {
|
|
259
|
+
const sb = sandbox();
|
|
260
|
+
assert.equal(drive(sb, ['-'], { CODEX_FAKE_PRE: P13 }).status, 72);
|
|
261
|
+
const [entry] = stateLines(sb);
|
|
262
|
+
assert.match(entry ?? '', /^0\.160\.0 [0-9a-f]{64}$/);
|
|
263
|
+
const refused = drive(sb, ['-'], KEYS);
|
|
264
|
+
assertFallback(refused);
|
|
265
|
+
assert.equal(runEnv(refused), PASSED);
|
|
266
|
+
assert.ok(bannerOf(refused).includes('jev=unreachable (profile not recognized by codex 0.160.0'), bannerOf(refused));
|
|
267
|
+
assert.ok(bannerOf(refused).includes(sb.state), 'the banner reason names the file');
|
|
268
|
+
for (const other of [`0.160.0 ${'a'.repeat(64)}`, `0.161.0 ${entry.split(' ')[1]}`]) {
|
|
269
|
+
writeFileSync(sb.state, `${other}\n`);
|
|
270
|
+
assertProfile(drive(sb, ['-']));
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('the profile error item before the turn — spec:jev-every-run/S11', () => {
|
|
276
|
+
for (const [label, shape] of [['the P13 shape', P13], ['the P1 shape', P1]]) {
|
|
277
|
+
it(`${label}, printed twice before turn.started: exit 72, one entry appended, a transport-failure receipt`, () => {
|
|
278
|
+
const sb = sandbox();
|
|
279
|
+
const r = drive(sb, ['--nonce', 'n1', 'task.md'], { CODEX_FAKE_PRE: `${shape}\n${shape}` });
|
|
280
|
+
assert.equal(r.status, 72, r.stderr);
|
|
281
|
+
assert.ok(r.stderr.includes(JSON.parse(shape).item.message), 'the matched message is printed');
|
|
282
|
+
assert.doesNotMatch(r.stderr, /update codex/i);
|
|
283
|
+
assert.doesNotMatch(r.stdout, /FAKE_FINAL/, 'no final message is printed');
|
|
284
|
+
assert.equal(stateLines(sb).length, 1);
|
|
285
|
+
assert.match(stateLines(sb)[0], /^0\.160\.0 [0-9a-f]{64}$/);
|
|
286
|
+
assert.equal(statSync(dirname(sb.state)).mode & 0o777, 0o700);
|
|
287
|
+
assert.equal(receiptOf(sb).outcome, 'transport-failure');
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
for (const [label, env] of [['after turn.started', { CODEX_FAKE_POST: P13 }], ['in another shape', { CODEX_FAKE_PRE: OTHER_SHAPE }]]) {
|
|
291
|
+
it(`${label}: the run ends as today and nothing is appended`, () => {
|
|
292
|
+
const sb = sandbox();
|
|
293
|
+
const r = drive(sb, ['-'], env);
|
|
294
|
+
assertProfile(r);
|
|
295
|
+
assert.equal(existsSync(sb.state), false);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
it('a failed write warns once naming the path and still exits 72 with its receipt', () => {
|
|
299
|
+
const sb = sandbox();
|
|
300
|
+
mkdirSync(dirname(dirname(sb.state)), { recursive: true });
|
|
301
|
+
writeFileSync(dirname(sb.state), 'a file where the state directory belongs\n');
|
|
302
|
+
const r = drive(sb, ['--nonce', 'n1', 'task.md'], { CODEX_FAKE_PRE: P13 });
|
|
303
|
+
assert.equal(r.status, 72, r.stderr);
|
|
304
|
+
assert.equal(r.stderr.split('\n').filter((line) => line.includes(sb.state)).length, 1, r.stderr);
|
|
305
|
+
assert.equal(receiptOf(sb).outcome, 'transport-failure');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('bwrap: execvp in a failed command — spec:jev-every-run/S12', () => {
|
|
310
|
+
it('exits 72 without touching the state file', () => {
|
|
311
|
+
const sb = sandbox();
|
|
312
|
+
const r = drive(sb, ['--nonce', 'n1', 'task.md'], { CODEX_FAKE_POST: command(BWRAP, 1, 'failed') });
|
|
313
|
+
assert.equal(r.status, 72, r.stderr);
|
|
314
|
+
assert.ok(r.stderr.includes('bwrap: execvp'), r.stderr);
|
|
315
|
+
assert.equal(existsSync(sb.state), false);
|
|
316
|
+
assert.equal(receiptOf(sb).outcome, 'transport-failure');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('the same strings where they never fire — spec:jev-every-run/S13', () => {
|
|
321
|
+
it('inside a successful command\'s output and in the final message: exit 0, a success receipt, no state file', () => {
|
|
322
|
+
const sb = sandbox();
|
|
323
|
+
const r = drive(sb, ['--nonce', 'n1', 'task.md'], { CODEX_FAKE_POST: command(`${P13_TEXT}\n${BWRAP}`, 0, 'completed'),
|
|
324
|
+
CODEX_FAKE_FINAL: `${P13_TEXT} ${BWRAP}` });
|
|
325
|
+
assertProfile(r);
|
|
326
|
+
assert.ok(r.stdout.includes(P13_TEXT));
|
|
327
|
+
assert.equal(receiptOf(sb).outcome, 'success');
|
|
328
|
+
assert.equal(existsSync(sb.state), false);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('the exec banner and the receipt — spec:jev-every-run/S14', () => {
|
|
333
|
+
it('a profile run reads sandbox=jev-profile and the two hosts before source=, the receipt keys unchanged', () => {
|
|
334
|
+
const sb = sandbox();
|
|
335
|
+
const r = drive(sb, ['--nonce', 'n1', 'task.md']);
|
|
336
|
+
assertProfile(r);
|
|
337
|
+
assert.equal(bannerOf(r), banner('jev-profile', HOSTS));
|
|
338
|
+
assert.deepEqual(Object.keys(receiptOf(sb)), RECEIPT_KEYS);
|
|
339
|
+
assert.deepEqual(Object.keys(receiptOf(sb).posture), ['model', 'effort', 'tier']);
|
|
340
|
+
});
|
|
341
|
+
it('a fallback run reads sandbox=workspace-write and jev=unreachable, timeout= last', () => {
|
|
342
|
+
const r = drive(sandbox(), ['--resume', 'sess-1', '-'], { CODEX_FAKE_VERSION: 'codex-cli 0.159.0' });
|
|
343
|
+
assert.equal(bannerOf(r), banner('workspace-write', 'unreachable (codex 0.159.0 below 0.160.0)', 'resume:sess-1'));
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('the execution directive and the resume reminder — spec:jev-every-run/S17', () => {
|
|
348
|
+
const HOST_LINE = 'the hosts api.typesafe.ai and docs.typesafe.ai are reachable; network access to any other host is an escalation: STOP and report';
|
|
349
|
+
it('a fresh profile run names the two hosts and the other-host escalation; a fallback run keeps network access whole', () => {
|
|
350
|
+
const profile = drive(sandbox(), ['-']);
|
|
351
|
+
assertProfile(profile);
|
|
352
|
+
assert.ok(profile.prompt.includes(HOST_LINE), profile.prompt);
|
|
353
|
+
const fallback = drive(sandbox(), ['-'], { CODEX_FAKE_UNAME: 'Darwin' });
|
|
354
|
+
assert.ok(fallback.prompt.includes('escalation (network access, writes outside the repo'), fallback.prompt);
|
|
355
|
+
assert.doesNotMatch(fallback.prompt, /typesafe/);
|
|
356
|
+
});
|
|
357
|
+
it('a resume states its current posture: the hosts on a profile run, network access on the next run of the same session that falls back', () => {
|
|
358
|
+
const sb = sandbox();
|
|
359
|
+
const profile = drive(sb, ['--resume', 'sess-1', '-']);
|
|
360
|
+
assertProfile(profile);
|
|
361
|
+
assert.ok(profile.prompt.includes(HOST_LINE), profile.prompt);
|
|
362
|
+
const fallback = drive(sb, ['--resume', 'sess-1', '-'], { CODEX_FAKE_UNAME: 'Darwin' });
|
|
363
|
+
assertFallback(fallback);
|
|
364
|
+
assert.match(fallback.prompt, /network access/);
|
|
365
|
+
assert.doesNotMatch(fallback.prompt, /typesafe/);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('the probe passthrough under the profile — spec:jev-every-run/S18', () => {
|
|
370
|
+
for (const flag of [['--add-dir', '/x'], ['-C', '/x'], ['--cd', '/x'], ['--enable', 'foo'], ['--disable', 'foo']]) {
|
|
371
|
+
it(`CODEX_PROBE=1 with ${flag[0]}: refused pre-spend under the profile, admitted under the fallback`, () => {
|
|
372
|
+
const refused = drive(sandbox(), ['-', '--', ...flag], { CODEX_PROBE: '1' });
|
|
373
|
+
assert.equal(refused.status, 2, refused.stderr);
|
|
374
|
+
assert.equal(spawnsOf(refused).some(([kind]) => kind === 'run'), false, 'no run is spent');
|
|
375
|
+
const admitted = drive(sandbox(), ['-', '--', ...flag], { CODEX_PROBE: '1', CODEX_FAKE_UNAME: 'Darwin' });
|
|
376
|
+
assertFallback(admitted);
|
|
377
|
+
assert.ok(admitted.argv.includes(flag[0]), admitted.argv.join(' '));
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
it('exactly these five: --ignore-rules stays admitted under the profile', () => {
|
|
381
|
+
const r = drive(sandbox(), ['-', '--', '--ignore-rules'], { CODEX_PROBE: '1' });
|
|
382
|
+
assertProfile(r);
|
|
383
|
+
assert.ok(r.argv.includes('--ignore-rules'));
|
|
384
|
+
});
|
|
385
|
+
});
|