mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 23:14:33 +01:00
* feat(codex): --effort <level> sets GSTACK_CODEX_EFFORT for one run /codex review --effort medium (or --effort=medium) prefixes each run-with-timeout dispatch with GSTACK_CODEX_EFFORT=<level>, which outranks codex_effort and the per-mode default; --xhigh becomes shorthand for --effort xhigh. The probe keeps owning validation. A missing value, a value that is not one lowercase word, or conflicting flags stop instead of falling back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(codex): per-run effort prefix reaches the rendered /codex review dispatch Runs the rendered review fence against the fake codex: no prefix uses codex_effort, GSTACK_CODEX_EFFORT=medium|xhigh wins over it, an unknown level never reaches codex. Step 1 check pins --effort tokens and the stop rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(codex): document codex_effort and --effort; Challenge runs at high, not xhigh Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): [Unreleased] entry for /codex --effort Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(codex): --effort reaches the probe as an allowlisted request level, parsed before the mode and checked before the paid probe The level now travels as run-with-timeout --request-effort '<effort-level>' (placeholder grammar ^[a-z]*$) instead of an agent-built env prefix, and the probe names /codex --effort when it rejects one. Effort flags count only right after /codex or its mode word and are removed before mode detection. Step 0.5 runs check-effort before probe-model, so a bad level makes no Codex call even on a cold cache. Tests cover all five dispatch fences and the zero-call cases. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(codex): per-request --effort in model-policy, CHANGELOG behavior changes, TODOS follow-ups Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: stamp v1.91.93.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(codex): effort flags sit among /codex's leading flags; the probe names an unfilled '<effort-level>' placeholder Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(codex): per-request row in the effort precedence table, in-tool help names /codex --effort, late-flag notice Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(codex): binding tripwire sees the request-effort dispatch; valid and unfilled levels through Step 0.5 and every fence; doc accuracy Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: John Nunemaker <nunemaker@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
26 files changed
+257
-86
No files matched your search
@@ -120,7 +120,9 @@ describe('content-binding template drift', () => {
|
||||
const codex = rendered('codex/sections/review-mode.md');
|
||||
const starts = [...codex.matchAll(/gstack-review-log --start codex-review/g)];
|
||||
expect(starts).toHaveLength(2);
|
||||
expect(starts[0].index).toBeLessThan(codex.indexOf('run-with-timeout --user-invoked --effort high 330 codex review'));
|
||||
const nativeReview = codex.indexOf(`run-with-timeout --user-invoked --effort high --request-effort '<effort-level>' 330 codex review`);
|
||||
expect(nativeReview).toBeGreaterThan(-1);
|
||||
expect(starts[0].index).toBeLessThan(nativeReview);
|
||||
expect(starts[1].index).toBeLessThan(codex.indexOf('git diff "<base>...HEAD"'));
|
||||
expect(codex).toContain('--finish CODEX_REVIEW_START');
|
||||
expect(codex).toContain('"completed":COMPLETED,"converged":CONVERGED');
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as path from 'node:path';
|
||||
import { codexEffortSetting, modelsMain } from '../lib/model-policy-cli';
|
||||
import { outsideVoiceCommand } from '../scripts/resolvers/outside-voice';
|
||||
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
|
||||
import { between, expectMentions, expectTokens } from './helpers/prompt-structure';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-effort-'));
|
||||
@@ -26,19 +27,42 @@ describe('manual /codex modes (#3098)', () => {
|
||||
test('every mode dispatches through run-with-timeout --effort with its own default', () => {
|
||||
for (const [mode, def] of MODES) {
|
||||
const tmpl = read(`codex/sections/${mode}.md.tmpl`);
|
||||
const dispatches = tmpl.split('\n').filter(line => /run-with-timeout --user-invoked --effort \w+ \d+ codex (exec|review)\b/.test(line));
|
||||
const dispatches = tmpl.split('\n').filter(line => /run-with-timeout --user-invoked --effort \w+ (--request-effort '<effort-level>' )?\d+ codex (exec|review)\b/.test(line));
|
||||
expect(dispatches.length, mode).toBeGreaterThan(0);
|
||||
for (const line of dispatches) {
|
||||
expect(line, mode).toContain(`--effort ${def} `);
|
||||
expect(line, mode).toContain(`--effort ${def} --request-effort '<effort-level>' `);
|
||||
expect(line, mode).not.toContain('model_reasoning_effort');
|
||||
}
|
||||
expect(tmpl, mode).not.toContain('check-effort');
|
||||
}
|
||||
});
|
||||
|
||||
test('the skill body documents the precedence and the --xhigh override', () => {
|
||||
test('the skill body documents the precedence and the --effort / --xhigh overrides', () => {
|
||||
const body = read('codex/SKILL.md.tmpl');
|
||||
for (const want of ['GSTACK_CODEX_EFFORT=xhigh', 'codex_effort', 'run-with-timeout --effort <default>']) expect(body).toContain(want);
|
||||
for (const want of ['GSTACK_CODEX_EFFORT', 'codex_effort', 'run-with-timeout --effort <default>', '--request-effort']) expect(body).toContain(want);
|
||||
const flags = between(body, '**Effort flags, read before the mode:**', '```bash');
|
||||
expectTokens(flags, ['--effort <level>', '--effort=<level>', '--xhigh', '--effort xhigh', '<effort-level>'], 'codex effort flags');
|
||||
expectMentions(flags, [['directly after', 'mode word', 'leading flags', '--model', '--role', 'before any prompt text'], ['later', 'prompt text'], ['remove', 'before Step 1'],
|
||||
['missing', 'lowercase word', 'differing levels', 'stop'], ['later', 'level', 'say so']], 'codex effort flags');
|
||||
const step1 = between(body, '## Step 1: Detect mode', '- Review (2A)');
|
||||
expectMentions(step1, [['effort flags removed'], ['--request-effort', 'outranks']], 'codex Step 1');
|
||||
});
|
||||
|
||||
test('effort flags are read and validated before mode detection and before the paid model probe', () => {
|
||||
const body = read('codex/SKILL.md.tmpl');
|
||||
const at = (s: string) => { const i = body.indexOf(s); expect(i, s).toBeGreaterThan(-1); return i; };
|
||||
const check = at(`"$_CODEX_PROBE" check-effort high --request '<effort-level>' >/dev/null || exit 1`);
|
||||
expect(at('**Effort flags, read before the mode:**')).toBeLessThan(check);
|
||||
expect(check).toBeLessThan(at('"$_CODEX_PROBE" probe-model exec'));
|
||||
expect(check).toBeLessThan(at('## Step 1: Detect mode'));
|
||||
expect(body).not.toMatch(/prefix each\s+`run-with-timeout` dispatch with `GSTACK_CODEX_EFFORT/);
|
||||
});
|
||||
|
||||
test('docs/model-policy.md documents the per-request --effort flag in its Reasoning effort section', () => {
|
||||
const doc = between(read('docs/model-policy.md'), '## Reasoning effort', '## Which workflows use the tiers?');
|
||||
expectTokens(doc, ['/codex review --effort medium', '--effort=<level>', '--xhigh', '--request-effort', 'GSTACK_CODEX_EFFORT'], 'model-policy effort');
|
||||
expectMentions(doc, [['outranks', 'exported'], ['directly after', 'mode word', 'leading flags', 'prompt'], ['unknown level', 'before any Codex call']], 'model-policy effort');
|
||||
expect(doc).toContain('| 0 | `/codex ... --effort <level>`');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,6 +121,71 @@ echo '{"type":"item.completed","item":{"type":"agent_message","text":"SCORE: 8"}
|
||||
expect(dispatchedArgs('review', 'banana', undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
const FENCES = (['review-mode', 'challenge-mode', 'consult-mode'] as const).flatMap(mode =>
|
||||
read(`codex/sections/${mode}.md`).split('\n').filter(line => /run-with-timeout --user-invoked --effort \w+ --request-effort '<effort-level>' \d+ codex /.test(line))
|
||||
.map(line => ({ mode, line: line.trim().split(' | tee ')[0] })));
|
||||
|
||||
test('all five rendered /codex dispatches carry the request level to the probe, over env and codex_effort', () => {
|
||||
expect(FENCES.length).toBe(5);
|
||||
const state = path.join(TMP, 'state-codex-request-effort');
|
||||
fs.mkdirSync(state, { recursive: true });
|
||||
fs.writeFileSync(path.join(state, 'config.yaml'), 'codex_effort: low\n');
|
||||
const env: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) if (v !== undefined && !/^(CODEX_|GSTACK_CODEX_|OPENAI_)/.test(k)) env[k] = v;
|
||||
Object.assign(env, { PATH: `${fakeBin}:${process.env.PATH}`, GSTACK_HOME: state, GSTACK_STATE_ROOT: '', CODEX_HOME: TMP, GSTACK_ACTIVE_HOST: 'claude', CLAUDECODE: '1' });
|
||||
const err = path.join(TMP, 'dispatch-err');
|
||||
const run = (line: string, level: string, exported?: string) => {
|
||||
fs.rmSync(capture, { force: true });
|
||||
fs.rmSync(err, { force: true });
|
||||
const script = [`_CODEX_PROBE=${JSON.stringify(path.join(ROOT, 'bin', 'gstack-codex-probe'))}`, '_CODEX_SANDBOX_MODE=read-only', '_CODEX_SEL=gpt-test',
|
||||
`_REPO_ROOT=${JSON.stringify(repo)}`, `PROMPT_FILE=${JSON.stringify(prompt)}`, `_PROMPT_FILE=${JSON.stringify(prompt)}`, '_SID=sid-test',
|
||||
`TMPOUT=${JSON.stringify(path.join(TMP, 'dispatch-out'))}`, `TMPRESP=${JSON.stringify(path.join(TMP, 'dispatch-resp'))}`, `TMPERR=${JSON.stringify(err)}`,
|
||||
line.replace('<base>', 'HEAD').replace(`'<effort-level>'`, `'${level}'`)].join('\n');
|
||||
const r = spawnSync('bash', ['-c', script], { cwd: repo, env: exported ? { ...env, GSTACK_CODEX_EFFORT: exported } : env, encoding: 'utf8', timeout: 60_000 });
|
||||
return { status: r.status, args: fs.existsSync(capture) ? fs.readFileSync(capture, 'utf8').split('\n') : [], stderr: fs.existsSync(err) ? fs.readFileSync(err, 'utf8') : '' };
|
||||
};
|
||||
for (const { mode, line } of FENCES) {
|
||||
expect(run(line, '').args, mode).toContain('model_reasoning_effort="low"');
|
||||
expect(run(line, 'medium').args, mode).toContain('model_reasoning_effort="medium"');
|
||||
expect(run(line, 'xhigh').args, mode).toContain('model_reasoning_effort="xhigh"');
|
||||
expect(run(line, 'medium', 'max').args, mode).toContain('model_reasoning_effort="medium"');
|
||||
const unfilled = run(line, '<effort-level>');
|
||||
expect(unfilled.args, mode).toEqual([]);
|
||||
expect(unfilled.stderr, mode).toContain('placeholder was left unfilled');
|
||||
const bogus = run(line, 'bogus');
|
||||
expect(bogus.args, mode).toEqual([]);
|
||||
expect(bogus.status, mode).toBe(1);
|
||||
expect(bogus.stderr, mode).toContain("CODEX_EFFORT: invalid — 'bogus' from /codex --effort");
|
||||
expect(bogus.stderr, mode).toContain('--effort medium');
|
||||
}
|
||||
});
|
||||
|
||||
test('check-effort --request outranks env and config, and names the flag when the level is invalid', () => {
|
||||
const probe = path.join(ROOT, 'bin', 'gstack-codex-probe');
|
||||
const state = path.join(TMP, 'state-check-effort-request');
|
||||
fs.mkdirSync(state, { recursive: true });
|
||||
fs.writeFileSync(path.join(state, 'config.yaml'), 'codex_effort: low\n');
|
||||
const env: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) if (v !== undefined && !/^(CODEX_|GSTACK_CODEX_|OPENAI_)/.test(k)) env[k] = v;
|
||||
Object.assign(env, { GSTACK_HOME: state, GSTACK_STATE_ROOT: '' });
|
||||
const check = (args: string[], exported?: string) => spawnSync(probe, ['check-effort', ...args], { env: exported ? { ...env, GSTACK_CODEX_EFFORT: exported } : env, encoding: 'utf8', timeout: 30_000 });
|
||||
expect(check(['high', '--request', 'medium'], 'max').stdout).toBe('CODEX_EFFORT: medium\n');
|
||||
expect(check(['high', '--request', '']).stdout).toBe('CODEX_EFFORT: low\n');
|
||||
expect(check(['high', '--request', ''], 'max').stdout).toBe('CODEX_EFFORT: max\n');
|
||||
const bad = check(['high', '--request', 'turbo']);
|
||||
expect(bad.status).toBe(1);
|
||||
expect(bad.stdout).toBe('');
|
||||
expect(bad.stderr).toContain("'turbo' from /codex --effort");
|
||||
expect(bad.stderr).not.toContain('export GSTACK_CODEX_EFFORT');
|
||||
expect(bad.stderr).toContain('docs/model-policy.md#reasoning-effort');
|
||||
const unfilled = check(['high', '--request', '<effort-level>']);
|
||||
expect(unfilled.status).toBe(1);
|
||||
expect(unfilled.stderr).toContain("'<effort-level>' placeholder was left unfilled");
|
||||
expect(unfilled.stderr).not.toContain('from /codex --effort');
|
||||
expect(check(['high', '--request']).status).toBe(64);
|
||||
expect(check(['high', '--bogus', 'low']).status).toBe(64);
|
||||
});
|
||||
|
||||
test('/spec keeps medium effort and its 120 s budget whatever env or config say', () => {
|
||||
for (const [env, stored] of [[undefined, undefined], ['max', undefined], [undefined, 'xhigh'], ['ultra', 'low']] as const) {
|
||||
expect(dispatchedArgs('spec', env, stored)).toContain('model_reasoning_effort="medium"');
|
||||
|
||||
@@ -645,7 +645,7 @@ describe('codex timeout wrapper: /review + /ship diff passes', () => {
|
||||
|
||||
test(`${relPath}: both diff-review Codex calls run under the wrapper`, () => {
|
||||
const wrapped =
|
||||
read().match(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?\d+\s+codex\s+(exec|review)\b/g) ?? [];
|
||||
read().match(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?\d+\s+codex\s+(exec|review)\b/g) ?? [];
|
||||
// Adversarial pass + structured review pass.
|
||||
expect(wrapped.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
@@ -658,7 +658,7 @@ describe('codex timeout wrapper: /review + /ship diff passes', () => {
|
||||
});
|
||||
|
||||
test(`${relPath}: wrapper budget stays under the outer Bash gate`, () => {
|
||||
const budgets = [...read().matchAll(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?(\d+)\s+codex\b/g)].map(
|
||||
const budgets = [...read().matchAll(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?(\d+)\s+codex\b/g)].map(
|
||||
(m) => Number(m[1]) * 1000,
|
||||
);
|
||||
expect(budgets.length).toBeGreaterThan(0);
|
||||
@@ -710,7 +710,7 @@ describe('codex skeleton+sections union: review sandbox + fail-closed gate + tim
|
||||
test(`${relPath}: (a) every scoped codex review invocation pins sandbox_mode from the selected sandbox`, () => {
|
||||
const invocations = read()
|
||||
.split('\n')
|
||||
.filter((l) => /run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?\d+\s+codex\s+review\b/.test(l));
|
||||
.filter((l) => /run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?\d+\s+codex\s+review\b/.test(l));
|
||||
expect(invocations.length).toBeGreaterThanOrEqual(1);
|
||||
for (const line of invocations) {
|
||||
// select-model reports read-only (full access only for
|
||||
@@ -772,7 +772,7 @@ describe('codex skeleton+sections union: review sandbox + fail-closed gate + tim
|
||||
const inspected: string[] = [];
|
||||
for (const section of sections) {
|
||||
const gates = [...section.matchAll(/timeout:\s*(\d{4,})/g)].map((m) => Number(m[1]));
|
||||
const wrappers = [...section.matchAll(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?(\d+)\s+codex\b/g)].map(
|
||||
const wrappers = [...section.matchAll(/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?(\d+)\s+codex\b/g)].map(
|
||||
(m) => Number(m[1]) * 1000,
|
||||
);
|
||||
if (gates.length === 0 || wrappers.length === 0) continue;
|
||||
|
||||
@@ -17,7 +17,7 @@ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-sites-'));
|
||||
afterAll(() => fs.rmSync(TMP, { recursive: true, force: true }));
|
||||
|
||||
/** `codex exec|review` at the start of a shell command, optionally behind the probe's run-with-timeout. */
|
||||
const COMMAND = /(?:^|[;&|]\s*|\$\(\s*)(?:"\$_CODEX_PROBE"\s+run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?\d+\s+)?codex\s+(?:exec|review)\b/;
|
||||
const COMMAND = /(?:^|[;&|]\s*|\$\(\s*)(?:"\$_CODEX_PROBE"\s+run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?\d+\s+)?codex\s+(?:exec|review)\b/;
|
||||
const VALIDATOR = /outside-review-result\.ts\b/;
|
||||
|
||||
interface Site { file: string; line: string; fence: string; after: string }
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('Codex skill isolation (#2847)', () => {
|
||||
const calls: string[] = [];
|
||||
for (const file of renderedSkillFiles()) {
|
||||
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
||||
if (/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+)?\d+\s+codex\s+(exec|review)\b/.test(line)) calls.push(`${path.relative(ROOT, file)}: ${line}`);
|
||||
if (/run-with-timeout\s+(?:--user-invoked\s+)?(?:--effort\s+\w+\s+(?:--request-effort\s+'[^']*'\s+)?)?\d+\s+codex\s+(exec|review)\b/.test(line)) calls.push(`${path.relative(ROOT, file)}: ${line}`);
|
||||
}
|
||||
}
|
||||
expect(calls.length).toBeGreaterThan(10);
|
||||
|
||||
@@ -41,6 +41,7 @@ export const IDENTIFIER_PLACEHOLDERS: Record<string, IdentifierGrammar> = {
|
||||
'<clean|flagged>': { grammar: oneOf('clean', 'flagged'), what: 'semantic review outcome' },
|
||||
'<N|null>': { grammar: /^(?:[0-9]+(?:\.[0-9]+)?|null)$/, what: 'health score or null' },
|
||||
'<platform>': { grammar: oneOf('github', 'gitlab', 'unknown'), what: 'detected git platform' },
|
||||
'<effort-level>': { grammar: /^[a-z]*$/, what: "the request's /codex --effort level, or empty for none (the probe validates the level)", quoted: true },
|
||||
|
||||
// Ids, keys and numbers.
|
||||
'<id>': { grammar: ID, what: 'question or record id (kebab-case)' },
|
||||
|
||||
@@ -563,6 +563,7 @@ describe('manual /codex entry: explicit role only, paid probe only for the dispa
|
||||
const preflight = skill.match(/```bash\n(_CODEX_PROBE=[^\n]*\n_CODEX_ROLE=''[\s\S]*?)\n```/)![1]!
|
||||
.replace('{{OUTSIDE_SELF_GUARD:codex}}', RESOLVERS.OUTSIDE_SELF_GUARD(ctxFor('codex', 'claude', HOST_PATHS.claude), ['codex']));
|
||||
const withRole = (text: string, role: string) => text.replaceAll("_CODEX_ROLE=''", `_CODEX_ROLE='${role}'`);
|
||||
const withEffort = (text: string, level: string) => text.replaceAll(`'<effort-level>'`, `'${level}'`);
|
||||
const shell = (h: { dir: string; state: string }, script: string, sh = 'bash') => {
|
||||
fs.mkdirSync(path.join(h.dir, '.claude', 'skills'), { recursive: true });
|
||||
if (!fs.existsSync(path.join(h.dir, '.claude', 'skills', 'gstack'))) fs.symlinkSync(ROOT, path.join(h.dir, '.claude', 'skills', 'gstack'));
|
||||
@@ -574,17 +575,33 @@ describe('manual /codex entry: explicit role only, paid probe only for the dispa
|
||||
|
||||
test('Step 0.5 probes the native model without a role and makes no model call with it', () => {
|
||||
expect(skill).toContain('[Policy setup](https://github.com/garrytan/gstack/blob/main/docs/model-policy.md)');
|
||||
const plain = shell(home(), preflight);
|
||||
const plain = shell(home(), withEffort(preflight, ''));
|
||||
expect(plain.status).toBe(0);
|
||||
expect(plain.stdout).toContain('MODEL_OK');
|
||||
expect(plain.calls.map(codexModel)).toEqual(['model="gpt-5.6-terra"']);
|
||||
const role = shell(home(), withRole(preflight, 'plan-review'));
|
||||
const role = shell(home(), withEffort(withRole(preflight, 'plan-review'), ''));
|
||||
expect(role.status).toBe(0);
|
||||
expect(role.stdout).not.toMatch(/MODEL_OK|MODEL_UNUSABLE|AUTH_FAILED/);
|
||||
expect(role.calls).toEqual([]);
|
||||
expect(role.stderr).not.toContain('NOTICE: gstack plan reviews');
|
||||
});
|
||||
|
||||
test('a valid /codex --effort level passes Step 0.5, shows its source once and probes once', () => {
|
||||
const r = shell(home(), withEffort(preflight, 'medium'));
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).not.toContain('CODEX_EFFORT');
|
||||
expect(r.stderr).toContain('CODEX_EFFORT: medium (source: /codex --effort)');
|
||||
expect(r.calls.map(codexModel)).toEqual(['model="gpt-5.6-terra"']);
|
||||
});
|
||||
|
||||
test('an invalid /codex --effort level stops Step 0.5 before the paid model probe', () => {
|
||||
const r = shell(home(), withEffort(preflight, 'turbo'));
|
||||
expect(r.status).toBe(1);
|
||||
expect(r.stderr).toContain("'turbo' from /codex --effort");
|
||||
expect(r.stdout).not.toMatch(/MODEL_OK|MODEL_UNUSABLE|AUTH_FAILED/);
|
||||
expect(r.calls).toEqual([]);
|
||||
});
|
||||
|
||||
test('a missing manual challenge prompt stops before the paid readiness probe or notice', () => {
|
||||
const h = home();
|
||||
const template = fs.readFileSync(path.join(ROOT, 'codex/sections/challenge-mode.md.tmpl'), 'utf8');
|
||||
@@ -607,7 +624,7 @@ describe('manual /codex entry: explicit role only, paid probe only for the dispa
|
||||
expect(template.match(/\{\{CODEX_SELECT/g)!.length).toBe(selections.length);
|
||||
for (const block of template.split(/\{\{CODEX_SELECT:/).slice(1)) {
|
||||
const [kind, budget] = block.split('}}')[0]!.split(':');
|
||||
const wrappers = [...block.split('```')[0]!.matchAll(/run-with-timeout (?:--user-invoked )?(?:--effort \w+ )?(\d+) codex/g)].map(m => m[1]);
|
||||
const wrappers = [...block.split('```')[0]!.matchAll(/run-with-timeout (?:--user-invoked )?(?:--effort \w+ (?:--request-effort '[^']*' )?)?(\d+) codex/g)].map(m => m[1]);
|
||||
expect(wrappers.length).toBeGreaterThan(0);
|
||||
expect(new Set(wrappers)).toEqual(new Set([budget]));
|
||||
expect(kind).toMatch(/^(exec|review)$/);
|
||||
|
||||
@@ -17,7 +17,7 @@ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-send-boundary-'));
|
||||
afterAll(() => fs.rmSync(TMP, { recursive: true, force: true }));
|
||||
|
||||
const POSITION = String.raw`(?:^|[;&|]\s*|\$\(\s*|\b(?:then|do|else|if|!)\s+)`;
|
||||
const CODEX = new RegExp(`${POSITION}(?:g?timeout\\s+\\S+\\s+)?((?:"\\$_CODEX_PROBE"\\s+run-with-timeout\\s+(?:--user-invoked\\s+)?(?:--effort\\s+\\w+\\s+)?\\d+\\s+)?)codex\\s+(?:exec|review)\\b`);
|
||||
const CODEX = new RegExp(`${POSITION}(?:g?timeout\\s+\\S+\\s+)?((?:"\\$_CODEX_PROBE"\\s+run-with-timeout\\s+(?:--user-invoked\\s+)?(?:--effort\\s+\\w+\\s+(?:--request-effort\\s+'[^']*'\\s+)?)?\\d+\\s+)?)codex\\s+(?:exec|review)\\b`);
|
||||
/** A headless Claude prompt run (`claude -p`, `--print`, or a prompt on stdin), or the resolved CLAUDE_BIN; `claude mcp|auth|config` manage the CLI and send no repository content. */
|
||||
const CLAUDE = new RegExp(`${POSITION}(?:command\\s+)?(?:"\\$\\{?CLAUDE_BIN\\}?"|claude(?=\\s)(?![^|;&]*\\s(?:mcp|auth|config|update|doctor)\\b)[^|;&]*(?:\\s(?:-p|--print)\\b|\\s<\\s*\\S))`);
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('generated skills start outside CLIs only through the consent-enforcing
|
||||
expect(files.length).toBeGreaterThan(20);
|
||||
const bad = files.flatMap(f => directLaunches(fs.readFileSync(f, 'utf8')).map(l => `${path.relative(TMP, f)}: ${l}`));
|
||||
expect(bad).toEqual([]);
|
||||
const wrapped = files.filter(f => /run-with-timeout (?:--user-invoked )?(?:--effort \w+ )?\d+ codex|gstack-claude-code/.test(fs.readFileSync(f, 'utf8')));
|
||||
const wrapped = files.filter(f => /run-with-timeout (?:--user-invoked )?(?:--effort \w+ (?:--request-effort '[^']*' )?)?\d+ codex|gstack-claude-code/.test(fs.readFileSync(f, 'utf8')));
|
||||
expect(wrapped.length).toBeGreaterThan(5);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user