@sabaiway/agent-workflow-kit 14.22.0 → 14.24.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 +74 -0
- package/README.md +1 -1
- 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 +5 -4
- package/references/modes/velocity.md +1 -1
- package/tools/ack-write.mjs +1 -1
- package/tools/bridge-sandbox-recipe.mjs +40 -0
- package/tools/detect-backends.mjs +3 -3
- package/tools/manifest/schema.md +9 -9
- package/tools/manifest/validate.mjs +5 -5
- package/tools/recommendations.mjs +17 -46
- package/tools/velocity-profile.mjs +64 -14
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, it, after } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync, cpSync, readdirSync, symlinkSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
4
|
import { join, dirname, resolve } from 'node:path';
|
|
6
5
|
import { fileURLToPath } from 'node:url';
|
|
7
6
|
import { spawnSync } from 'node:child_process';
|
|
@@ -17,6 +16,7 @@ const FAKE_CODEX = [
|
|
|
17
16
|
'#!/usr/bin/env bash',
|
|
18
17
|
'set -u',
|
|
19
18
|
'if [[ "${1:-}" == "login" ]]; then echo "Logged in using ChatGPT"; exit 0; fi',
|
|
19
|
+
'if [[ "${1:-}" == "--version" ]]; then echo "codex-cli ${CODEX_FAKE_VERSION:-0.160.0}"; exit 0; fi',
|
|
20
20
|
'if [[ "${1:-}" == "debug" ]]; then',
|
|
21
21
|
' echo "catalog argv=[$*] home=${CODEX_HOME:-} keys=${OPENAI_API_KEY:-unset},${FOO_API_KEY:-unset},${OPENAI_BASE_URL:-unset}" >>"$CODEX_FAKE_LOG"',
|
|
22
22
|
' if [[ -n "${CODEX_FAKE_CATALOG_SLEEP:-}" ]]; then sleep "$CODEX_FAKE_CATALOG_SLEEP"; fi',
|
|
@@ -35,6 +35,7 @@ const FAKE_CODEX = [
|
|
|
35
35
|
'',
|
|
36
36
|
].join('\n');
|
|
37
37
|
|
|
38
|
+
const FAKE_UNAME = `#!/usr/bin/env bash\nif [[ $# -eq 0 || "$1" == "-s" ]]; then echo Linux; exit 0; fi\nexec "${spawnSync('bash', ['-c', 'type -P uname'], { encoding: 'utf8' }).stdout.trim()}" "$@"\n`;
|
|
38
39
|
const FAKE_TIMEOUT = [
|
|
39
40
|
'#!/usr/bin/env bash',
|
|
40
41
|
'if [[ " $* " == *" debug models "* ]]; then',
|
|
@@ -52,7 +53,10 @@ const ALL = ['low', 'medium', 'high', 'xhigh'];
|
|
|
52
53
|
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
54
|
const HOST_SETTING = 'CODEX_MODEL=gpt-6-luna\nCODEX_EFFORT=medium\n';
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
// The repositories sit outside every temp root (spec jev-every-run): under node_modules, TMPDIR their sibling.
|
|
57
|
+
const FIXTURES = join(HERE, '..', 'node_modules', '.aw-fixtures');
|
|
58
|
+
for (const dir of ['repos', 'tmp']) mkdirSync(join(FIXTURES, dir), { recursive: true });
|
|
59
|
+
const SHARED = mkdtempSync(join(FIXTURES, 'repos', 'codex-posture-'));
|
|
56
60
|
after(() => rmSync(SHARED, { recursive: true, force: true }));
|
|
57
61
|
const TEMPLATE = (() => {
|
|
58
62
|
const home = join(SHARED, 'template');
|
|
@@ -60,6 +64,7 @@ const TEMPLATE = (() => {
|
|
|
60
64
|
mkdirSync(join(home, 'bin'), { recursive: true });
|
|
61
65
|
mkdirSync(repo);
|
|
62
66
|
writeFileSync(join(home, 'bin', 'codex'), FAKE_CODEX, { mode: 0o755 });
|
|
67
|
+
writeFileSync(join(home, 'bin', 'uname'), FAKE_UNAME, { mode: 0o755 });
|
|
63
68
|
const git = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
|
|
64
69
|
git('init', '-q');
|
|
65
70
|
git('config', 'user.email', 'probe@example.com');
|
|
@@ -94,7 +99,7 @@ const drive = (sb, [wrapper, args], env = {}) => {
|
|
|
94
99
|
const r = spawnSync('bash', [wrapper, ...args], {
|
|
95
100
|
cwd: sb.repo, input: 'do the thing', encoding: 'utf8', timeout: 60000,
|
|
96
101
|
env: {
|
|
97
|
-
PATH: `${sb.bin}:${process.env.PATH}`, HOME: sb.home, TMPDIR:
|
|
102
|
+
PATH: `${sb.bin}:${process.env.PATH}`, HOME: sb.home, TMPDIR: join(FIXTURES, 'tmp'),
|
|
98
103
|
CODEX_FAKE_LOG: sb.log, CODEX_FAKE_CATALOG: sb.catalog, CODEX_FAKE_USER_CATALOG: sb.userCatalog, ...env,
|
|
99
104
|
},
|
|
100
105
|
});
|
|
@@ -136,12 +141,16 @@ describe('a fresh host runs the built-in default — spec:bridge-model/S1', () =
|
|
|
136
141
|
it('codex-exec and codex-review run gpt-6.1-sol at effort high and the banner names source default', () => {
|
|
137
142
|
const exec = drive(sandbox(), FRESH_EXEC);
|
|
138
143
|
assertRan(exec, 'gpt-6.1-sol', 'high');
|
|
139
|
-
assert.equal(bannerOf(exec.stderr),
|
|
140
|
-
'
|
|
144
|
+
assert.equal(bannerOf(exec.stderr), 'exec posture: model=gpt-6.1-sol effort=high tier=standard sandbox=jev-profile session=fresh '
|
|
145
|
+
+ 'jev=api.typesafe.ai,docs.typesafe.ai source=model:default,effort:default timeout=3600s');
|
|
141
146
|
const review = drive(sandbox(), PLAN_REVIEW);
|
|
142
147
|
assertRan(review, 'gpt-6.1-sol', 'high');
|
|
143
|
-
assert.equal(bannerOf(review.stderr),
|
|
144
|
-
|
|
148
|
+
assert.equal(bannerOf(review.stderr), 'review posture: model=gpt-6.1-sol effort=high tier=standard jev=api.typesafe.ai,docs.typesafe.ai source=model:default,effort:default timeout=1800s');
|
|
149
|
+
});
|
|
150
|
+
it('a codex-exec fallback run keeps the same model fields, sandbox=workspace-write and jev=unreachable before source=', () => {
|
|
151
|
+
const exec = drive(sandbox(), FRESH_EXEC, { CODEX_FAKE_VERSION: '0.159.0' });
|
|
152
|
+
assertRan(exec, 'gpt-6.1-sol', 'high');
|
|
153
|
+
assert.equal(bannerOf(exec.stderr), 'exec posture: model=gpt-6.1-sol effort=high tier=standard sandbox=workspace-write session=fresh jev=unreachable (codex 0.159.0 below 0.160.0) source=model:default,effort:default timeout=3600s');
|
|
145
154
|
});
|
|
146
155
|
});
|
|
147
156
|
|
|
@@ -20,6 +20,7 @@ const FAKE_CODEX = [
|
|
|
20
20
|
'#!/usr/bin/env bash',
|
|
21
21
|
'set -u',
|
|
22
22
|
'if [[ "${1:-}" == "login" ]]; then echo "Logged in using ChatGPT"; exit 0; fi',
|
|
23
|
+
'if [[ "${1:-}" == "--version" ]]; then echo "codex-cli 0.160.0"; exit 0; fi',
|
|
23
24
|
'printf invoked > "${CODEX_FAKE_SENTINEL:-/dev/null}"',
|
|
24
25
|
'cat >/dev/null',
|
|
25
26
|
'out=""; prev=""',
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// codex-review-jev.test.mjs — spec jev-every-run on codex-review: the key rule, the review profile (no write entry),
|
|
2
|
+
// the fallback, the loud failure on each attempt's own trace, the banner, the fence home's skill link and the
|
|
3
|
+
// directives. 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 } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
|
|
11
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const REVIEW = join(HERE, 'codex-review.sh');
|
|
13
|
+
const HOSTS = 'api.typesafe.ai,docs.typesafe.ai';
|
|
14
|
+
// The same layout as codex-exec-jev: repositories under the package's node_modules, TMPDIR their sibling.
|
|
15
|
+
const FIXTURES = join(HERE, '..', 'node_modules', '.aw-fixtures');
|
|
16
|
+
const TMP = join(FIXTURES, 'tmp');
|
|
17
|
+
mkdirSync(TMP, { recursive: true });
|
|
18
|
+
mkdirSync(join(FIXTURES, 'repos'), { recursive: true });
|
|
19
|
+
const ROOT = mkdtempSync(join(FIXTURES, 'repos', 'review-jev-'));
|
|
20
|
+
after(() => rmSync(ROOT, { recursive: true, force: true }));
|
|
21
|
+
const REAL_UNAME = spawnSync('bash', ['-c', 'type -P uname'], { encoding: 'utf8' }).stdout.trim();
|
|
22
|
+
|
|
23
|
+
// Every spawn appends its kind and key states; a run also records argv, stdin and what $HOME/.agents/skills is.
|
|
24
|
+
const FAKE_CODEX = [
|
|
25
|
+
'#!/usr/bin/env bash',
|
|
26
|
+
'set -u',
|
|
27
|
+
'st() { if [[ -n "${!1+x}" ]]; then printf "[%s]" "${!1}"; else printf unset; fi; }',
|
|
28
|
+
'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"; }',
|
|
29
|
+
'case "${1:-}" in',
|
|
30
|
+
' login) rec login; echo "Logged in using ChatGPT"; exit 0 ;;',
|
|
31
|
+
' --version) rec version; echo "${CODEX_FAKE_VERSION-codex-cli 0.160.0}"; exit 0 ;;',
|
|
32
|
+
' debug) rec catalog; cat <<EOF',
|
|
33
|
+
'{"models":[{"slug":"gpt-6.1-sol","priority":1,"visibility":"list","default_reasoning_level":"low","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"}]}]}',
|
|
34
|
+
'EOF',
|
|
35
|
+
' exit 0 ;;',
|
|
36
|
+
'esac',
|
|
37
|
+
'rec run',
|
|
38
|
+
'{ for a in "$@"; do echo "$a"; done; } >"$CODEX_FAKE_ARGV"',
|
|
39
|
+
'cat >"$CODEX_FAKE_STDIN"',
|
|
40
|
+
'if [[ -L "$HOME/.agents/skills" ]]; then echo "link $(readlink -f "$HOME/.agents/skills")"; elif [[ -e "$HOME/.agents/skills" ]]; then echo present; else echo absent; fi >"$CODEX_FAKE_SKILLS"',
|
|
41
|
+
'out=""; prev=""; schema=""',
|
|
42
|
+
'for a in "$@"; do if [[ "$prev" == "-o" ]]; then out="$a"; fi; if [[ "$a" == "--output-schema" ]]; then schema=1; fi; prev="$a"; done',
|
|
43
|
+
'if [[ -n "$out" ]]; then printf "%s\\n" "${CODEX_FAKE_FINAL:-FAKE_REVIEW}" "Verdict: ship" >"$out"; fi',
|
|
44
|
+
'echo "{\\"type\\":\\"thread.started\\",\\"thread_id\\":\\"sess-review\\"}"',
|
|
45
|
+
'if [[ -n "$schema" ]]; then pre="${CODEX_FAKE_PRE_SCHEMA:-}"; else pre="${CODEX_FAKE_PRE_PLAIN:-}"; fi',
|
|
46
|
+
'if [[ -n "${CODEX_FAKE_PRE:-}$pre" ]]; then echo "${CODEX_FAKE_PRE:-}$pre"; fi',
|
|
47
|
+
'echo "{\\"type\\":\\"turn.started\\"}"',
|
|
48
|
+
'if [[ -n "${CODEX_FAKE_POST:-}" ]]; then echo "$CODEX_FAKE_POST"; fi',
|
|
49
|
+
'echo "{\\"type\\":\\"turn.completed\\",\\"usage\\":{}}"',
|
|
50
|
+
'if [[ -n "$schema" && -n "${CODEX_FAKE_FAIL_ON_SCHEMA:-}" ]]; then exit 1; fi',
|
|
51
|
+
'exit 0',
|
|
52
|
+
'',
|
|
53
|
+
].join('\n');
|
|
54
|
+
const FAKE_UNAME = ['#!/usr/bin/env bash', 'if [[ $# -eq 0 || "$1" == "-s" ]]; then echo "${CODEX_FAKE_UNAME:-Linux}"; exit 0; fi',
|
|
55
|
+
`exec "${REAL_UNAME}" "$@"`, ''].join('\n');
|
|
56
|
+
|
|
57
|
+
const TEMPLATE = join(ROOT, 'template');
|
|
58
|
+
mkdirSync(join(TEMPLATE, 'bin'), { recursive: true });
|
|
59
|
+
mkdirSync(join(TEMPLATE, 'home'));
|
|
60
|
+
mkdirSync(join(TEMPLATE, 'repo'));
|
|
61
|
+
writeFileSync(join(TEMPLATE, 'bin', 'codex'), FAKE_CODEX);
|
|
62
|
+
writeFileSync(join(TEMPLATE, 'bin', 'uname'), FAKE_UNAME);
|
|
63
|
+
{
|
|
64
|
+
const git = (...args) => spawnSync('git', args, { cwd: join(TEMPLATE, 'repo'), encoding: 'utf8' });
|
|
65
|
+
git('init', '-q');
|
|
66
|
+
git('config', 'user.email', 'probe@example.com');
|
|
67
|
+
git('config', 'user.name', 'probe');
|
|
68
|
+
writeFileSync(join(TEMPLATE, 'repo', 'AGENTS.md'), '# AGENTS\n\nHard Constraints: none (test fixture).\n');
|
|
69
|
+
writeFileSync(join(TEMPLATE, 'repo', 'plan.md'), '# Plan\n\nDo a thing in two steps.\n');
|
|
70
|
+
git('add', '-A');
|
|
71
|
+
git('commit', '-qm', 'base');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let sequence = 0;
|
|
75
|
+
const sandbox = ({ skills = true } = {}) => {
|
|
76
|
+
const dir = join(ROOT, `sb-${++sequence}`);
|
|
77
|
+
cpSync(TEMPLATE, dir, { recursive: true });
|
|
78
|
+
for (const name of ['codex', 'uname']) chmodSync(join(dir, 'bin', name), 0o755);
|
|
79
|
+
const sb = { dir, bin: join(dir, 'bin'), home: join(dir, 'home'), repo: join(dir, 'repo'),
|
|
80
|
+
state: join(dir, 'home', '.local', 'state', 'agent-workflow', 'codex-jev-profile-refused') };
|
|
81
|
+
if (skills) {
|
|
82
|
+
mkdirSync(join(sb.home, '.agents', 'skills', 'typesafe-ai'), { recursive: true });
|
|
83
|
+
writeFileSync(join(sb.home, '.agents', 'skills', 'typesafe-ai', 'SKILL.md'), '# the vendor skill\n');
|
|
84
|
+
}
|
|
85
|
+
writeFileSync(join(sb.repo, 'pending.txt'), 'an uncommitted change to review\n');
|
|
86
|
+
return sb;
|
|
87
|
+
};
|
|
88
|
+
const PLAN = ['plan', 'plan.md'];
|
|
89
|
+
const drive = (sb, args, env = {}, cwd = sb.repo) => {
|
|
90
|
+
const cap = (name) => join(sb.dir, name);
|
|
91
|
+
for (const name of ['log', 'argv', 'stdin', 'skills']) rmSync(cap(name), { force: true });
|
|
92
|
+
const r = spawnSync('bash', [REVIEW, ...args], {
|
|
93
|
+
cwd, input: '', encoding: 'utf8', timeout: 60000,
|
|
94
|
+
env: { PATH: `${sb.bin}:${process.env.PATH}`, HOME: sb.home, TMPDIR: TMP, CODEX_FAKE_LOG: cap('log'), CODEX_FAKE_ARGV: cap('argv'),
|
|
95
|
+
CODEX_FAKE_STDIN: cap('stdin'), CODEX_FAKE_SKILLS: cap('skills'), ...env },
|
|
96
|
+
});
|
|
97
|
+
const read = (name) => (existsSync(cap(name)) ? readFileSync(cap(name), 'utf8') : '');
|
|
98
|
+
const spawns = read('log').split('\n').filter(Boolean).map((line) => [line.slice(0, line.indexOf(' ')), line.slice(line.indexOf(' ') + 1)]);
|
|
99
|
+
return { status: r.status, stdout: r.stdout, stderr: r.stderr, spawns, runs: spawns.filter(([kind]) => kind === 'run').length,
|
|
100
|
+
argv: read('argv').split('\n').slice(0, -1), prompt: read('stdin').replace(/\s+/g, ' '), skills: read('skills').trim() };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const overrides = (argv) => Object.fromEntries(argv.flatMap((arg, i) => (argv[i - 1] === '-c'
|
|
104
|
+
? [[arg.slice(0, arg.indexOf('=')), arg.slice(arg.indexOf('=') + 1)]] : [])));
|
|
105
|
+
// A reader for the TOML inline tables a profile override carries.
|
|
106
|
+
const inline = (text) => {
|
|
107
|
+
let at = 0;
|
|
108
|
+
const eat = (re) => {
|
|
109
|
+
const m = re.exec(text.slice(at));
|
|
110
|
+
assert.ok(m, `an inline table: ${text}`);
|
|
111
|
+
at += m[0].length;
|
|
112
|
+
return m;
|
|
113
|
+
};
|
|
114
|
+
const value = () => {
|
|
115
|
+
if (eat(/^\s*(\{?)/)[1] === '') return eat(/^"([^"]*)"|^([\w.:-]+)/).slice(1).find((part) => part !== undefined);
|
|
116
|
+
const table = {};
|
|
117
|
+
while (eat(/^\s*(\}?)/)[1] === '') {
|
|
118
|
+
const key = value();
|
|
119
|
+
eat(/^\s*=/);
|
|
120
|
+
table[key] = value();
|
|
121
|
+
eat(/^\s*,?/);
|
|
122
|
+
}
|
|
123
|
+
return table;
|
|
124
|
+
};
|
|
125
|
+
const parsed = value();
|
|
126
|
+
assert.equal(text.slice(at).trim(), '', `nothing after the inline table: ${text}`);
|
|
127
|
+
return parsed;
|
|
128
|
+
};
|
|
129
|
+
const PROFILE_KEYS = ['default_permissions', 'permissions.jev.filesystem', 'permissions.jev.network.domains', 'permissions.jev.network.enabled'];
|
|
130
|
+
const profileKeys = (c) => Object.keys(c).filter((key) => key === 'default_permissions' || key.startsWith('permissions.')).sort();
|
|
131
|
+
const assertProfile = (r) => {
|
|
132
|
+
assert.equal(r.status, 0, r.stderr);
|
|
133
|
+
const c = overrides(r.argv);
|
|
134
|
+
assert.deepEqual(profileKeys(c), PROFILE_KEYS, r.argv.join(' '));
|
|
135
|
+
assert.equal(c.default_permissions.replace(/^"(.*)"$/, '$1'), 'jev');
|
|
136
|
+
assert.equal(c['permissions.jev.network.enabled'], 'true');
|
|
137
|
+
assert.deepEqual(inline(c['permissions.jev.network.domains']), { 'api.typesafe.ai': 'allow', 'docs.typesafe.ai': 'allow' });
|
|
138
|
+
assert.deepEqual(inline(c['permissions.jev.filesystem']), { ':root': 'read' }, 'the review profile carries no write entry');
|
|
139
|
+
assert.equal(r.argv.includes('--sandbox') || 'sandbox_mode' in c, false, 'no --sandbox read-only beside the profile');
|
|
140
|
+
};
|
|
141
|
+
const assertFallback = (r) => {
|
|
142
|
+
assert.equal(r.status, 0, r.stderr);
|
|
143
|
+
assert.deepEqual(profileKeys(overrides(r.argv)), [], r.argv.join(' '));
|
|
144
|
+
assert.equal(r.argv[r.argv.indexOf('--sandbox') + 1], 'read-only', r.argv.join(' '));
|
|
145
|
+
};
|
|
146
|
+
const bannerOf = (r) => r.stderr.split('\n').find((line) => line.startsWith('review posture: ')) ?? '';
|
|
147
|
+
const banner = (jev) => `review posture: model=gpt-6.1-sol effort=high tier=standard jev=${jev} source=model:default,effort:default timeout=1800s`;
|
|
148
|
+
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' };
|
|
149
|
+
const PASSED = 'key=[jev-key] openai=unset codex=unset foo=unset base=unset';
|
|
150
|
+
const runEnv = (r) => (r.spawns.find(([kind]) => kind === 'run') ?? [])[1];
|
|
151
|
+
const item = (body) => JSON.stringify({ type: 'item.completed', item: body });
|
|
152
|
+
const P13_TEXT = 'Configured filesystem path `:root` is not recognized by this version of Codex and will be ignored. Upgrade Codex if this path is required.';
|
|
153
|
+
const P13 = item({ id: 'item_0', type: 'error', message: P13_TEXT });
|
|
154
|
+
const command = (output, exitCode, status) => item({ id: 'item_1', type: 'command_execution', command: 'bash -lc ls', aggregated_output: output, exit_code: exitCode, status });
|
|
155
|
+
const BWRAP = 'bwrap: execvp /usr/lib/node_modules/@openai/codex/vendor/x86_64-unknown-linux-musl/bin/codex: No such file or directory\n';
|
|
156
|
+
const receiptsOf = (sb) => {
|
|
157
|
+
const file = join(sb.repo, '.git', 'agent-workflow-review-receipts.jsonl');
|
|
158
|
+
return existsSync(file) ? readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line)) : [];
|
|
159
|
+
};
|
|
160
|
+
const RECEIPT_KEYS = ['schema', 'artifact', 'fresh', 'fingerprint', 'backend', 'verdict', 'grounded', 'factsHash', 'wrapperVersion', 'timestamp',
|
|
161
|
+
'probe', 'durationS', 'blocking', 'artifactPath', 'posture'];
|
|
162
|
+
const stateLines = (sb) => (existsSync(sb.state) ? readFileSync(sb.state, 'utf8').split('\n').filter(Boolean) : []);
|
|
163
|
+
|
|
164
|
+
describe('the key rule on codex-review — spec:jev-every-run/S3', () => {
|
|
165
|
+
it('passes TYPESAFE_API_KEY and unsets every other key on the login, version, catalog and run spawns', () => {
|
|
166
|
+
const r = drive(sandbox(), PLAN, KEYS);
|
|
167
|
+
assert.equal(r.status, 0, r.stderr);
|
|
168
|
+
assert.deepEqual([...new Set(r.spawns.map(([kind]) => kind))].sort(), ['catalog', 'login', 'run', 'version']);
|
|
169
|
+
for (const [kind, env] of r.spawns) assert.equal(env, PASSED, `${kind} spawn`);
|
|
170
|
+
});
|
|
171
|
+
it('the review half of S5: an empty key reaches codex empty, an unset one stays unset, the argv the same as with the key set', () => {
|
|
172
|
+
const runs = ['jev-key', '', undefined].map((value) => drive(sandbox(), PLAN, { TYPESAFE_API_KEY: value }));
|
|
173
|
+
assert.deepEqual(runs.map((r) => runEnv(r)?.split(' ')[0]), ['key=[jev-key]', 'key=[]', 'key=unset']);
|
|
174
|
+
const normalized = runs.map((r) => r.argv.map((arg, i) => (r.argv[i - 1] === '-o' ? '<out>' : arg)).join(' '));
|
|
175
|
+
assert.deepEqual([normalized[1], normalized[2]], [normalized[0], normalized[0]]);
|
|
176
|
+
assertProfile(runs[0]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('the review profile on Linux from codex-cli 0.160.0 — spec:jev-every-run/S8', () => {
|
|
181
|
+
for (const args of [PLAN, ['code']]) {
|
|
182
|
+
it(`codex-review ${args[0]}: :root read and no write entry, the two hosts, no --sandbox read-only`, () => {
|
|
183
|
+
assertProfile(drive(sandbox(), args));
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('the review half of S9 and S10: the fallback, and the temp-root rule that is codex-exec\'s alone', () => {
|
|
189
|
+
for (const [label, env, reason] of [['a uname other than Linux', { CODEX_FAKE_UNAME: 'Darwin' }, 'not linux (Darwin)'],
|
|
190
|
+
['codex-cli 0.159.0', { CODEX_FAKE_VERSION: 'codex-cli 0.159.0' }, 'codex 0.159.0 below 0.160.0']]) {
|
|
191
|
+
it(`${label}: --sandbox read-only with the key passed, the reason named`, () => {
|
|
192
|
+
const r = drive(sandbox(), PLAN, { ...KEYS, ...env });
|
|
193
|
+
assertFallback(r);
|
|
194
|
+
assert.equal(runEnv(r), PASSED);
|
|
195
|
+
assert.equal(bannerOf(r), banner(`unreachable (${reason})`));
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
it('a review from a repository under TMPDIR still runs the profile', () => {
|
|
199
|
+
const sb = sandbox();
|
|
200
|
+
const root = join(sb.dir, 'tmproot');
|
|
201
|
+
cpSync(sb.repo, join(root, 'repo'), { recursive: true });
|
|
202
|
+
assertProfile(drive(sb, PLAN, { TMPDIR: root }, join(root, 'repo')));
|
|
203
|
+
});
|
|
204
|
+
it('its own refused entry falls back naming the file; another digest of the same version runs the profile', () => {
|
|
205
|
+
const sb = sandbox();
|
|
206
|
+
assert.equal(drive(sb, PLAN, { CODEX_FAKE_PRE: P13 }).status, 72);
|
|
207
|
+
const refused = drive(sb, PLAN);
|
|
208
|
+
assertFallback(refused);
|
|
209
|
+
assert.ok(bannerOf(refused).includes('jev=unreachable (profile not recognized by codex 0.160.0'), bannerOf(refused));
|
|
210
|
+
assert.ok(bannerOf(refused).includes(sb.state), 'the banner reason names the file');
|
|
211
|
+
writeFileSync(sb.state, `0.160.0 ${'a'.repeat(64)}\n`);
|
|
212
|
+
assertProfile(drive(sb, PLAN));
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('the review half of S11-S13: the loud failure on each attempt\'s own trace', () => {
|
|
217
|
+
it('a P13 item before turn.started exits 72 with no receipt and appends one entry', () => {
|
|
218
|
+
const sb = sandbox();
|
|
219
|
+
const r = drive(sb, PLAN, { CODEX_FAKE_PRE: `${P13}\n${P13}` });
|
|
220
|
+
assert.equal(r.status, 72, r.stderr);
|
|
221
|
+
assert.ok(r.stderr.includes(P13_TEXT));
|
|
222
|
+
assert.doesNotMatch(r.stderr, /update codex/i);
|
|
223
|
+
assert.deepEqual(receiptsOf(sb), []);
|
|
224
|
+
assert.equal(stateLines(sb).length, 1);
|
|
225
|
+
assert.match(stateLines(sb)[0], /^0\.160\.0 [0-9a-f]{64}$/);
|
|
226
|
+
});
|
|
227
|
+
it('bwrap: execvp in a failed command exits 72 with no receipt and no state file', () => {
|
|
228
|
+
const sb = sandbox();
|
|
229
|
+
const r = drive(sb, PLAN, { CODEX_FAKE_POST: command(BWRAP, 1, 'failed') });
|
|
230
|
+
assert.equal(r.status, 72, r.stderr);
|
|
231
|
+
assert.deepEqual(receiptsOf(sb), []);
|
|
232
|
+
assert.equal(existsSync(sb.state), false);
|
|
233
|
+
});
|
|
234
|
+
it('the same strings in a successful command and in the final message leave the review and its receipt as today', () => {
|
|
235
|
+
const sb = sandbox();
|
|
236
|
+
const r = drive(sb, PLAN, { CODEX_FAKE_POST: command(`${P13_TEXT}\n${BWRAP}`, 0, 'completed'), CODEX_FAKE_FINAL: `${P13_TEXT} ${BWRAP}` });
|
|
237
|
+
assertProfile(r);
|
|
238
|
+
assert.equal(receiptsOf(sb).length, 1);
|
|
239
|
+
assert.equal(existsSync(sb.state), false);
|
|
240
|
+
});
|
|
241
|
+
it('a schema attempt that carries the item exits 72 before the retry', () => {
|
|
242
|
+
const r = drive(sandbox(), PLAN, { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FAIL_ON_SCHEMA: '1', CODEX_FAKE_PRE_SCHEMA: P13 });
|
|
243
|
+
assert.equal(r.status, 72, r.stderr);
|
|
244
|
+
assert.equal(r.runs, 1, 'no retry is spent');
|
|
245
|
+
});
|
|
246
|
+
it('a retry that carries the item is scanned on its own trace and exits 72', () => {
|
|
247
|
+
const r = drive(sandbox(), PLAN, { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FAIL_ON_SCHEMA: '1', CODEX_FAKE_PRE_PLAIN: P13 });
|
|
248
|
+
assert.equal(r.status, 72, r.stderr);
|
|
249
|
+
assert.equal(r.runs, 2);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('the review half of S14: the banner and the receipt', () => {
|
|
254
|
+
it('a profile run names the two hosts before source=, timeout= last; the receipt keys unchanged', () => {
|
|
255
|
+
const sb = sandbox();
|
|
256
|
+
const r = drive(sb, PLAN);
|
|
257
|
+
assertProfile(r);
|
|
258
|
+
assert.equal(bannerOf(r), banner(HOSTS));
|
|
259
|
+
assert.deepEqual(Object.keys(receiptsOf(sb)[0]), RECEIPT_KEYS);
|
|
260
|
+
assert.deepEqual(Object.keys(receiptsOf(sb)[0].posture), ['model', 'effort', 'tier']);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('the fence home links the real Codex skill root — spec:jev-every-run/S15', () => {
|
|
265
|
+
for (const [label, env, assertPosture] of [['a profile run', {}, assertProfile], ['a fallback run', { CODEX_FAKE_UNAME: 'Darwin' }, assertFallback]]) {
|
|
266
|
+
it(`${label}: .agents/skills links to the real root when it exists, no link when it does not, and the review runs`, () => {
|
|
267
|
+
const sb = sandbox();
|
|
268
|
+
const linked = drive(sb, PLAN, env);
|
|
269
|
+
assertPosture(linked);
|
|
270
|
+
assert.equal(linked.skills, `link ${realpathSync(join(sb.home, '.agents', 'skills'))}`);
|
|
271
|
+
const bare = drive(sandbox({ skills: false }), PLAN, env);
|
|
272
|
+
assertPosture(bare);
|
|
273
|
+
assert.equal(bare.skills, 'absent');
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('the three fence directives — spec:jev-every-run/S16', () => {
|
|
279
|
+
const NO_WRITE = 'do NOT edit, create, or delete any file';
|
|
280
|
+
it('each directive admits the linked skill and names the two hosts on a profile run, and still forbids every write', () => {
|
|
281
|
+
for (const [args, env] of [[PLAN, {}], [['code'], {}], [['code'], { CODEX_REVIEW_MAX_TOTAL_BYTES: '1' }]]) {
|
|
282
|
+
const r = drive(sandbox(), args, env);
|
|
283
|
+
assertProfile(r);
|
|
284
|
+
for (const text of ['.agents/skills', 'api.typesafe.ai', 'docs.typesafe.ai', NO_WRITE]) assert.ok(r.prompt.includes(text), `${args[0]} ${text}`);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
it('the oversized code review keeps its one exception, the precomputed-diff file', () => {
|
|
288
|
+
const r = drive(sandbox(), ['code'], { CODEX_REVIEW_MAX_TOTAL_BYTES: '1' });
|
|
289
|
+
assertProfile(r);
|
|
290
|
+
assert.match(r.prompt, /with ONE exception . the precomputed-diff file at \S*codex-review-diff\.\d+; read it IN FULL/);
|
|
291
|
+
});
|
|
292
|
+
it('a fallback run names no host, and no skill is admitted when no link is made', () => {
|
|
293
|
+
const fallback = drive(sandbox(), PLAN, { CODEX_FAKE_UNAME: 'Darwin' });
|
|
294
|
+
assertFallback(fallback);
|
|
295
|
+
assert.doesNotMatch(fallback.prompt, /typesafe/);
|
|
296
|
+
assert.ok(fallback.prompt.includes('.agents/skills'), 'the link is made on a fallback run too');
|
|
297
|
+
const bare = drive(sandbox({ skills: false }), PLAN);
|
|
298
|
+
assertProfile(bare);
|
|
299
|
+
assert.equal(bare.prompt.includes('.agents/skills'), false);
|
|
300
|
+
});
|
|
301
|
+
});
|