@sabaiway/agent-workflow-kit 1.33.0 → 1.35.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 +99 -0
- package/README.md +2 -1
- package/SKILL.md +6 -2
- package/bin/install.mjs +37 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +13 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +96 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +204 -1
- package/bridges/antigravity-cli-bridge/bin/agy.sh +94 -0
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +245 -1
- package/bridges/antigravity-cli-bridge/capability.json +17 -1
- package/bridges/codex-cli-bridge/SKILL.md +15 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +113 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +288 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +114 -1
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +229 -1
- package/bridges/codex-cli-bridge/capability.json +29 -1
- package/capability.json +1 -1
- package/launchers/windsurf-workflow.md +3 -1
- package/package.json +1 -1
- package/references/contracts.md +3 -2
- package/references/modes/bootstrap.md +11 -6
- package/references/modes/bridge-settings.md +29 -0
- package/references/modes/gates.md +4 -2
- package/references/modes/review-state.md +1 -1
- package/references/modes/status.md +2 -0
- package/references/modes/upgrade.md +11 -5
- package/references/shared/deploy-tail.md +1 -1
- package/references/shared/report-footer.md +11 -4
- package/tools/atomic-write.mjs +144 -0
- package/tools/bridge-settings-read.mjs +221 -0
- package/tools/bridge-settings.mjs +288 -0
- package/tools/commands.mjs +22 -1
- package/tools/family-registry.mjs +45 -2
- package/tools/manifest/schema.md +31 -0
- package/tools/manifest/validate.mjs +85 -0
- package/tools/orchestration-write.mjs +8 -78
- package/tools/presentation.mjs +1 -0
- package/tools/procedures.mjs +26 -4
- package/tools/recipes.mjs +16 -6
- package/tools/renderers.mjs +17 -2
- package/tools/review-state.mjs +4 -2
- package/tools/seed-gates.mjs +413 -0
- package/tools/setup-backends.mjs +107 -9
- package/tools/velocity-profile.mjs +4 -0
- package/tools/view-model.mjs +20 -0
|
@@ -50,6 +50,10 @@ Usage:
|
|
|
50
50
|
agy-run @path/to/prompt.md
|
|
51
51
|
agy-run <prompt|-|@file> -- <extra agy flags...>
|
|
52
52
|
|
|
53
|
+
Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
|
|
54
|
+
${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
|
|
55
|
+
AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default = AGY_TIMEOUT, 5m)
|
|
56
|
+
|
|
53
57
|
Environment: AGY_MODEL (exact display string from `agy models`; empty ⇒ agy's settings.json), AGY_TIMEOUT / AGY_HARD_TIMEOUT (duration strings), AGY_MAX_PROMPT_BYTES (single-argv byte ceiling; the override only lowers it).
|
|
54
58
|
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
55
59
|
HELP
|
|
@@ -68,6 +72,96 @@ while IFS= read -r _api_key_var; do
|
|
|
68
72
|
unset "$_api_key_var" 2>/dev/null || true
|
|
69
73
|
done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
|
|
70
74
|
|
|
75
|
+
# This wrapper's applied settings-file subset (see the shared reader block below).
|
|
76
|
+
AW_SETTINGS_APPLIED="AGY_HARD_TIMEOUT"
|
|
77
|
+
|
|
78
|
+
# --- Bridge settings file (host-level, kit-independent) — byte-identical across the four wrappers ---
|
|
79
|
+
# ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE lines,
|
|
80
|
+
# PARSED (grep/case), NEVER sourced — a file line can never execute code. Precedence: explicit
|
|
81
|
+
# env (even empty: KEY= disables the knob for one run) > file > built-in default. Each wrapper
|
|
82
|
+
# APPLIES only its own subset ($AW_SETTINGS_APPLIED, set above this block) but RECOGNIZES the
|
|
83
|
+
# whole registry: a key belonging to another wrapper or another bridge is skipped silently; only
|
|
84
|
+
# a key unknown to the entire registry warns (once per key), naming this file as the source.
|
|
85
|
+
# A malformed line warns and is ignored; a value failing the key's typed validation warns and
|
|
86
|
+
# falls back to the built-in default (never passed to the binary); duplicate key → the LAST
|
|
87
|
+
# occurrence wins; a missing file is silent; an existing-but-unreadable or non-regular file
|
|
88
|
+
# warns loudly and falls back to built-in defaults (a directory or FIFO is never opened).
|
|
89
|
+
# Diagnostics are emitted once per user-visible run: a delegating wrapper (agy-review →
|
|
90
|
+
# agy-run) exports AW_SETTINGS_NOTIFIED so the child never repeats the same file's warnings.
|
|
91
|
+
# The registry, per-wrapper subsets, and typed constants mirror
|
|
92
|
+
# the bridges' capability.json `settings` blocks (manifest-as-source, drift-guarded by tests).
|
|
93
|
+
aw_settings_file() {
|
|
94
|
+
printf '%s/agent-workflow/bridge-settings.conf' "${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
95
|
+
}
|
|
96
|
+
aw_settings_known() {
|
|
97
|
+
case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR " in
|
|
98
|
+
*" $1 "*) return 0 ;;
|
|
99
|
+
*) return 1 ;;
|
|
100
|
+
esac
|
|
101
|
+
}
|
|
102
|
+
aw_settings_valid() {
|
|
103
|
+
local k="$1" v="$2" int_re='^[0-9]+$' dur_re='^[0-9]+(\.[0-9]+)?[smhd]$' zero_re='^0+(\.0+)?[smhd]$'
|
|
104
|
+
case "$k" in
|
|
105
|
+
CODEX_SERVICE_TIER) [[ "$v" == "priority" ]] ;;
|
|
106
|
+
CODEX_HARD_TIMEOUT) [[ "$v" =~ $int_re ]] && (( 10#$v >= 1 && 10#$v <= 86400 )) ;;
|
|
107
|
+
CODEX_REVIEW_MAX_TOTAL_BYTES) [[ "$v" =~ $int_re ]] && (( 10#$v >= 1 && 10#$v <= 100000000 )) ;;
|
|
108
|
+
AGY_HARD_TIMEOUT) [[ "$v" =~ $dur_re && ! "$v" =~ $zero_re ]] ;;
|
|
109
|
+
AGY_REVIEW_ALLOW_ADDDIR) [[ "$v" == "0" || "$v" == "1" ]] ;;
|
|
110
|
+
*) return 1 ;;
|
|
111
|
+
esac
|
|
112
|
+
}
|
|
113
|
+
aw_apply_settings() {
|
|
114
|
+
local file line key value warned notify
|
|
115
|
+
file="$(aw_settings_file)"
|
|
116
|
+
[[ -e "$file" ]] || return 0
|
|
117
|
+
notify=1
|
|
118
|
+
[[ -n "${AW_SETTINGS_NOTIFIED:-}" ]] && notify=0
|
|
119
|
+
export AW_SETTINGS_NOTIFIED=1
|
|
120
|
+
if [[ ! -f "$file" || ! -r "$file" ]]; then
|
|
121
|
+
if (( notify )); then
|
|
122
|
+
echo "warning: bridge settings file '$file' exists but is unreadable or not a regular file — using built-in defaults." >&2
|
|
123
|
+
fi
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
if (( notify )); then
|
|
127
|
+
warned=" "
|
|
128
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
129
|
+
[[ -z "${line//[[:space:]]/}" ]] && continue
|
|
130
|
+
case "${line#"${line%%[![:space:]]*}"}" in "#"*) continue ;; esac
|
|
131
|
+
if [[ ! "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
|
132
|
+
echo "warning: malformed line in bridge settings file '$file' (ignored): $line" >&2
|
|
133
|
+
continue
|
|
134
|
+
fi
|
|
135
|
+
key="${line%%=*}"
|
|
136
|
+
if ! aw_settings_known "$key"; then
|
|
137
|
+
case "$warned" in
|
|
138
|
+
*" $key "*) : ;;
|
|
139
|
+
*)
|
|
140
|
+
warned="$warned$key "
|
|
141
|
+
echo "warning: unknown key '$key' in bridge settings file '$file' (ignored)." >&2
|
|
142
|
+
;;
|
|
143
|
+
esac
|
|
144
|
+
fi
|
|
145
|
+
done <"$file"
|
|
146
|
+
fi
|
|
147
|
+
for key in $AW_SETTINGS_APPLIED; do
|
|
148
|
+
if [[ -n "${!key+x}" ]]; then continue; fi
|
|
149
|
+
value="$(grep "^${key}=" "$file" 2>/dev/null || true)"
|
|
150
|
+
[[ -n "$value" ]] || continue
|
|
151
|
+
value="${value##*$'\n'}"
|
|
152
|
+
value="${value#*=}"
|
|
153
|
+
if ! aw_settings_valid "$key" "$value"; then
|
|
154
|
+
if (( notify )); then
|
|
155
|
+
echo "warning: invalid value '$value' for $key in bridge settings file '$file' — using the built-in default." >&2
|
|
156
|
+
fi
|
|
157
|
+
continue
|
|
158
|
+
fi
|
|
159
|
+
export "$key=$value"
|
|
160
|
+
done
|
|
161
|
+
return 0
|
|
162
|
+
}
|
|
163
|
+
aw_apply_settings
|
|
164
|
+
|
|
71
165
|
if ! command -v agy >/dev/null 2>&1; then
|
|
72
166
|
echo "error: 'agy' (Antigravity CLI) not found on PATH. Install it and run 'agy' once to sign in." >&2
|
|
73
167
|
exit 127
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, existsSync, readdirSync, symlinkSync } from 'node:fs';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, existsSync, readdirSync, symlinkSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join, dirname, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -208,3 +208,247 @@ describe('agy.sh — --help (pre-preflight, candidate C)', () => {
|
|
|
208
208
|
assert.equal(invoked, true, 'the run must proceed to agy with the payload');
|
|
209
209
|
});
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
// ── bridge settings file (bridges 2.3.0) ─────────────────────────────────────────
|
|
213
|
+
// ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE
|
|
214
|
+
// lines, PARSED (never sourced). Precedence: explicit env (even empty: KEY= disables the
|
|
215
|
+
// knob) > file > built-in default. agy-run APPLIES only AGY_HARD_TIMEOUT and RECOGNIZES
|
|
216
|
+
// the whole registry. HOME is the sandbox home, so the default path is hermetic per test.
|
|
217
|
+
|
|
218
|
+
const writeSettings = (home, text) => {
|
|
219
|
+
const dir = join(home, '.config', 'agent-workflow');
|
|
220
|
+
mkdirSync(dir, { recursive: true });
|
|
221
|
+
const file = join(dir, 'bridge-settings.conf');
|
|
222
|
+
writeFileSync(file, text);
|
|
223
|
+
return file;
|
|
224
|
+
};
|
|
225
|
+
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
226
|
+
|
|
227
|
+
describe('agy.sh — bridge settings file (bridges 2.3.0)', () => {
|
|
228
|
+
it('a file-set AGY_HARD_TIMEOUT is effective (killed at the file cap)', () => {
|
|
229
|
+
const home = makeSandbox('#!/usr/bin/env bash\nsleep 5\n');
|
|
230
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=2s\n');
|
|
231
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
232
|
+
rmSync(home, { recursive: true, force: true });
|
|
233
|
+
assert.notEqual(r.status, 0, 'the file cap must apply when the env is unset');
|
|
234
|
+
assert.match(r.stderr, /exceeded the hard cap AGY_HARD_TIMEOUT=2s/);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('env overrides file: env=2s file=10m → killed at the env cap', () => {
|
|
238
|
+
const home = makeSandbox('#!/usr/bin/env bash\nsleep 5\n');
|
|
239
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=10m\n');
|
|
240
|
+
const r = runWrapper(home, { AGY_HARD_TIMEOUT: '2s', AGY_TIMEOUT: '2s', AGY_MODEL: '' });
|
|
241
|
+
rmSync(home, { recursive: true, force: true });
|
|
242
|
+
assert.notEqual(r.status, 0, 'the env cap (2s) must win over the file cap (10m)');
|
|
243
|
+
assert.match(r.stderr, /exceeded the hard cap AGY_HARD_TIMEOUT=2s/);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('an EXPLICITLY EMPTY env (AGY_HARD_TIMEOUT=) disables the file knob for one run', () => {
|
|
247
|
+
const home = makeSandbox('#!/usr/bin/env bash\nsleep 3\necho "OK reply"\n');
|
|
248
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=2s\n');
|
|
249
|
+
const r = runWrapper(home, { AGY_HARD_TIMEOUT: '', AGY_MODEL: '' });
|
|
250
|
+
rmSync(home, { recursive: true, force: true });
|
|
251
|
+
assert.equal(r.status, 0, `built-in default must apply (not the 2s file cap): ${r.stderr}`);
|
|
252
|
+
assert.match(r.stdout, /OK reply/);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('duplicate key → the LAST occurrence wins (10m then 2s → killed at 2s)', () => {
|
|
256
|
+
const home = makeSandbox('#!/usr/bin/env bash\nsleep 5\n');
|
|
257
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=10m\nAGY_HARD_TIMEOUT=2s\n');
|
|
258
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
259
|
+
rmSync(home, { recursive: true, force: true });
|
|
260
|
+
assert.notEqual(r.status, 0);
|
|
261
|
+
assert.match(r.stderr, /exceeded the hard cap AGY_HARD_TIMEOUT=2s/);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('an invalid duration warns and falls back to the built-in default', () => {
|
|
265
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
266
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=abc\n');
|
|
267
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
268
|
+
rmSync(home, { recursive: true, force: true });
|
|
269
|
+
assert.equal(r.status, 0, r.stderr);
|
|
270
|
+
assert.match(r.stderr, /invalid value 'abc'/);
|
|
271
|
+
assert.match(r.stdout, /OK reply/);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('a bare-integer duration (no unit suffix) is invalid → warn + built-in default', () => {
|
|
275
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
276
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=90\n');
|
|
277
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
278
|
+
rmSync(home, { recursive: true, force: true });
|
|
279
|
+
assert.equal(r.status, 0, r.stderr);
|
|
280
|
+
assert.match(r.stderr, /invalid value '90'/, 'duration values require a unit suffix (5m/30m/90s)');
|
|
281
|
+
assert.match(r.stdout, /OK reply/);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('a ZERO duration is invalid — timeout 0 would silently DISABLE the hard cap', () => {
|
|
285
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
286
|
+
writeSettings(home, 'AGY_HARD_TIMEOUT=0s\n');
|
|
287
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
288
|
+
rmSync(home, { recursive: true, force: true });
|
|
289
|
+
assert.equal(r.status, 0, r.stderr);
|
|
290
|
+
assert.match(r.stderr, /invalid value '0s'/, 'a persistent settings line must never remove the stall guard');
|
|
291
|
+
assert.match(r.stdout, /OK reply/);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', () => {
|
|
295
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
296
|
+
mkdirSync(join(home, '.config', 'agent-workflow', 'bridge-settings.conf'), { recursive: true });
|
|
297
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
298
|
+
rmSync(home, { recursive: true, force: true });
|
|
299
|
+
assert.equal(r.status, 0, `a directory must degrade honestly, not kill the run: ${r.stderr}`);
|
|
300
|
+
assert.match(r.stderr, /unreadable or not a regular file/);
|
|
301
|
+
assert.doesNotMatch(r.stderr, /Is a directory/, 'no raw bash error may leak');
|
|
302
|
+
assert.match(r.stdout, /OK reply/);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("another bridge's valid key is skipped silently (and never applied)", () => {
|
|
306
|
+
const home = makeSandbox('#!/usr/bin/env bash\nsleep 3\necho "OK reply"\n');
|
|
307
|
+
writeSettings(home, 'CODEX_SERVICE_TIER=priority\nCODEX_HARD_TIMEOUT=2\n');
|
|
308
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
309
|
+
rmSync(home, { recursive: true, force: true });
|
|
310
|
+
assert.equal(r.status, 0, `a codex key must not cap an agy run: ${r.stderr}`);
|
|
311
|
+
assert.doesNotMatch(r.stderr, /bridge settings/, 'a recognized non-applied key earns NO warning');
|
|
312
|
+
assert.match(r.stdout, /OK reply/);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('a truly unknown key warns ONCE naming the file; the run is unaffected', () => {
|
|
316
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
317
|
+
writeSettings(home, 'TOTALLY_UNKNOWN=1\nTOTALLY_UNKNOWN=2\n');
|
|
318
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
319
|
+
rmSync(home, { recursive: true, force: true });
|
|
320
|
+
assert.equal(r.status, 0, r.stderr);
|
|
321
|
+
const warns = r.stderr.match(/unknown key 'TOTALLY_UNKNOWN'/g) ?? [];
|
|
322
|
+
assert.equal(warns.length, 1, `exactly one warning per unknown key, got ${warns.length}`);
|
|
323
|
+
assert.match(r.stderr, /bridge-settings\.conf/, 'the warning must name the settings file');
|
|
324
|
+
assert.match(r.stdout, /OK reply/);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('malformed lines warn and are ignored; comments and blank lines are silent', () => {
|
|
328
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
329
|
+
writeSettings(home, '# a comment\n\nNOT A KEY VALUE LINE\n');
|
|
330
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
331
|
+
rmSync(home, { recursive: true, force: true });
|
|
332
|
+
assert.equal(r.status, 0, r.stderr);
|
|
333
|
+
const malformed = r.stderr.match(/malformed line/g) ?? [];
|
|
334
|
+
assert.equal(malformed.length, 1, 'comments/blank lines must NOT count as malformed');
|
|
335
|
+
assert.match(r.stdout, /OK reply/);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, () => {
|
|
339
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
340
|
+
const file = writeSettings(home, 'AGY_HARD_TIMEOUT=2s\n');
|
|
341
|
+
chmodSync(file, 0o000);
|
|
342
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
343
|
+
rmSync(home, { recursive: true, force: true });
|
|
344
|
+
assert.equal(r.status, 0, r.stderr);
|
|
345
|
+
assert.match(r.stderr, /unreadable/);
|
|
346
|
+
assert.match(r.stdout, /OK reply/);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('a settings line can NEVER execute code (command-substitution payload inert)', () => {
|
|
350
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
351
|
+
const pwned = join(home, 'pwned');
|
|
352
|
+
writeSettings(home, `AGY_HARD_TIMEOUT=$(touch ${pwned})\nEVIL_KEY=\`touch ${pwned}2\`\n`);
|
|
353
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
354
|
+
const executed = existsSync(pwned) || existsSync(`${pwned}2`);
|
|
355
|
+
rmSync(home, { recursive: true, force: true });
|
|
356
|
+
assert.equal(r.status, 0, r.stderr);
|
|
357
|
+
assert.equal(executed, false, 'file content must be parsed, never evaluated');
|
|
358
|
+
assert.match(r.stdout, /OK reply/);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('no file → byte-identical behaviour to today (no settings chatter)', () => {
|
|
362
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
363
|
+
const r = runWrapper(home, { AGY_MODEL: '' });
|
|
364
|
+
rmSync(home, { recursive: true, force: true });
|
|
365
|
+
assert.equal(r.status, 0, r.stderr);
|
|
366
|
+
assert.doesNotMatch(r.stderr, /bridge settings/);
|
|
367
|
+
assert.match(r.stdout, /OK reply/);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ── settings surface ⟷ manifest (drift guard, D6) ────────────────────────────────
|
|
372
|
+
// agy-run's --help stays candidate-C (not contract-pinned), but its SETTINGS surface
|
|
373
|
+
// is manifest-pinned like the other three wrappers: the Settings help section and the
|
|
374
|
+
// shell constants (registry / applied subset / typed validation arms) stay set-equal
|
|
375
|
+
// to the bridges' capability.json `settings` blocks. The sibling manifest path
|
|
376
|
+
// resolves identically in the repo layout and in the kit's bridges/ mirror layout.
|
|
377
|
+
const SETTINGS_HEADER = 'Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):';
|
|
378
|
+
const MANIFEST = JSON.parse(readFileSync(join(HERE, '..', 'capability.json'), 'utf8'));
|
|
379
|
+
const SIBLING_MANIFEST = JSON.parse(readFileSync(join(HERE, '..', '..', 'codex-cli-bridge', 'capability.json'), 'utf8'));
|
|
380
|
+
const ALL_SETTINGS = [...(MANIFEST.settings ?? []), ...(SIBLING_MANIFEST.settings ?? [])];
|
|
381
|
+
const SETTINGS_CMD = 'agy-run';
|
|
382
|
+
const setEq = (got, want, msg) => assert.deepEqual([...got].sort(), [...want].sort(), msg);
|
|
383
|
+
const helpSection = (text, header) => {
|
|
384
|
+
const lines = text.split('\n');
|
|
385
|
+
const i = lines.findIndex((l) => l.trim() === header);
|
|
386
|
+
assert.notEqual(i, -1, `--help must carry a "${header}" section`);
|
|
387
|
+
const out = [];
|
|
388
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
389
|
+
if (lines[j].trim() === '') break;
|
|
390
|
+
out.push(lines[j].trim());
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
describe('agy.sh — settings surface ⟷ manifest (D6, manifest-pinned)', () => {
|
|
396
|
+
const runHelpText = () => {
|
|
397
|
+
const home = mkdtempSync(join(tmpdir(), 'agy-settings-help-'));
|
|
398
|
+
const r = spawnSync('bash', [WRAPPER, '--help'], {
|
|
399
|
+
env: { HOME: home, PATH: makePathWithout(home, ['agy', 'git']) },
|
|
400
|
+
encoding: 'utf8',
|
|
401
|
+
timeout: 15000,
|
|
402
|
+
});
|
|
403
|
+
rmSync(home, { recursive: true, force: true });
|
|
404
|
+
assert.equal(r.status, 0, r.stderr);
|
|
405
|
+
return r.stdout;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', () => {
|
|
409
|
+
const section = helpSection(runHelpText(), SETTINGS_HEADER);
|
|
410
|
+
const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
|
|
411
|
+
const want = (MANIFEST.settings ?? []).filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
|
|
412
|
+
assert.ok(want.length > 0, 'the manifest must declare settings for this wrapper');
|
|
413
|
+
setEq(got, want, 'help Settings keys ⟷ manifest settings.appliesTo');
|
|
414
|
+
assert.ok(section.some((l) => l.includes('agent-workflow/bridge-settings.conf')), 'the section names the settings file');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const source = readFileSync(WRAPPER, 'utf8');
|
|
418
|
+
|
|
419
|
+
it('aw_settings_known carries exactly the UNION of both bridges settings keys', () => {
|
|
420
|
+
const m = source.match(/aw_settings_known\(\) \{\n case " ([^"]+) " in/);
|
|
421
|
+
assert.ok(m, 'aw_settings_known registry case not found');
|
|
422
|
+
assert.ok(ALL_SETTINGS.length >= 5, 'both manifests must contribute settings');
|
|
423
|
+
setEq(m[1].trim().split(/\s+/), ALL_SETTINGS.map((s) => s.key), 'shell registry ⟷ manifest union');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', () => {
|
|
427
|
+
const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
|
|
428
|
+
assert.ok(m, 'AW_SETTINGS_APPLIED not found');
|
|
429
|
+
const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
|
|
430
|
+
assert.ok(want.length > 0);
|
|
431
|
+
setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('aw_settings_valid arms carry the manifest typed constants per key', () => {
|
|
435
|
+
const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
|
|
436
|
+
assert.ok(body, 'aw_settings_valid not found');
|
|
437
|
+
const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
|
|
438
|
+
setEq(armKeys, ALL_SETTINGS.map((s) => s.key), 'validation arms ⟷ manifest keys');
|
|
439
|
+
for (const s of ALL_SETTINGS) {
|
|
440
|
+
const arm = body[0].match(new RegExp(`^ ${s.key}\\) (.*) ;;$`, 'm'));
|
|
441
|
+
assert.ok(arm, `no validation arm for ${s.key}`);
|
|
442
|
+
if (s.kind === 'enum') for (const v of s.values) assert.ok(arm[1].includes(`"${v}"`), `${s.key}: enum value '${v}' not pinned`);
|
|
443
|
+
if (s.kind === 'integer') {
|
|
444
|
+
assert.match(arm[1], new RegExp(`>= ${s.min}\\b`), `${s.key}: min ${s.min} not pinned`);
|
|
445
|
+
assert.match(arm[1], new RegExp(`<= ${s.max}\\b`), `${s.key}: max ${s.max} not pinned`);
|
|
446
|
+
}
|
|
447
|
+
if (s.kind === 'boolean') assert.ok(arm[1].includes('"0"') && arm[1].includes('"1"'), `${s.key}: boolean 0/1 not pinned`);
|
|
448
|
+
if (s.kind === 'duration') {
|
|
449
|
+
assert.ok(arm[1].includes('$dur_re'), `${s.key}: duration grammar not pinned`);
|
|
450
|
+
assert.ok(arm[1].includes('$zero_re'), `${s.key}: zero-duration rejection not pinned (timeout 0 disables the cap)`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "antigravity-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.3.0",
|
|
7
7
|
"provides": ["review", "probe"],
|
|
8
8
|
"roles": {
|
|
9
9
|
"review": {
|
|
@@ -33,6 +33,22 @@
|
|
|
33
33
|
},
|
|
34
34
|
"probe": { "cmd": "agy-run", "source": "bin/agy.sh", "output": "advisory" }
|
|
35
35
|
},
|
|
36
|
+
"settings": [
|
|
37
|
+
{
|
|
38
|
+
"key": "AGY_HARD_TIMEOUT",
|
|
39
|
+
"kind": "duration",
|
|
40
|
+
"default": null,
|
|
41
|
+
"appliesTo": ["agy-run", "agy-review"],
|
|
42
|
+
"effect": "hard wall-clock cap via timeout(1), duration string (e.g. 5m, 30m, 90s); built-in default: agy-run = AGY_TIMEOUT (5m), agy-review 30m."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "AGY_REVIEW_ALLOW_ADDDIR",
|
|
46
|
+
"kind": "boolean",
|
|
47
|
+
"default": "0",
|
|
48
|
+
"appliesTo": ["agy-review"],
|
|
49
|
+
"effect": "1 ⇒ an oversized code review offloads the change set to a private --add-dir staging dir (re-enables the Issue-001 stall risk; the hard timeout bounds it). Default 0: an oversized prompt refuses instead."
|
|
50
|
+
}
|
|
51
|
+
],
|
|
36
52
|
"detect": {
|
|
37
53
|
"installed": {
|
|
38
54
|
"env": "ANTIGRAVITY_CLI_BRIDGE_DIR",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: codex-cli-bridge
|
|
3
3
|
description: Delegate work to the OpenAI Codex CLI (`codex`) under a ChatGPT subscription — run plan/instruction EXECUTION in a sandboxed workspace, or get a read-only ADVISORY review of a plan or working-tree diff — as a second delegated-execution backend beside Antigravity. Use when the user wants to hand a bounded coding task or plan to `codex exec`, get a second-opinion review from codex, install or authenticate Codex CLI, understand its sandbox/network/approval policy, drive codex efficiently from the main agent (exec vs review, resume, the commit boundary), bridge project context (`AGENTS.md`) into codex, or troubleshoot codex flags, models, auth, or its no-TTY headless behaviour.
|
|
4
4
|
metadata:
|
|
5
|
-
version: '2.
|
|
5
|
+
version: '2.3.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# codex-cli-bridge
|
|
@@ -109,6 +109,7 @@ defeat a policy is guarded — see [§ Models](#models-quality-first-pinned).
|
|
|
109
109
|
| `CODEX_MODEL` | `gpt-5.5` (pinned) | model; non-default REFUSED unless `CODEX_PROBE=1` |
|
|
110
110
|
| `CODEX_EFFORT` | `xhigh` (pinned) | reasoning effort; non-default REFUSED unless `CODEX_PROBE=1` |
|
|
111
111
|
| `CODEX_HARD_TIMEOUT` | `3600` (exec) / `1800` (review) | hard wall-clock cap (seconds) via `timeout`/`gtimeout`; exit 124/137 ⇒ "exceeded hard cap". No `timeout` binary ⇒ loud warning + uncapped (never silent). |
|
|
112
|
+
| `CODEX_SERVICE_TIER` | unset (standard tier) | **SPEND knob**: `priority` (catalog name "Fast") = ~1.5× token speed at a **2.5× credit rate** on gpt-5.5 — quality-neutral (same model). codex accepts any `-c service_tier` string silently (probe-pinned 2026-07-05), so the wrapper validates: an unsupported value warns and runs standard. Env or settings file. |
|
|
112
113
|
| `CODEX_SESSION_FILE` | `./.codex-last-session` | where `codex-exec` records the session id and where `--resume-last` reads it |
|
|
113
114
|
| `CODEX_REVIEW_MAX_TOTAL_BYTES` | `1500000` | `codex-review code`: above this the assembled diff goes via a git-dir temp file instead of inline — never truncated |
|
|
114
115
|
| `CODEX_REVIEW_SCHEMA` | unset | `codex-review`: `=1` returns findings as a validated JSON object (`--output-schema`), with a raw-text fallback. Default off. |
|
|
@@ -117,6 +118,19 @@ defeat a policy is guarded — see [§ Models](#models-quality-first-pinned).
|
|
|
117
118
|
The git-write shim, `--ignore-user-config`, and the `*_API_KEY` scrub are NOT env-tunable — they are
|
|
118
119
|
fixed invariants.
|
|
119
120
|
|
|
121
|
+
### Settings file (host-level, survives kit upgrades)
|
|
122
|
+
|
|
123
|
+
`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf` holds `KEY=VALUE` lines,
|
|
124
|
+
**parsed, never sourced** — a file line can never execute code. Precedence: explicit env (even
|
|
125
|
+
empty — `KEY=` disables a knob for one run) > file > built-in default. File-settable keys for this
|
|
126
|
+
bridge: `CODEX_SERVICE_TIER` (the Fast tier — **2.5× credit rate**; enabling it is a consented
|
|
127
|
+
per-host spend decision, never a default), `CODEX_HARD_TIMEOUT`, `CODEX_REVIEW_MAX_TOTAL_BYTES` —
|
|
128
|
+
exactly the manifest `settings` block (the single source; the wrapper constants and `--help` are
|
|
129
|
+
drift-guarded against it). Model/effort keys are **not** file-settable — the quality guard above
|
|
130
|
+
is untouched. The file lives **outside every kit-managed tree**, so a kit refresh/upgrade can
|
|
131
|
+
never wipe it; edit it by hand or via `/agent-workflow-kit bridge-settings` (preview-first,
|
|
132
|
+
consent-gated).
|
|
133
|
+
|
|
120
134
|
## Project context (how `codex` sees the repo)
|
|
121
135
|
|
|
122
136
|
`codex` auto-**merges** `AGENTS.md` (root→cwd, plus a global `~/.codex/AGENTS.md`) straight into its
|
|
@@ -67,6 +67,11 @@ Guarded passthrough after '--':
|
|
|
67
67
|
blocked always: -c* --config* -s* --sandbox* --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust --full-auto --oss --local-provider* -p* --profile* -m* --model* -o* --output-last-message* --json* --color* --output-schema* --ephemeral*
|
|
68
68
|
relaxed only under CODEX_PROBE=1: --add-dir* -C* --cd* --skip-git-repo-check --ignore-rules --enable* --disable*
|
|
69
69
|
|
|
70
|
+
Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
|
|
71
|
+
${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
|
|
72
|
+
CODEX_SERVICE_TIER — service tier: 'priority' (Fast — ~1.5x speed at a 2.5x credit rate on gpt-5.5); a consented SPEND knob, default off (standard tier)
|
|
73
|
+
CODEX_HARD_TIMEOUT — hard wall-clock cap, integer seconds 1..86400 (built-in default 3600)
|
|
74
|
+
|
|
70
75
|
Environment: CODEX_HARD_TIMEOUT (seconds, default 3600), CODEX_PROBE=1 (throwaway probe only).
|
|
71
76
|
Requires at run time: the codex CLI on PATH, a ChatGPT-subscription login, a git work tree with a root AGENTS.md (--help needs none of these).
|
|
72
77
|
HELP
|
|
@@ -74,6 +79,96 @@ HELP
|
|
|
74
79
|
;;
|
|
75
80
|
esac
|
|
76
81
|
|
|
82
|
+
# This wrapper's applied settings-file subset (see the shared reader block below).
|
|
83
|
+
AW_SETTINGS_APPLIED="CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT"
|
|
84
|
+
|
|
85
|
+
# --- Bridge settings file (host-level, kit-independent) — byte-identical across the four wrappers ---
|
|
86
|
+
# ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE lines,
|
|
87
|
+
# PARSED (grep/case), NEVER sourced — a file line can never execute code. Precedence: explicit
|
|
88
|
+
# env (even empty: KEY= disables the knob for one run) > file > built-in default. Each wrapper
|
|
89
|
+
# APPLIES only its own subset ($AW_SETTINGS_APPLIED, set above this block) but RECOGNIZES the
|
|
90
|
+
# whole registry: a key belonging to another wrapper or another bridge is skipped silently; only
|
|
91
|
+
# a key unknown to the entire registry warns (once per key), naming this file as the source.
|
|
92
|
+
# A malformed line warns and is ignored; a value failing the key's typed validation warns and
|
|
93
|
+
# falls back to the built-in default (never passed to the binary); duplicate key → the LAST
|
|
94
|
+
# occurrence wins; a missing file is silent; an existing-but-unreadable or non-regular file
|
|
95
|
+
# warns loudly and falls back to built-in defaults (a directory or FIFO is never opened).
|
|
96
|
+
# Diagnostics are emitted once per user-visible run: a delegating wrapper (agy-review →
|
|
97
|
+
# agy-run) exports AW_SETTINGS_NOTIFIED so the child never repeats the same file's warnings.
|
|
98
|
+
# The registry, per-wrapper subsets, and typed constants mirror
|
|
99
|
+
# the bridges' capability.json `settings` blocks (manifest-as-source, drift-guarded by tests).
|
|
100
|
+
aw_settings_file() {
|
|
101
|
+
printf '%s/agent-workflow/bridge-settings.conf' "${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
102
|
+
}
|
|
103
|
+
aw_settings_known() {
|
|
104
|
+
case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR " in
|
|
105
|
+
*" $1 "*) return 0 ;;
|
|
106
|
+
*) return 1 ;;
|
|
107
|
+
esac
|
|
108
|
+
}
|
|
109
|
+
aw_settings_valid() {
|
|
110
|
+
local k="$1" v="$2" int_re='^[0-9]+$' dur_re='^[0-9]+(\.[0-9]+)?[smhd]$' zero_re='^0+(\.0+)?[smhd]$'
|
|
111
|
+
case "$k" in
|
|
112
|
+
CODEX_SERVICE_TIER) [[ "$v" == "priority" ]] ;;
|
|
113
|
+
CODEX_HARD_TIMEOUT) [[ "$v" =~ $int_re ]] && (( 10#$v >= 1 && 10#$v <= 86400 )) ;;
|
|
114
|
+
CODEX_REVIEW_MAX_TOTAL_BYTES) [[ "$v" =~ $int_re ]] && (( 10#$v >= 1 && 10#$v <= 100000000 )) ;;
|
|
115
|
+
AGY_HARD_TIMEOUT) [[ "$v" =~ $dur_re && ! "$v" =~ $zero_re ]] ;;
|
|
116
|
+
AGY_REVIEW_ALLOW_ADDDIR) [[ "$v" == "0" || "$v" == "1" ]] ;;
|
|
117
|
+
*) return 1 ;;
|
|
118
|
+
esac
|
|
119
|
+
}
|
|
120
|
+
aw_apply_settings() {
|
|
121
|
+
local file line key value warned notify
|
|
122
|
+
file="$(aw_settings_file)"
|
|
123
|
+
[[ -e "$file" ]] || return 0
|
|
124
|
+
notify=1
|
|
125
|
+
[[ -n "${AW_SETTINGS_NOTIFIED:-}" ]] && notify=0
|
|
126
|
+
export AW_SETTINGS_NOTIFIED=1
|
|
127
|
+
if [[ ! -f "$file" || ! -r "$file" ]]; then
|
|
128
|
+
if (( notify )); then
|
|
129
|
+
echo "warning: bridge settings file '$file' exists but is unreadable or not a regular file — using built-in defaults." >&2
|
|
130
|
+
fi
|
|
131
|
+
return 0
|
|
132
|
+
fi
|
|
133
|
+
if (( notify )); then
|
|
134
|
+
warned=" "
|
|
135
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
136
|
+
[[ -z "${line//[[:space:]]/}" ]] && continue
|
|
137
|
+
case "${line#"${line%%[![:space:]]*}"}" in "#"*) continue ;; esac
|
|
138
|
+
if [[ ! "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
|
139
|
+
echo "warning: malformed line in bridge settings file '$file' (ignored): $line" >&2
|
|
140
|
+
continue
|
|
141
|
+
fi
|
|
142
|
+
key="${line%%=*}"
|
|
143
|
+
if ! aw_settings_known "$key"; then
|
|
144
|
+
case "$warned" in
|
|
145
|
+
*" $key "*) : ;;
|
|
146
|
+
*)
|
|
147
|
+
warned="$warned$key "
|
|
148
|
+
echo "warning: unknown key '$key' in bridge settings file '$file' (ignored)." >&2
|
|
149
|
+
;;
|
|
150
|
+
esac
|
|
151
|
+
fi
|
|
152
|
+
done <"$file"
|
|
153
|
+
fi
|
|
154
|
+
for key in $AW_SETTINGS_APPLIED; do
|
|
155
|
+
if [[ -n "${!key+x}" ]]; then continue; fi
|
|
156
|
+
value="$(grep "^${key}=" "$file" 2>/dev/null || true)"
|
|
157
|
+
[[ -n "$value" ]] || continue
|
|
158
|
+
value="${value##*$'\n'}"
|
|
159
|
+
value="${value#*=}"
|
|
160
|
+
if ! aw_settings_valid "$key" "$value"; then
|
|
161
|
+
if (( notify )); then
|
|
162
|
+
echo "warning: invalid value '$value' for $key in bridge settings file '$file' — using the built-in default." >&2
|
|
163
|
+
fi
|
|
164
|
+
continue
|
|
165
|
+
fi
|
|
166
|
+
export "$key=$value"
|
|
167
|
+
done
|
|
168
|
+
return 0
|
|
169
|
+
}
|
|
170
|
+
aw_apply_settings
|
|
171
|
+
|
|
77
172
|
DEFAULT_CODEX_MODEL="gpt-5.5" # frontier coding model (verified locally) — pinned
|
|
78
173
|
DEFAULT_CODEX_EFFORT="xhigh" # maximum reasoning effort — pinned
|
|
79
174
|
CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
|
|
@@ -82,6 +177,22 @@ CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
|
|
|
82
177
|
# varies — a trivial reply was observed taking minutes). Raise for a known-healthy
|
|
83
178
|
# long run; lowering it only risks killing real work.
|
|
84
179
|
CODEX_HARD_TIMEOUT="${CODEX_HARD_TIMEOUT:-3600}"
|
|
180
|
+
# Codex service tier (quality-neutral speed knob; live-probed 2026-07-05): default EMPTY ⇒ no
|
|
181
|
+
# service_tier flag (standard tier) — enabling Fast is a consented per-host SPEND act, never a
|
|
182
|
+
# silent default. The only server-catalog tier id on this subscription is 'priority' (catalog
|
|
183
|
+
# display name "Fast": ~1.5x token speed at a 2.5x credit rate on gpt-5.5; quality-neutral —
|
|
184
|
+
# same model). codex itself accepts ANY -c service_tier string silently (probe-verified), so
|
|
185
|
+
# the wrapper validates the effective value: an unsupported one warns and runs on the standard
|
|
186
|
+
# tier — a typo can never silently masquerade as Fast.
|
|
187
|
+
CODEX_SERVICE_TIER="${CODEX_SERVICE_TIER:-}"
|
|
188
|
+
if [[ -n "$CODEX_SERVICE_TIER" ]] && ! aw_settings_valid CODEX_SERVICE_TIER "$CODEX_SERVICE_TIER"; then
|
|
189
|
+
echo "warning: CODEX_SERVICE_TIER='$CODEX_SERVICE_TIER' is not a supported service tier ('priority') — running on the standard tier." >&2
|
|
190
|
+
CODEX_SERVICE_TIER=""
|
|
191
|
+
fi
|
|
192
|
+
tier_flags=()
|
|
193
|
+
if [[ -n "$CODEX_SERVICE_TIER" ]]; then
|
|
194
|
+
tier_flags=(-c "service_tier=$CODEX_SERVICE_TIER")
|
|
195
|
+
fi
|
|
85
196
|
CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
|
|
86
197
|
|
|
87
198
|
# --- Quality-first guard: refuse a silent model/effort downgrade ---------------
|
|
@@ -371,6 +482,7 @@ if [[ -n "$resume_mode" ]]; then
|
|
|
371
482
|
-c sandbox_workspace_write.network_access=false
|
|
372
483
|
-c hide_agent_reasoning=true
|
|
373
484
|
-c model_reasoning_summary=none
|
|
485
|
+
"${tier_flags[@]+"${tier_flags[@]}"}"
|
|
374
486
|
-)
|
|
375
487
|
full_prompt="$RESUME_REMINDER"$'\n\n'"$task"
|
|
376
488
|
else
|
|
@@ -386,6 +498,7 @@ else
|
|
|
386
498
|
-c model_reasoning_effort="$CODEX_EFFORT"
|
|
387
499
|
-c hide_agent_reasoning=true
|
|
388
500
|
-c model_reasoning_summary=none
|
|
501
|
+
"${tier_flags[@]+"${tier_flags[@]}"}"
|
|
389
502
|
--color never
|
|
390
503
|
-o "$out"
|
|
391
504
|
--json
|