@sabaiway/agent-workflow-kit 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.md +1 -0
- package/SKILL.md +5 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-await-guard.test.mjs +176 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +61 -14
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +606 -467
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +42 -4
- package/bridges/codex-cli-bridge/SKILL.md +18 -5
- package/bridges/codex-cli-bridge/bin/codex-await-guard.test.mjs +161 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +22 -17
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +356 -363
- package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +275 -286
- package/bridges/codex-cli-bridge/capability.json +1 -1
- package/bridges/codex-cli-bridge/references/driving-codex.md +4 -2
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +3 -2
- package/bridges/codex-cli-bridge/setup/README.md +3 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/mcp.md +37 -0
- package/references/modes/recommendations.md +1 -0
- package/references/modes/uninstall.md +2 -1
- package/tools/commands.mjs +7 -0
- package/tools/direct-run.mjs +3 -0
- package/tools/doc-parity.mjs +18 -2
- package/tools/mcp-registration.mjs +283 -0
- package/tools/mcp-server.mjs +314 -0
- package/tools/mcp-stdio.mjs +229 -0
- package/tools/mcp.mjs +299 -0
- package/tools/recommendations.mjs +90 -1
- package/tools/uninstall.mjs +356 -45
|
@@ -61,15 +61,31 @@ the operator can split.
|
|
|
61
61
|
of the change set. Only BODIES concatenate, and they concatenate **byte-for-byte**: nothing the
|
|
62
62
|
wrapper adds ever enters the reviewed artifact, and the receipt's fingerprint domain is untouched.
|
|
63
63
|
- **Delivery is proven, never assumed.** After assembly the wrapper picks, per part, an interior line
|
|
64
|
-
the model cannot anticipate, and asks for it **by address only**. The
|
|
65
|
-
|
|
64
|
+
the model cannot anticipate, and asks for it **by address only**. The candidate walk starts at the
|
|
65
|
+
part's HEAD, which MINIMIZES the address rather than bounding it — the model is denied every tool
|
|
66
|
+
and must count by reading, and a midpoint address it cannot count to is a false refusal on a
|
|
67
|
+
correctly delivered change set.
|
|
68
|
+
A line the ASSEMBLER emits — one of its own `=== repo file map / git status / staged diff /
|
|
69
|
+
unstaged diff / untracked … ===` banners, per-path forms included — is never a candidate (with one
|
|
70
|
+
stated exception below). The change set's OWN banner-shaped lines stay admissible: refusing the
|
|
71
|
+
shape rather than the vocabulary would discard real content and re-earn the refusal this
|
|
72
|
+
design removes.
|
|
73
|
+
Nor is a line a model could **derive** without the part it belongs to. The repo-map and status
|
|
74
|
+
blocks travel in part 1 and name every path, so those blocks and all git diff METADATA (the
|
|
75
|
+
`diff --git`, `@@`, `index`, mode, rename and copy headers) would prove nothing about a LATER part.
|
|
76
|
+
They are sliced out of the assembled change set — its own bytes, never recomputed, so a rename's
|
|
77
|
+
headers and git's quoting of an awkward name are covered without guessing what git wrote — and fed
|
|
78
|
+
to the same non-body filter. Untracked file CONTENTS are deliberately NOT in that set: nothing in
|
|
79
|
+
part 1 reveals them, so they stay the proof material this lane depends on.
|
|
80
|
+
The mandated shape gains `### Delivery proof` as its **FIRST** section, so output truncation cannot
|
|
81
|
+
silently drop it:
|
|
66
82
|
|
|
67
83
|
```text
|
|
68
84
|
### Delivery proof
|
|
69
85
|
part <K> line <L>: <the text of line L of part K, VERBATIM>
|
|
70
86
|
Requested addresses, one per line:
|
|
71
|
-
part 1 line
|
|
72
|
-
part 2 line
|
|
87
|
+
part 1 line 16
|
|
88
|
+
part 2 line 2
|
|
73
89
|
### Verdict
|
|
74
90
|
…
|
|
75
91
|
```
|
|
@@ -105,6 +121,28 @@ itself can never reveal the very text it asks for, and the wrapper needs no seco
|
|
|
105
121
|
- A change set whose parts carry **no unique interior line** in the 24..200-byte window (a single
|
|
106
122
|
huge minified line, for instance) cannot be proven delivered, so the wrapper **refuses** rather
|
|
107
123
|
than reviewing unprovably. Split the review, or exclude the blob.
|
|
124
|
+
- The head-first walk **minimizes** the requested address; it does not BOUND it. A part whose head is
|
|
125
|
+
a long run of unusable lines — too short, not unique, or an assembler banner — still yields a large
|
|
126
|
+
address, and the miscount case above returns with it. No runtime maximum is enforced: refusing on
|
|
127
|
+
address size would trade a false refusal for a different false refusal.
|
|
128
|
+
- The banner reject matches the assembler's **vocabulary**, so a change set carrying a line like
|
|
129
|
+
`=== untracked notes ===` of its own loses that one candidate. Harmless while any other line
|
|
130
|
+
qualifies; the walk simply continues.
|
|
131
|
+
- The derivable set is computed **once for the whole change set**, not per part, and the partition
|
|
132
|
+
cuts by BYTES — so the repo-map and status blocks are not guaranteed to sit in part 1. A change set
|
|
133
|
+
with thousands of changed paths can put a whole part inside the status block, whose every line is
|
|
134
|
+
then excluded, and that part refuses for want of a candidate. The failure is **fail-closed** — a
|
|
135
|
+
named refusal telling the operator to split the review, never an unsound proof — and the repo map is
|
|
136
|
+
already budget-truncated, so only an unbounded status block reaches it. A per-part exclusion would
|
|
137
|
+
have to carry derivation provenance (which path was named where) to avoid re-opening the leak it
|
|
138
|
+
exists to close; that is queued, not smuggled in here.
|
|
139
|
+
- The reject is by vocabulary, **not exact by origin**. An untracked path containing a literal
|
|
140
|
+
NEWLINE splits its own `=== untracked: <p> ===` banner across lines, and the tail fragment
|
|
141
|
+
(`<rest> ===`) does not carry the vocabulary, so it stays admissible — while a model holding
|
|
142
|
+
part 1 could rebuild it from the quoted git-status entry there. The exposure is strictly SMALLER
|
|
143
|
+
than before the reject existed (the whole banner line was admissible then) and needs a newline in
|
|
144
|
+
a filename; closing it exactly means escaping the dynamic field, which changes the assembled
|
|
145
|
+
artifact BYTES and so must move in both bridges together under the fingerprint-parity contract.
|
|
108
146
|
|
|
109
147
|
## agy's own permission ask — surfaced, never applied
|
|
110
148
|
|
|
@@ -48,12 +48,25 @@ this skill. Both wrappers enforce the subscription path before invoking codex:
|
|
|
48
48
|
|
|
49
49
|
## Models quality-first pinned
|
|
50
50
|
|
|
51
|
-
Delegated codex work ALWAYS runs on the **
|
|
51
|
+
Delegated codex work ALWAYS runs on the **PINNED model at maximum reasoning effort**: the wrappers
|
|
52
52
|
**pin** `gpt-5.6-sol` / `xhigh` and **refuse** (exit 2, loud) a non-default `CODEX_MODEL` / `CODEX_EFFORT`
|
|
53
|
-
— knowingly-worse output is never traded for quota. The pin is deliberate
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
— knowingly-worse output is never traded for quota. The pin is deliberate: an explicit `-m gpt-5.6-sol`
|
|
54
|
+
names one model rather than following the CLI's current default. That the pinned id is also the
|
|
55
|
+
*strongest* selectable Codex model is a **separate, time-bounded claim**, checked BY HAND against
|
|
56
|
+
<https://developers.openai.com/codex/models>. There is **no automated gate and this file records no
|
|
57
|
+
date for that check**, so treat "strongest" as unverified until someone looks. Upstream can move
|
|
58
|
+
either way between releases:
|
|
59
|
+
- **retirement** is loud — the pinned id stops being served and codex fails with an error naming the
|
|
60
|
+
model. That is an upstream failure, not a wrapper refusal; the fix is a pin bump in its own reviewed
|
|
61
|
+
commit, never a `CODEX_PROBE=1` run, whose output is never delegated work.
|
|
62
|
+
- **a newer, stronger model is SILENT** — nothing fails, and the pin simply stops being the best
|
|
63
|
+
available until someone re-checks the page.
|
|
64
|
+
|
|
65
|
+
The guard is therefore a **pin-integrity** guard, not a quality guarantee: it enforces that runs use
|
|
66
|
+
the id named here, and it refuses a *stronger* model exactly as it refuses a weaker one. Read a
|
|
67
|
+
refusal as "not the pinned id", never as "a downgrade was prevented".
|
|
68
|
+
|
|
69
|
+
Economy comes only from **quality-neutral waste removal** (clean capture, a hard timeout,
|
|
57
70
|
a precomputed review diff, `resume` instead of re-sending context), never from a downgrade.
|
|
58
71
|
|
|
59
72
|
The ONLY escape is a **throwaway probe** whose result is effort-independent (a reachability / smoke
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// codex-await-guard.test.mjs — the source-level guard that makes this bridge's asynchronous
|
|
2
|
+
// dispatch lane self-checking. Twin of the antigravity bridge's guard; a bridge dir ships WHOLE and
|
|
3
|
+
// is placed on its own, so neither guard can read the other's suite and the rule lives once per
|
|
4
|
+
// bundle rather than once per family.
|
|
5
|
+
//
|
|
6
|
+
// Why it exists. Both suites drive a bash wrapper, and every dispatch used to be a blocking
|
|
7
|
+
// `spawnSync` that pinned the file to one core. The dispatches are awaited now so a describe can
|
|
8
|
+
// declare concurrency — which turns a MISSING `await` into a real hazard.
|
|
9
|
+
//
|
|
10
|
+
// The tempting claim is that a missing `await` announces itself, because an assertion against a
|
|
11
|
+
// Promise never matches a status. That claim is FALSE at the sites where the dispatch's RESULT IS
|
|
12
|
+
// DISCARDED (`run(sb, {...});` as a bare statement, the capture read on the next line). Un-awaited,
|
|
13
|
+
// such a test reads the fixture before the wrapper has written it and tears the sandbox down under
|
|
14
|
+
// a live child. Nothing in the suites can catch that; only the source can.
|
|
15
|
+
//
|
|
16
|
+
// It is a separate FILE rather than a describe inside a suite because the repo's survivor-corpus
|
|
17
|
+
// checker (`scripts/suite-parity.mjs`) binds each SURVIVING file's assert-call-site COUNT and
|
|
18
|
+
// `--accept-rewrites` exempts only the assertion-expression hash, never the count. A guard added to
|
|
19
|
+
// a suite would fail the very acceptance it exists to serve; a NEW file is counted separately.
|
|
20
|
+
//
|
|
21
|
+
// Dependency-free, Node >= 22. No side effects on import.
|
|
22
|
+
|
|
23
|
+
import { describe, it } from 'node:test';
|
|
24
|
+
import assert from 'node:assert/strict';
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const SUITES = Object.freeze(['codex-exec.test.mjs', 'codex-review.test.mjs']);
|
|
31
|
+
|
|
32
|
+
// A CALL, not a definition and not prose: the callee name, `(`, then an identifier or object start.
|
|
33
|
+
// A definition reads `const run = (`, and a comment's `run()` carries no argument, so neither
|
|
34
|
+
// matches. Keying on the ARGUMENT NAME instead would be a trap — the sandboxes are called `sb`,
|
|
35
|
+
// `single`, `nested`, and a pattern pinned to one name silently skips the rest.
|
|
36
|
+
const DISPATCH_CALL = /\b(?:run|runAsync)\(\s*[A-Za-z_${]/g;
|
|
37
|
+
|
|
38
|
+
// A call site is DISCHARGED when the code immediately before it hands the promise on. Two rules,
|
|
39
|
+
// and they are NOT the same rule — reading the raw prefix for either keyword is a false green in
|
|
40
|
+
// the one guard that exists to prevent false greens:
|
|
41
|
+
// • `return` counts ONLY on the call's own line. ASI ends a bare `return` at the newline, so
|
|
42
|
+
// `return\n run(...)` returns undefined and leaves the dispatch unheld.
|
|
43
|
+
// • `await` and an arrow head may sit on an earlier line, so those are read across newlines —
|
|
44
|
+
// but only after TRAILING PROSE is dropped, because a comment line ending in the word "await"
|
|
45
|
+
// or "return" looks exactly like the keyword to a suffix test.
|
|
46
|
+
// The keyword must come from CODE. A line-oriented prose filter is not enough — `previous(); //
|
|
47
|
+
// await` is a code line whose tail is a comment — so comment and string bodies are masked to
|
|
48
|
+
// spaces in one pass first. Newlines survive the mask so the per-line `return` rule still sees
|
|
49
|
+
// them. Not a parser: it tracks the states a JS prefix can be in, and every ambiguity it cannot
|
|
50
|
+
// resolve leaves MORE text masked, which can only make the guard stricter.
|
|
51
|
+
// Mask ONE line: a comment tail and every string body become spaces, so no keyword can come from
|
|
52
|
+
// prose. Per LINE on purpose — a whole-file scan accumulates state, and one quote inside a regex
|
|
53
|
+
// leaves it "in a string" for the rest of the file (measured: 29 real call sites reported as
|
|
54
|
+
// un-awaited). A `'`/`"` string cannot span a newline and neither can a `//` comment, so a
|
|
55
|
+
// line-scoped mask resynchronises at every newline and cannot desync at all.
|
|
56
|
+
// Stated residual: a line INSIDE a multi-line template literal or block comment is read as code.
|
|
57
|
+
export const maskLine = (line) => {
|
|
58
|
+
if (/^\s*\*/.test(line)) return ' '.repeat(line.length);
|
|
59
|
+
const out = [...line];
|
|
60
|
+
let quote = null;
|
|
61
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
62
|
+
const ch = line[i];
|
|
63
|
+
if (quote === null) {
|
|
64
|
+
if (ch === '/' && line[i + 1] === '/') { for (let j = i; j < line.length; j += 1) out[j] = ' '; break; }
|
|
65
|
+
if (ch === "'" || ch === '"' || ch === '`') quote = ch;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (ch === '\\') { out[i] = ' '; if (i + 1 < line.length) out[i + 1] = ' '; i += 1; continue; }
|
|
69
|
+
if (ch === quote) { quote = null; continue; }
|
|
70
|
+
out[i] = ' ';
|
|
71
|
+
}
|
|
72
|
+
return out.join('');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Two rules, and they are NOT the same rule:
|
|
76
|
+
// • `return` counts ONLY on the call's own line — ASI ends a bare `return` at the newline, so
|
|
77
|
+
// `return\n run(...)` returns undefined and leaves the dispatch unheld;
|
|
78
|
+
// • `await` and an arrow head may sit on an earlier line, so the walk skips blank and
|
|
79
|
+
// fully-masked lines to find the nearest line that carries code.
|
|
80
|
+
// Neither keyword counts when it is part of a longer name or a member access (`x.await`).
|
|
81
|
+
const RETURNED_HERE = /(?<![.$\w])return[ \t]*$/;
|
|
82
|
+
const HANDED_ON = /(?:(?<![.$\w])await|=>)\s*$/;
|
|
83
|
+
|
|
84
|
+
export const isDischarged = (rawPrefix) => {
|
|
85
|
+
const lines = rawPrefix.split('\n').map(maskLine);
|
|
86
|
+
if (RETURNED_HERE.test(lines[lines.length - 1])) return true;
|
|
87
|
+
let at = lines.length - 1;
|
|
88
|
+
while (at >= 0 && /^\s*$/.test(lines[at])) at -= 1;
|
|
89
|
+
return at >= 0 && HANDED_ON.test(lines[at]);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const dispatchSites = (source) => {
|
|
93
|
+
const sites = [];
|
|
94
|
+
for (const match of source.matchAll(DISPATCH_CALL)) {
|
|
95
|
+
const before = source.slice(0, match.index);
|
|
96
|
+
sites.push({
|
|
97
|
+
line: before.split('\n').length,
|
|
98
|
+
call: match[0],
|
|
99
|
+
discharged: isDischarged(before),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return sites;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
describe('codex bridge suites — every wrapper dispatch is awaited (concurrency guard)', () => {
|
|
106
|
+
// A guard that matches nothing passes for the wrong reason. The two suites carried 262 dispatch
|
|
107
|
+
// call sites when this guard was written; the floor sits well below that so ordinary editing does
|
|
108
|
+
// not trip it, and far above zero so a broken pattern cannot read as "all clear".
|
|
109
|
+
it('the guard really finds the suite dispatch sites (never a vacuous pass)', () => {
|
|
110
|
+
const total = SUITES.reduce((n, rel) => n + dispatchSites(readFileSync(join(HERE, rel), 'utf8')).length, 0);
|
|
111
|
+
assert.ok(total >= 200, `only ${total} dispatch call site(s) matched — the pattern stopped seeing the suites`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('no dispatch call site is left un-awaited', () => {
|
|
115
|
+
const loose = SUITES.flatMap((rel) => dispatchSites(readFileSync(join(HERE, rel), 'utf8'))
|
|
116
|
+
.filter((site) => !site.discharged)
|
|
117
|
+
.map((site) => `${rel}:${site.line} ${site.call}`));
|
|
118
|
+
assert.deepEqual(loose, [], 'each listed site starts a dispatch whose promise nothing holds — prefix it with await');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// The predicate must be able to REPORT a violation, or the green above proves nothing.
|
|
122
|
+
it('the predicate reports a bare dispatch and accepts a discharged one', () => {
|
|
123
|
+
assert.deepEqual(dispatchSites(' run(sb, { args: [] });\n').map((s) => s.discharged), [false]);
|
|
124
|
+
assert.deepEqual(
|
|
125
|
+
dispatchSites(' const r = await run(sb, {});\n return runAsync(nested);\n const f = (sb) =>\n run(sb, {});\n').map((s) => s.discharged),
|
|
126
|
+
[true, true, true],
|
|
127
|
+
);
|
|
128
|
+
// The three shapes that LOOK discharged to a prefix test and are not. Each fixture really ends
|
|
129
|
+
// in the keyword — a fixture whose comment trails off into another word tests nothing.
|
|
130
|
+
assert.deepEqual(
|
|
131
|
+
dispatchSites(' // do not await\n run(sb, {});\n').map((s) => s.discharged),
|
|
132
|
+
[false],
|
|
133
|
+
'a comment ending in await is prose, not a discharge',
|
|
134
|
+
);
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
dispatchSites(' // early return\n run(sb, {});\n').map((s) => s.discharged),
|
|
137
|
+
[false],
|
|
138
|
+
'a comment ending in return is prose, not a discharge',
|
|
139
|
+
);
|
|
140
|
+
assert.deepEqual(
|
|
141
|
+
dispatchSites(' return\n run(sb, {});\n').map((s) => s.discharged),
|
|
142
|
+
[false],
|
|
143
|
+
'ASI ends a bare return at the newline, so the next call is NOT returned',
|
|
144
|
+
);
|
|
145
|
+
assert.deepEqual(
|
|
146
|
+
dispatchSites(' previous(); // await\n run(sb, {});\n').map((s) => s.discharged),
|
|
147
|
+
[false],
|
|
148
|
+
'a CODE line whose tail is a comment ending in await is still prose',
|
|
149
|
+
);
|
|
150
|
+
assert.deepEqual(
|
|
151
|
+
dispatchSites(' const x = thing.await\n run(sb, {});\n').map((s) => s.discharged),
|
|
152
|
+
[false],
|
|
153
|
+
'a member access named await is not the operator',
|
|
154
|
+
);
|
|
155
|
+
assert.deepEqual(
|
|
156
|
+
dispatchSites(" const s = 'await ';\n run(sb, {});\n").map((s) => s.discharged),
|
|
157
|
+
[false],
|
|
158
|
+
'a string whose body ends in await is not the operator',
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
# - network access OFF: new dependencies / network installs are done by a human
|
|
14
14
|
# - approval_policy=never: there is no TTY in exec; anything needing escalation
|
|
15
15
|
# is refused and reported, then handled by hand
|
|
16
|
-
# -
|
|
16
|
+
# - the PINNED model at maximum reasoning effort (quality-first — see below)
|
|
17
17
|
# - git WRITES are blocked by a physical shim (codex spawns git via execve, which
|
|
18
18
|
# bypasses shell functions) — the orchestrator owns the commit boundary.
|
|
19
19
|
#
|
|
20
|
-
# Quality-first (hard rule): delegated codex work ALWAYS runs on the
|
|
20
|
+
# Quality-first (hard rule): delegated codex work ALWAYS runs on the PINNED
|
|
21
21
|
# model at maximum reasoning effort. The defaults below are pinned and the wrapper
|
|
22
22
|
# REFUSES a non-default CODEX_MODEL/CODEX_EFFORT — knowingly-worse output is never
|
|
23
23
|
# traded for quota. The ONLY exception is a throwaway probe whose result does not
|
|
@@ -319,7 +319,7 @@ aw_resolve_timeout_bin() {
|
|
|
319
319
|
printf '%s' "$bin"
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
DEFAULT_CODEX_MODEL="gpt-5.6-sol" #
|
|
322
|
+
DEFAULT_CODEX_MODEL="gpt-5.6-sol" # pinned model id (see SKILL.md: strongest-model status is hand-checked, ungated)
|
|
323
323
|
DEFAULT_CODEX_EFFORT="xhigh" # maximum reasoning effort — pinned
|
|
324
324
|
CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
|
|
325
325
|
CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
|
|
@@ -378,16 +378,16 @@ AW_BRIDGE_VERSION="3.5.0" # aw-version-anchor
|
|
|
378
378
|
# actually applied.
|
|
379
379
|
CODEX_KILL_GRACE_S=15
|
|
380
380
|
|
|
381
|
-
# --- Quality-first guard: refuse
|
|
382
|
-
# Real delegated runs must use the
|
|
381
|
+
# --- Quality-first guard: refuse any non-pinned model/effort ---------------
|
|
382
|
+
# Real delegated runs must use the pinned model at max effort. A throwaway probe
|
|
383
383
|
# (effort-independent result) may opt out with CODEX_PROBE=1, announced loudly.
|
|
384
384
|
if [[ "${CODEX_PROBE:-}" == "1" ]]; then
|
|
385
385
|
echo "warning: CODEX_PROBE=1 — THROWAWAY PROBE MODE. Quality guards relaxed; do NOT use this run's" >&2
|
|
386
386
|
echo " output as real delegated work (model='$CODEX_MODEL' effort='$CODEX_EFFORT')." >&2
|
|
387
387
|
else
|
|
388
388
|
if [[ "$CODEX_MODEL" != "$DEFAULT_CODEX_MODEL" ]]; then
|
|
389
|
-
echo "error: CODEX_MODEL='$CODEX_MODEL' is not the pinned
|
|
390
|
-
echo " Delegated codex work must run on the
|
|
389
|
+
echo "error: CODEX_MODEL='$CODEX_MODEL' is not the pinned model '$DEFAULT_CODEX_MODEL'." >&2
|
|
390
|
+
echo " Delegated codex work must run on the pinned model at max effort (quality-first)." >&2
|
|
391
391
|
echo " For a throwaway probe whose result is effort-independent, set CODEX_PROBE=1." >&2
|
|
392
392
|
exit 2
|
|
393
393
|
fi
|
|
@@ -623,7 +623,7 @@ else
|
|
|
623
623
|
# (1) ALWAYS rejected — they would defeat the subscription / sandbox / approval /
|
|
624
624
|
# config-isolation policy (-c/-s/--full-auto/bypass), switch the provider off
|
|
625
625
|
# the subscription (--oss/--local-provider), load alternate config (-p/--profile),
|
|
626
|
-
# override the pinned
|
|
626
|
+
# override the pinned model (-m), or break the wrapper-owned clean
|
|
627
627
|
# output / session capture (-o/--json/--color/--output-schema/--ephemeral).
|
|
628
628
|
# CODEX_PROBE=1 NEVER relaxes these: a probe still runs on the subscription, in
|
|
629
629
|
# the sandbox, with clean capture; its model is chosen via CODEX_MODEL, not -m.
|
|
@@ -634,7 +634,7 @@ else
|
|
|
634
634
|
case "$_arg" in
|
|
635
635
|
-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*)
|
|
636
636
|
echo "error: passthrough flag '$_arg' is not allowed — it would defeat the subscription / sandbox /" >&2
|
|
637
|
-
echo " approval / config-isolation policy, the pinned
|
|
637
|
+
echo " approval / config-isolation policy, the pinned model, or the clean output/session" >&2
|
|
638
638
|
echo " capture. It stays blocked even under CODEX_PROBE=1. Invoke 'codex' directly if you must." >&2
|
|
639
639
|
exit 2
|
|
640
640
|
;;
|
|
@@ -1146,12 +1146,16 @@ aw_ns_is_string_content() {
|
|
|
1146
1146
|
# a hostile parent environment): the trace's only non-CLI content is plain stderr, which does not
|
|
1147
1147
|
# start with `{`, and a tool call's own output is JSON-escaped into a string and cannot inject
|
|
1148
1148
|
# structure. Anything not matching the walk is "not evidence" — never an error, never a stop.
|
|
1149
|
-
#
|
|
1150
|
-
#
|
|
1151
|
-
#
|
|
1152
|
-
#
|
|
1149
|
+
# Slice costs are MEASURED, never assumed — a real tool call's aggregated_output reaches 200KB.
|
|
1150
|
+
# `${var%%<long>*}` and a prefix removal whose PATTERN is a huge variable both hang the wrapper
|
|
1151
|
+
# outright at that size. A SHORT-pattern `#*` cut is cheap only while the delimiter sits NEAR the
|
|
1152
|
+
# start: bash tries each prefix in turn, so the same cut costs 20.7s when the delimiter sits at
|
|
1153
|
+
# the far end of a 200KB value. That is exactly where `","exit_code":` sits, so that one cut
|
|
1154
|
+
# reads the delimiter's first-match offset from ONE linear grep pass instead (8ms, same slices).
|
|
1155
|
+
# The whole walk runs in byte semantics for it: grep reports BYTES and the slices index by them.
|
|
1153
1156
|
aw_ns_item_evidence() {
|
|
1154
|
-
local
|
|
1157
|
+
local LC_ALL=C
|
|
1158
|
+
local line="$1" d1='","aggregated_output":"' d2='","exit_code":' rest tail cmd agg code after off
|
|
1155
1159
|
case "$line" in '{'*) ;; *) return 1 ;; esac
|
|
1156
1160
|
rest="${line#*'"type":"command_execution","command":"'}"
|
|
1157
1161
|
if [[ "$rest" == "$line" ]]; then return 1; fi
|
|
@@ -1159,9 +1163,10 @@ aw_ns_item_evidence() {
|
|
|
1159
1163
|
if [[ "$tail" == "$rest" ]]; then return 1; fi
|
|
1160
1164
|
cmd="${rest:0:$(( ${#rest} - ${#tail} - ${#d1} ))}"
|
|
1161
1165
|
aw_ns_is_string_content "$cmd" || return 1
|
|
1162
|
-
|
|
1163
|
-
if [[
|
|
1164
|
-
agg="${tail:0
|
|
1166
|
+
while IFS=: read -r off _; do break; done < <(grep -aboF -- "$d2" <<<"$tail")
|
|
1167
|
+
if [[ -z "$off" ]]; then return 1; fi
|
|
1168
|
+
agg="${tail:0:off}"
|
|
1169
|
+
after="${tail:$(( off + ${#d2} ))}"
|
|
1165
1170
|
aw_ns_is_string_content "$agg" || return 1
|
|
1166
1171
|
code="${after%%,*}"
|
|
1167
1172
|
if [[ "$code" =~ ^-?[0-9]+$ && "$code" != "0" ]]; then
|