agent-bios 0.1.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/DEPENDENCIES.md +89 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/claude/CLAUDE.md +138 -0
- package/claude/guides/cli-multi-model-workflow.md +194 -0
- package/claude/guides/coding-staged-workflow.md +70 -0
- package/claude/guides/implementation-map.md +34 -0
- package/claude/guides/llm-capability-boundary-examples.md +123 -0
- package/claude/guides/llm-capability-boundary-patterns.md +339 -0
- package/claude/guides/llm-capability-boundary.md +255 -0
- package/claude/guides/mock-realization-boundary.md +275 -0
- package/claude/guides/svg-visualization-guide.md +321 -0
- package/codex/AGENTS.md +139 -0
- package/codex/agents/frontier.toml +8 -0
- package/codex/agents/reviewer.toml +9 -0
- package/codex/agents/sweep.toml +9 -0
- package/codex/agents/workhorse.toml +8 -0
- package/codex/guides/cli-multi-model-workflow.md +194 -0
- package/codex/guides/coding-staged-workflow.md +70 -0
- package/codex/guides/implementation-map.md +34 -0
- package/codex/guides/llm-capability-boundary-examples.md +123 -0
- package/codex/guides/llm-capability-boundary-patterns.md +339 -0
- package/codex/guides/llm-capability-boundary.md +255 -0
- package/codex/guides/mock-realization-boundary.md +275 -0
- package/codex/guides/svg-visualization-guide.md +321 -0
- package/config/agent-launch.toml +94 -0
- package/package.json +54 -0
- package/scripts/agent-launch.py +1742 -0
- package/scripts/check-parity.sh +1703 -0
- package/scripts/codex-helm.sh +370 -0
- package/scripts/codex-run.sh +176 -0
- package/scripts/install.sh +310 -0
- package/scripts/provision-venv.sh +28 -0
- package/scripts/session-cost.py +106 -0
- package/shell/agent-launch.zsh +38 -0
|
@@ -0,0 +1,1703 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Parity gate for mirrored instruction files.
|
|
3
|
+
# EN canonical (installed): claude/, codex/. KO reference (never installed): ko/claude, ko/codex.
|
|
4
|
+
# Checks:
|
|
5
|
+
# (1) EN guide dirs mirror: claude/guides == codex/guides (modulo config-home var)
|
|
6
|
+
# (2) KO guide dirs mirror: ko/claude/guides == ko/codex/guides (modulo config-home var)
|
|
7
|
+
# (3) EN globals mirror: claude/CLAUDE.md == codex/AGENTS.md (modulo title, config-home, declared Codex authorization)
|
|
8
|
+
# (4) KO globals mirror: ko/claude/CLAUDE.md == ko/codex/AGENTS.md (same declared exception)
|
|
9
|
+
# (5) EN and KO guide sets match — every English guide has a Korean counterpart
|
|
10
|
+
# (6) every guide referenced by claude/CLAUDE.md exists in both EN guide dirs
|
|
11
|
+
# (7) frontmatter: guide_id matches filename; language matches tree (ko/ => ko, else en); parent resolves
|
|
12
|
+
# (8) anchor phrases: each intentional global↔guide restatement pair shares a fixed anchor
|
|
13
|
+
# phrase in both EN files, so editing one side without the other fails here
|
|
14
|
+
# (9) launch-profile, Codex role-slot, and wrapper defaults match runtime projections
|
|
15
|
+
# Exit non-zero on any divergence. Run from the repo root; safe as a pre-commit hook.
|
|
16
|
+
set -u
|
|
17
|
+
cd "$(dirname "$0")/.."
|
|
18
|
+
fail=0
|
|
19
|
+
|
|
20
|
+
# Non-empty-subject guards: a parity check over missing inputs must fail, not pass vacuously.
|
|
21
|
+
for p in claude/guides codex/guides ko/claude/guides ko/codex/guides; do
|
|
22
|
+
[ -d "$p" ] || { echo "FAIL: required dir missing: $p"; exit 1; }
|
|
23
|
+
done
|
|
24
|
+
for p in claude/CLAUDE.md codex/AGENTS.md ko/claude/CLAUDE.md ko/codex/AGENTS.md; do
|
|
25
|
+
[ -f "$p" ] || { echo "FAIL: required file missing: $p"; exit 1; }
|
|
26
|
+
done
|
|
27
|
+
guide_count=$(ls claude/guides/*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
28
|
+
[ "$guide_count" -ge 1 ] || { echo "FAIL: no guides found in claude/guides"; exit 1; }
|
|
29
|
+
|
|
30
|
+
# Guide dirs mirror across CLIs modulo the config-home variable (both sides normalized).
|
|
31
|
+
mirror_guides() { # $1 = claude-side dir, $2 = codex-side dir
|
|
32
|
+
if ! diff <(ls "$1") <(ls "$2"); then
|
|
33
|
+
echo "FAIL: guide dirs hold different file sets: $1 vs $2"; fail=1
|
|
34
|
+
fi
|
|
35
|
+
for f in "$1"/*.md; do
|
|
36
|
+
name=$(basename "$f")
|
|
37
|
+
if ! diff <(sed 's|${CLAUDE_CONFIG_DIR:-$HOME/.claude}|__CONFIG__|g' "$f") \
|
|
38
|
+
<(sed 's|${CODEX_HOME:-$HOME/.codex}|__CONFIG__|g' "$2/$name") >/dev/null 2>&1; then
|
|
39
|
+
echo "FAIL: guide mirror diverges beyond config-home var: $2/$name"; fail=1
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
}
|
|
43
|
+
mirror_guides claude/guides codex/guides
|
|
44
|
+
mirror_guides ko/claude/guides ko/codex/guides
|
|
45
|
+
|
|
46
|
+
# Global files mirror across CLIs modulo title, config-home var, and the one
|
|
47
|
+
# Codex-only standing-authorization bullet required by the host trigger contract.
|
|
48
|
+
# The ko pair is reference-only but is held to the same bounded exception.
|
|
49
|
+
check_global_mirror() { # $1 = claude-side file, $2 = codex-side file
|
|
50
|
+
if ! diff <(sed -e 's|${CLAUDE_CONFIG_DIR:-$HOME/.claude}|__CONFIG__|g' -e '1s/.*/# GLOBAL/' "$1") \
|
|
51
|
+
<(sed -e 's|${CODEX_HOME:-$HOME/.codex}|__CONFIG__|g' -e '1s/.*/# GLOBAL/' -e '/^- Codex-only standing authorization:/d' "$2"); then
|
|
52
|
+
echo "FAIL: global files diverge beyond title/config-home/Codex-authorization exceptions: $1 vs $2"; fail=1
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
check_global_mirror claude/CLAUDE.md codex/AGENTS.md
|
|
56
|
+
check_global_mirror ko/claude/CLAUDE.md ko/codex/AGENTS.md
|
|
57
|
+
|
|
58
|
+
expected_codex_authorization='- Codex-only standing authorization: on root/main local tasks, ordinary subagent dispatch is authorized when the `When To Spawn` gates fire. Explicit no-fan-out wins. Delegated agents may re-delegate only when their role allows. This grants no destructive, remote, credential, install, OAuth, push, live-network-expanding, or broader-sandbox authority.'
|
|
59
|
+
expected_ko_codex_authorization='- Codex-only standing authorization: root/main local task에서 `When To Spawn` gate가 발동하면 ordinary subagent dispatch를 상시 허용한다. Explicit no-fan-out이 우선하며 delegated agent는 role이 허용할 때만 재위임한다. 이는 destructive, remote, credential, install, OAuth, push, live-network-expanding, broader-sandbox authority를 주지 않는다.'
|
|
60
|
+
grep -qxF -- "$expected_codex_authorization" codex/AGENTS.md || { echo "FAIL: exact Codex standing authorization missing or changed: codex/AGENTS.md"; fail=1; }
|
|
61
|
+
grep -qxF -- "$expected_ko_codex_authorization" ko/codex/AGENTS.md || { echo "FAIL: exact Codex standing authorization missing or changed: ko/codex/AGENTS.md"; fail=1; }
|
|
62
|
+
|
|
63
|
+
# EN/KO guide-set parity: every English guide has a Korean counterpart (same basenames).
|
|
64
|
+
if ! diff <(ls claude/guides) <(ls ko/claude/guides); then
|
|
65
|
+
echo "FAIL: EN and KO guide sets differ (a guide lacks its Korean version, or vice versa)"; fail=1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Every guide referenced from the global file must exist in both EN guide dirs.
|
|
69
|
+
refs=$(grep -o 'guides/[a-z0-9-]*\.md' claude/CLAUDE.md | sort -u)
|
|
70
|
+
[ -n "$refs" ] || { echo "FAIL: global file references no guides (extraction empty)"; fail=1; }
|
|
71
|
+
for ref in $refs; do
|
|
72
|
+
name=$(basename "$ref")
|
|
73
|
+
for dir in claude/guides codex/guides; do
|
|
74
|
+
[ -f "$dir/$name" ] || { echo "FAIL: $dir/$name referenced from a global file but missing"; fail=1; }
|
|
75
|
+
done
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
# Frontmatter gate: every guide declares guide_id/language; guide_id matches the
|
|
79
|
+
# filename, language matches the tree (files under ko/ are ko, else en), and any
|
|
80
|
+
# declared parent resolves to a sibling guide in the same dir.
|
|
81
|
+
for f in claude/guides/*.md codex/guides/*.md ko/claude/guides/*.md ko/codex/guides/*.md; do
|
|
82
|
+
name=$(basename "$f")
|
|
83
|
+
case "$f" in ko/*) want_lang=ko ;; *) want_lang=en ;; esac
|
|
84
|
+
if [ "$(head -1 "$f")" != "---" ]; then
|
|
85
|
+
echo "FAIL: missing YAML frontmatter: $f"; fail=1; continue
|
|
86
|
+
fi
|
|
87
|
+
fm=$(awk '/^---$/{n++; next} n==1{print} n>=2{exit}' "$f")
|
|
88
|
+
gid=$(printf '%s\n' "$fm" | awk -F': ' '$1=="guide_id"{print $2; exit}')
|
|
89
|
+
lang=$(printf '%s\n' "$fm" | awk -F': ' '$1=="language"{print $2; exit}')
|
|
90
|
+
parent=$(printf '%s\n' "$fm" | awk -F': ' '$1=="parent"{print $2; exit}')
|
|
91
|
+
[ "$name" = "$gid.md" ] || { echo "FAIL: guide_id does not match filename: $f (guide_id=$gid)"; fail=1; }
|
|
92
|
+
[ "$lang" = "$want_lang" ] || { echo "FAIL: language does not match tree: $f (language=$lang, want=$want_lang)"; fail=1; }
|
|
93
|
+
if [ -n "$parent" ] && [ ! -f "$(dirname "$f")/$parent.md" ]; then
|
|
94
|
+
echo "FAIL: parent guide missing: $f (parent=$parent)"; fail=1
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
# Anchor-phrase gate: the global↔guide restatement pairs kept on purpose share a
|
|
99
|
+
# fixed anchor phrase; a one-sided edit that drops or rewords the anchor fails here.
|
|
100
|
+
# Format: anchor|fileA|fileB (EN canonical only; ko is a translation, not checked).
|
|
101
|
+
while IFS='|' read -r anchor fa fb; do
|
|
102
|
+
[ -n "$anchor" ] || continue
|
|
103
|
+
for f in "$fa" "$fb"; do
|
|
104
|
+
grep -qF "$anchor" "$f" || { echo "FAIL: anchor phrase '$anchor' missing from $f (restatement pair drifted)"; fail=1; }
|
|
105
|
+
done
|
|
106
|
+
done <<'ANCHORS'
|
|
107
|
+
difficulty × blast radius|claude/CLAUDE.md|claude/guides/cli-multi-model-workflow.md
|
|
108
|
+
convergence heuristic by reviewer kind|claude/CLAUDE.md|claude/guides/cli-multi-model-workflow.md
|
|
109
|
+
code-level circuit breaker|claude/CLAUDE.md|claude/guides/cli-multi-model-workflow.md
|
|
110
|
+
current-state dashboard|claude/CLAUDE.md|claude/guides/implementation-map.md
|
|
111
|
+
Verification Menus|claude/CLAUDE.md|claude/guides/coding-staged-workflow.md
|
|
112
|
+
real Microsoft Excel engine|claude/CLAUDE.md|claude/guides/coding-staged-workflow.md
|
|
113
|
+
severity contract|README.md|claude/guides/coding-staged-workflow.md
|
|
114
|
+
ANCHORS
|
|
115
|
+
|
|
116
|
+
# The launcher's Textual preflight UI tests need the managed venv (textual).
|
|
117
|
+
# Provision it if missing; every non-UI check above runs under system python.
|
|
118
|
+
VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
|
|
119
|
+
if ! { [ -x "$VENV/bin/python" ] && "$VENV/bin/python" -c 'import textual' 2>/dev/null; }; then
|
|
120
|
+
echo "provisioning agent-launch venv for parity UI tests ($VENV) ..."
|
|
121
|
+
AGENT_LAUNCH_VENV="$VENV" bash "$(dirname "$0")/provision-venv.sh" >/dev/null 2>&1 || {
|
|
122
|
+
echo "FAIL: could not provision the textual venv required for UI parity tests"; exit 1; }
|
|
123
|
+
fi
|
|
124
|
+
export AGENT_LAUNCH_VENV="$VENV"
|
|
125
|
+
|
|
126
|
+
if ! "$VENV/bin/python" - <<'PY'
|
|
127
|
+
import pathlib
|
|
128
|
+
import importlib.util
|
|
129
|
+
import fcntl
|
|
130
|
+
import json
|
|
131
|
+
import re
|
|
132
|
+
import pty
|
|
133
|
+
import select
|
|
134
|
+
import signal
|
|
135
|
+
import struct
|
|
136
|
+
import subprocess
|
|
137
|
+
import sys
|
|
138
|
+
import os
|
|
139
|
+
import tempfile
|
|
140
|
+
import termios
|
|
141
|
+
import time
|
|
142
|
+
import tomllib
|
|
143
|
+
|
|
144
|
+
sys.dont_write_bytecode = True
|
|
145
|
+
|
|
146
|
+
fail = 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def mark_fail(message):
|
|
150
|
+
global fail
|
|
151
|
+
print(f"FAIL: {message}")
|
|
152
|
+
fail = 1
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
agent_dir = pathlib.Path("codex/agents")
|
|
156
|
+
launch_profile_path = pathlib.Path("config/agent-launch.toml")
|
|
157
|
+
launcher = pathlib.Path("scripts/agent-launch.py")
|
|
158
|
+
shell_init = pathlib.Path("shell/agent-launch.zsh")
|
|
159
|
+
|
|
160
|
+
for required in (launch_profile_path, launcher, shell_init):
|
|
161
|
+
if not required.is_file():
|
|
162
|
+
mark_fail(f"required launch asset missing: {required}")
|
|
163
|
+
|
|
164
|
+
for doc_path, required_phrases in {
|
|
165
|
+
pathlib.Path("README.md"): (
|
|
166
|
+
"Every arrow-key TUI selection screen",
|
|
167
|
+
"persistent settings hub",
|
|
168
|
+
"Start with these settings",
|
|
169
|
+
"Exit without launching",
|
|
170
|
+
"`b` is the back command",
|
|
171
|
+
),
|
|
172
|
+
pathlib.Path("ko/README.md"): (
|
|
173
|
+
"화살표 키 TUI의 모든 선택 화면",
|
|
174
|
+
"지속형 설정 허브",
|
|
175
|
+
"Start with these settings",
|
|
176
|
+
"Exit without launching",
|
|
177
|
+
"`b`가 뒤로가기 명령",
|
|
178
|
+
),
|
|
179
|
+
}.items():
|
|
180
|
+
doc_text = doc_path.read_text()
|
|
181
|
+
for phrase in required_phrases:
|
|
182
|
+
if phrase not in doc_text:
|
|
183
|
+
mark_fail(f"agent-launch documentation contract missing from {doc_path}: {phrase!r}")
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
launch_profile = tomllib.loads(launch_profile_path.read_text())
|
|
187
|
+
except Exception as exc:
|
|
188
|
+
mark_fail(f"launch profile does not parse: {launch_profile_path} ({exc})")
|
|
189
|
+
launch_profile = {}
|
|
190
|
+
|
|
191
|
+
expected_launch_tiers = {
|
|
192
|
+
"frontier": ("gpt-5.6-sol", "max"),
|
|
193
|
+
"helm": ("gpt-5.6-sol", "xhigh"),
|
|
194
|
+
"workhorse": ("gpt-5.6-terra", "high"),
|
|
195
|
+
"sweep": ("gpt-5.6-luna", "low"),
|
|
196
|
+
}
|
|
197
|
+
codex_launch_tiers = launch_profile.get("hosts", {}).get("codex", {}).get("tiers", {})
|
|
198
|
+
if set(codex_launch_tiers) != set(expected_launch_tiers):
|
|
199
|
+
mark_fail("launch profile Codex tiers must be exactly frontier/helm/workhorse/sweep")
|
|
200
|
+
for tier, (model, effort) in expected_launch_tiers.items():
|
|
201
|
+
binding = codex_launch_tiers.get(tier, {})
|
|
202
|
+
if (binding.get("model"), binding.get("effort")) != (model, effort):
|
|
203
|
+
mark_fail(f"launch profile Codex {tier} is {binding!r}, want {model}/{effort}")
|
|
204
|
+
|
|
205
|
+
expected_claude_tiers = {
|
|
206
|
+
"frontier": ("claude-fable-5", "max"),
|
|
207
|
+
"helm": ("claude-opus-4-8", "xhigh"),
|
|
208
|
+
"workhorse": ("claude-sonnet-5", "high"),
|
|
209
|
+
"sweep": ("claude-haiku-4-5", "low"),
|
|
210
|
+
}
|
|
211
|
+
claude_launch_tiers = launch_profile.get("hosts", {}).get("claude", {}).get("tiers", {})
|
|
212
|
+
if set(claude_launch_tiers) != set(expected_claude_tiers):
|
|
213
|
+
mark_fail("launch profile Claude tiers must be exactly frontier/helm/workhorse/sweep")
|
|
214
|
+
for tier, (model, effort) in expected_claude_tiers.items():
|
|
215
|
+
binding = claude_launch_tiers.get(tier, {})
|
|
216
|
+
if (binding.get("model"), binding.get("effort")) != (model, effort):
|
|
217
|
+
mark_fail(f"launch profile Claude {tier} is {binding!r}, want {model}/{effort}")
|
|
218
|
+
|
|
219
|
+
balanced = launch_profile.get("presets", {}).get("balanced", {})
|
|
220
|
+
if (balanced.get("main_tier"), balanced.get("review_setup")) != ("helm", "native-panel"):
|
|
221
|
+
mark_fail("balanced launch preset must start HELM with native-panel review setup")
|
|
222
|
+
deep_review = launch_profile.get("presets", {}).get("deep-review", {})
|
|
223
|
+
deep_frontier = deep_review.get("frontier_effort", {})
|
|
224
|
+
if (
|
|
225
|
+
deep_review.get("main_tier") != "helm"
|
|
226
|
+
or deep_frontier.get("codex") != "ultra"
|
|
227
|
+
or deep_frontier.get("claude") != "max"
|
|
228
|
+
):
|
|
229
|
+
mark_fail("deep-review must keep HELM main, allow Codex FRONTIER Ultra, and cap Claude at max")
|
|
230
|
+
|
|
231
|
+
expected_agents = {
|
|
232
|
+
"frontier.toml": ("frontier", "gpt-5.6-sol", None),
|
|
233
|
+
"workhorse.toml": ("workhorse", "gpt-5.6-terra", "high"),
|
|
234
|
+
"sweep.toml": ("sweep", "gpt-5.6-luna", "low"),
|
|
235
|
+
"reviewer.toml": ("reviewer", "gpt-5.6-terra", "high"),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
missing_agents = sorted(set(expected_agents) - {path.name for path in agent_dir.glob("*.toml")}) if agent_dir.is_dir() else sorted(expected_agents)
|
|
239
|
+
if missing_agents:
|
|
240
|
+
mark_fail(f"required Codex agent files missing: {', '.join(missing_agents)}")
|
|
241
|
+
|
|
242
|
+
for filename, (expected_name, expected_model, expected_effort) in expected_agents.items():
|
|
243
|
+
path = agent_dir / filename
|
|
244
|
+
if not path.is_file():
|
|
245
|
+
continue
|
|
246
|
+
try:
|
|
247
|
+
data = tomllib.loads(path.read_text())
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
mark_fail(f"agent TOML does not parse: {path} ({exc})")
|
|
250
|
+
continue
|
|
251
|
+
if data.get("name") != expected_name:
|
|
252
|
+
mark_fail(f"{path} name is {data.get('name')!r}, want {expected_name!r}")
|
|
253
|
+
for required_text_field in ("description", "developer_instructions"):
|
|
254
|
+
value = data.get(required_text_field)
|
|
255
|
+
if not isinstance(value, str) or not value.strip():
|
|
256
|
+
mark_fail(f"{path} requires non-empty {required_text_field}")
|
|
257
|
+
if data.get("model") != expected_model:
|
|
258
|
+
mark_fail(f"{path} model is {data.get('model')!r}, want {expected_model!r}")
|
|
259
|
+
if expected_effort is None and "model_reasoning_effort" in data:
|
|
260
|
+
mark_fail(f"{path} must omit model_reasoning_effort so HELM can pin task-fit effort per dispatch")
|
|
261
|
+
elif expected_effort is not None and data.get("model_reasoning_effort") != expected_effort:
|
|
262
|
+
mark_fail(
|
|
263
|
+
f"{path} effort is {data.get('model_reasoning_effort')!r}, "
|
|
264
|
+
f"want {expected_effort!r}"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def table_row(path, slot):
|
|
269
|
+
for raw_line in path.read_text().splitlines():
|
|
270
|
+
line = raw_line.strip()
|
|
271
|
+
if not line.startswith("|") or not line.endswith("|"):
|
|
272
|
+
continue
|
|
273
|
+
cells = [cell.strip() for cell in line.strip("|").split("|")]
|
|
274
|
+
if cells and cells[0] == slot:
|
|
275
|
+
return cells
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def has_exact_model(text, model):
|
|
280
|
+
return re.search(rf"(?<![\w.-]){re.escape(model)}(?![\w.-])", text) is not None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
for guide_name in [
|
|
284
|
+
"claude/guides/cli-multi-model-workflow.md",
|
|
285
|
+
"codex/guides/cli-multi-model-workflow.md",
|
|
286
|
+
"ko/claude/guides/cli-multi-model-workflow.md",
|
|
287
|
+
"ko/codex/guides/cli-multi-model-workflow.md",
|
|
288
|
+
]:
|
|
289
|
+
guide = pathlib.Path(guide_name)
|
|
290
|
+
rows = {slot: table_row(guide, slot) for slot in ("FRONTIER", "HELM", "WORKHORSE", "SWEEP")}
|
|
291
|
+
for slot, row in rows.items():
|
|
292
|
+
if row is None or len(row) < 2:
|
|
293
|
+
mark_fail(f"{guide} missing Environment Binding row for {slot}")
|
|
294
|
+
if rows["FRONTIER"] and (
|
|
295
|
+
not has_exact_model(rows["FRONTIER"][1], "GPT-5.6 Sol")
|
|
296
|
+
or "max" not in rows["FRONTIER"][1]
|
|
297
|
+
or "Ultra" not in rows["FRONTIER"][1]
|
|
298
|
+
):
|
|
299
|
+
mark_fail(f"{guide} FRONTIER must bind GPT-5.6 Sol at max with Ultra allowed")
|
|
300
|
+
if rows["HELM"] and (
|
|
301
|
+
not has_exact_model(rows["HELM"][1], "GPT-5.6 Sol")
|
|
302
|
+
or "xhigh" not in rows["HELM"][1]
|
|
303
|
+
or "bounded FRONTIER Ultra" not in rows["HELM"][1]
|
|
304
|
+
or not any(token in rows["HELM"][1] for token in ("explicit selection", "명시 선택"))
|
|
305
|
+
):
|
|
306
|
+
mark_fail(f"{guide} HELM must keep the main at GPT-5.6 Sol xhigh, require explicit main Ultra selection, and allow bounded FRONTIER Ultra dispatch")
|
|
307
|
+
if rows["WORKHORSE"] and (
|
|
308
|
+
not has_exact_model(rows["WORKHORSE"][1], "GPT-5.6 Terra")
|
|
309
|
+
or "(high)" not in rows["WORKHORSE"][1]
|
|
310
|
+
):
|
|
311
|
+
mark_fail(f"{guide} WORKHORSE must bind exact GPT-5.6 Terra (high)")
|
|
312
|
+
if rows["SWEEP"] and not has_exact_model(rows["SWEEP"][1], "GPT-5.6 Luna"):
|
|
313
|
+
mark_fail(f"{guide} SWEEP must bind exact GPT-5.6 Luna")
|
|
314
|
+
for slot, claude_model in (
|
|
315
|
+
("FRONTIER", "Claude Fable 5"),
|
|
316
|
+
("HELM", "Claude Opus 4.8"),
|
|
317
|
+
("WORKHORSE", "Claude Sonnet 5"),
|
|
318
|
+
("SWEEP", "Claude Haiku 4.5"),
|
|
319
|
+
):
|
|
320
|
+
if rows[slot] and claude_model not in rows[slot][1]:
|
|
321
|
+
mark_fail(f"{guide} {slot} must bind exact {claude_model}")
|
|
322
|
+
|
|
323
|
+
def invoke(argv, *, input_text=None, env=None):
|
|
324
|
+
return subprocess.run(argv, input=input_text, capture_output=True, text=True, env=env)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def expect_text(result, label, present=(), absent=(), status=0):
|
|
328
|
+
if result.returncode != status:
|
|
329
|
+
mark_fail(f"{label} returned {result.returncode}, want {status}: {result.stderr.strip()}")
|
|
330
|
+
return False
|
|
331
|
+
for token in present:
|
|
332
|
+
if token not in result.stdout:
|
|
333
|
+
mark_fail(f"{label} missing {token!r}")
|
|
334
|
+
for token in absent:
|
|
335
|
+
if token in result.stdout:
|
|
336
|
+
mark_fail(f"{label} unexpectedly contains {token!r}")
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
helm = pathlib.Path("scripts/codex-helm.sh")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def helm_dry(*args):
|
|
344
|
+
return invoke(["bash", str(helm), "--dry-run", "--reach", "hermetic", *args, "probe"])
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if not helm.is_file():
|
|
348
|
+
mark_fail("required file missing: scripts/codex-helm.sh")
|
|
349
|
+
else:
|
|
350
|
+
if "Expert Codex config override" not in helm.read_text():
|
|
351
|
+
mark_fail("codex-helm must document -c as an expert override")
|
|
352
|
+
frontier_tokens = (
|
|
353
|
+
"Do not use native spawn_agent for FRONTIER",
|
|
354
|
+
"--model gpt-5.6-sol --effort <effort> --multi-agent auto --sandbox read-only",
|
|
355
|
+
"adapter enables native multi-agent only for ultra",
|
|
356
|
+
"agents.max_threads=4",
|
|
357
|
+
"agents.max_depth=1",
|
|
358
|
+
)
|
|
359
|
+
for mode in ("auto", "implement", "review", "scout", "single"):
|
|
360
|
+
result = helm_dry("--mode", mode)
|
|
361
|
+
if not expect_text(
|
|
362
|
+
result,
|
|
363
|
+
f"codex-helm {mode} dry-run",
|
|
364
|
+
("--bypass-sandbox", "features.multi_agent=false"),
|
|
365
|
+
("explicitly selected Codex Ultra",),
|
|
366
|
+
):
|
|
367
|
+
continue
|
|
368
|
+
match = re.search(rf"^mode={mode} reach=hermetic model=(\S+) effort=(\S+) sandbox=(\S+) ", result.stdout, re.MULTILINE)
|
|
369
|
+
if not match or match.groups() != ("gpt-5.6-sol", "xhigh", "dangerously-bypass-approvals-and-sandbox"):
|
|
370
|
+
mark_fail(f"codex-helm {mode} defaults are missing or incorrect")
|
|
371
|
+
has_adapter = "Internal FRONTIER command base" in result.stdout
|
|
372
|
+
if has_adapter != (mode != "single"):
|
|
373
|
+
mark_fail(f"codex-helm {mode} FRONTIER adapter exposure is incorrect")
|
|
374
|
+
if mode != "single":
|
|
375
|
+
for token in frontier_tokens:
|
|
376
|
+
if token not in result.stdout:
|
|
377
|
+
mark_fail(f"codex-helm {mode} missing FRONTIER contract token: {token}")
|
|
378
|
+
|
|
379
|
+
expect_text(
|
|
380
|
+
helm_dry("--effort", "ultra"),
|
|
381
|
+
"codex-helm Ultra dry-run",
|
|
382
|
+
("explicitly selected Codex Ultra", "features.multi_agent=true"),
|
|
383
|
+
)
|
|
384
|
+
single_ultra = helm_dry("--mode", "single", "--effort", "ultra")
|
|
385
|
+
if single_ultra.returncode == 0 or "Ultra requires fan-out" not in single_ultra.stderr:
|
|
386
|
+
mark_fail("codex-helm single + Ultra must fail with the fan-out explanation")
|
|
387
|
+
expect_text(
|
|
388
|
+
helm_dry("--no-fanout"),
|
|
389
|
+
"codex-helm no-fanout dry-run",
|
|
390
|
+
("Do not spawn subagents",),
|
|
391
|
+
("Internal FRONTIER command base",),
|
|
392
|
+
)
|
|
393
|
+
limited = helm_dry("--max-threads", "2", "--max-depth", "2")
|
|
394
|
+
expect_text(
|
|
395
|
+
limited,
|
|
396
|
+
"codex-helm FRONTIER limits",
|
|
397
|
+
("Internal FRONTIER command base",),
|
|
398
|
+
)
|
|
399
|
+
frontier_line = next((line for line in limited.stdout.splitlines() if line.startswith("- Internal FRONTIER command base:")), "")
|
|
400
|
+
if "agents.max_threads=2" not in frontier_line or "agents.max_depth=2" not in frontier_line:
|
|
401
|
+
mark_fail("codex-helm FRONTIER command does not carry the requested 2/2 limits")
|
|
402
|
+
for args in (
|
|
403
|
+
("--sandbox", "read-only"),
|
|
404
|
+
("--sandbox", "read-only", "--allow-danger"),
|
|
405
|
+
("--allow-danger", "--sandbox", "read-only"),
|
|
406
|
+
):
|
|
407
|
+
expect_text(
|
|
408
|
+
helm_dry(*args),
|
|
409
|
+
f"codex-helm explicit sandbox order {' '.join(args)}",
|
|
410
|
+
("sandbox=read-only",),
|
|
411
|
+
("--bypass-sandbox",),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
run = pathlib.Path("scripts/codex-run.sh")
|
|
415
|
+
if not run.is_file():
|
|
416
|
+
mark_fail("required file missing: scripts/codex-run.sh")
|
|
417
|
+
else:
|
|
418
|
+
with tempfile.TemporaryDirectory() as raw_tmp:
|
|
419
|
+
tmp = pathlib.Path(raw_tmp)
|
|
420
|
+
fake, argv_log = tmp / "codex", tmp / "argv"
|
|
421
|
+
fake.write_text(
|
|
422
|
+
"#!/usr/bin/env bash\n"
|
|
423
|
+
"printf '%s\\n' \"$@\" > \"$FAKE_CODEX_ARGV\"\n"
|
|
424
|
+
"printf 'progress-err\\n' >&2\n"
|
|
425
|
+
"printf 'final-out\\n'\n"
|
|
426
|
+
"exit \"${FAKE_CODEX_STATUS:-0}\"\n"
|
|
427
|
+
)
|
|
428
|
+
fake.chmod(0o755)
|
|
429
|
+
env = os.environ.copy()
|
|
430
|
+
env.update(PATH=f"{tmp}{os.pathsep}{env.get('PATH', '')}", FAKE_CODEX_ARGV=str(argv_log))
|
|
431
|
+
|
|
432
|
+
def fake_call(argv):
|
|
433
|
+
result = invoke(argv, input_text="probe\n", env=env)
|
|
434
|
+
return result, argv_log.read_text().splitlines()
|
|
435
|
+
|
|
436
|
+
def expect_args(label, received, present=(), absent=(), pair=None):
|
|
437
|
+
for token in present:
|
|
438
|
+
if token not in received:
|
|
439
|
+
mark_fail(f"{label} missing argument: {token}")
|
|
440
|
+
for token in absent:
|
|
441
|
+
if token in received:
|
|
442
|
+
mark_fail(f"{label} unexpectedly passed argument: {token}")
|
|
443
|
+
if pair and not any(received[i : i + 2] == list(pair) for i in range(len(received) - 1)):
|
|
444
|
+
mark_fail(f"{label} missing argument pair: {' '.join(pair)}")
|
|
445
|
+
|
|
446
|
+
result, received = fake_call([
|
|
447
|
+
"bash", str(run), "--profile", "inherit", "--bypass-sandbox",
|
|
448
|
+
"--model", "gpt-5.6-sol", "--effort", "max", "-",
|
|
449
|
+
])
|
|
450
|
+
if (result.returncode, result.stdout, result.stderr) != (0, "final-out\n", "progress-err\n"):
|
|
451
|
+
mark_fail("codex-run does not preserve stdout/stderr/exit channels")
|
|
452
|
+
expect_args(
|
|
453
|
+
"codex-run bypass",
|
|
454
|
+
received,
|
|
455
|
+
("--dangerously-bypass-approvals-and-sandbox", "gpt-5.6-sol", 'model_reasoning_effort="max"'),
|
|
456
|
+
("--sandbox",),
|
|
457
|
+
)
|
|
458
|
+
result, received = fake_call(["bash", str(run), "--profile", "inherit", "--sandbox", "read-only"])
|
|
459
|
+
expect_args(
|
|
460
|
+
"codex-run explicit sandbox",
|
|
461
|
+
received,
|
|
462
|
+
absent=("--dangerously-bypass-approvals-and-sandbox",),
|
|
463
|
+
pair=("--sandbox", "read-only"),
|
|
464
|
+
)
|
|
465
|
+
if result.returncode != 0:
|
|
466
|
+
mark_fail("codex-run explicit sandbox fake runtime failed")
|
|
467
|
+
|
|
468
|
+
for effort, multi_agent in (("max", "false"), ("ultra", "true")):
|
|
469
|
+
result, received = fake_call([
|
|
470
|
+
"bash", str(run), "--profile", "inherit", "--effort", effort, "--multi-agent", "auto",
|
|
471
|
+
])
|
|
472
|
+
if result.returncode != 0:
|
|
473
|
+
mark_fail(f"codex-run multi-agent auto failed at {effort}")
|
|
474
|
+
expect_args(
|
|
475
|
+
f"codex-run multi-agent auto {effort}",
|
|
476
|
+
received,
|
|
477
|
+
(f'model_reasoning_effort="{effort}"', f"features.multi_agent={multi_agent}"),
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
for effort, multi_agent in (("xhigh", "false"), ("ultra", "true")):
|
|
481
|
+
result, received = fake_call(["bash", str(helm), "--reach", "inherit", "--effort", effort, "probe"])
|
|
482
|
+
if result.returncode != 0:
|
|
483
|
+
mark_fail(f"codex-helm fake runtime failed at effort {effort}: {result.stderr.strip()}")
|
|
484
|
+
expect_args(
|
|
485
|
+
f"codex-helm {effort}",
|
|
486
|
+
received,
|
|
487
|
+
("--dangerously-bypass-approvals-and-sandbox", f'model_reasoning_effort="{effort}"', f"features.multi_agent={multi_agent}"),
|
|
488
|
+
("--sandbox",),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
env["FAKE_CODEX_STATUS"] = "7"
|
|
492
|
+
failed, _ = fake_call(["bash", str(run), "--profile", "inherit", "--bypass-sandbox"])
|
|
493
|
+
if failed.returncode != 7:
|
|
494
|
+
mark_fail(f"codex-run returns {failed.returncode} instead of Codex exit status 7")
|
|
495
|
+
|
|
496
|
+
if launcher.is_file() and launch_profile:
|
|
497
|
+
syntax = invoke([sys.executable, "-c", f"compile(open({str(launcher)!r}).read(), {str(launcher)!r}, 'exec')"])
|
|
498
|
+
if syntax.returncode != 0:
|
|
499
|
+
mark_fail(f"agent-launch Python syntax failed: {syntax.stderr.strip()}")
|
|
500
|
+
zsh_syntax = invoke(["zsh", "-n", str(shell_init)])
|
|
501
|
+
if zsh_syntax.returncode != 0:
|
|
502
|
+
mark_fail(f"agent-launch zsh syntax failed: {zsh_syntax.stderr.strip()}")
|
|
503
|
+
|
|
504
|
+
try:
|
|
505
|
+
spec = importlib.util.spec_from_file_location("agent_launch_under_test", launcher)
|
|
506
|
+
if spec is None or spec.loader is None:
|
|
507
|
+
raise RuntimeError("could not create import specification")
|
|
508
|
+
launcher_module = importlib.util.module_from_spec(spec)
|
|
509
|
+
sys.modules[spec.name] = launcher_module
|
|
510
|
+
spec.loader.exec_module(launcher_module)
|
|
511
|
+
except Exception as exc:
|
|
512
|
+
mark_fail(f"agent-launch test import failed: {exc}")
|
|
513
|
+
launcher_module = None
|
|
514
|
+
|
|
515
|
+
if launcher_module is not None:
|
|
516
|
+
class ScriptedUI:
|
|
517
|
+
def __init__(self):
|
|
518
|
+
self.plan = None
|
|
519
|
+
self.checked = False
|
|
520
|
+
self.hub_visits = 0
|
|
521
|
+
|
|
522
|
+
def set_plan(self, plan):
|
|
523
|
+
self.plan = plan
|
|
524
|
+
|
|
525
|
+
def choose(self, title, options, default, allow_back, preview=None):
|
|
526
|
+
if title == "Preset":
|
|
527
|
+
if preview is not None:
|
|
528
|
+
balanced = launcher_module.setup_summary_lines(preview("balanced"))
|
|
529
|
+
deep = launcher_module.setup_summary_lines(preview("deep-review"))
|
|
530
|
+
if balanced == deep:
|
|
531
|
+
mark_fail(
|
|
532
|
+
"agent-launch Preset menu does not live-preview the "
|
|
533
|
+
"highlighted preset in the setup panel"
|
|
534
|
+
)
|
|
535
|
+
return launcher_module.CUSTOM_PRESET
|
|
536
|
+
if title == "Custom settings":
|
|
537
|
+
self.hub_visits += 1
|
|
538
|
+
if self.hub_visits == 1:
|
|
539
|
+
return "main"
|
|
540
|
+
summary = launcher_module.setup_summary_lines(self.plan)
|
|
541
|
+
option_by_value = {option.value: option for option in options}
|
|
542
|
+
if "Main WORKHORSE | Review native-panel" not in summary:
|
|
543
|
+
mark_fail(
|
|
544
|
+
"agent-launch did not project the selected main tier back "
|
|
545
|
+
"into the Custom hub"
|
|
546
|
+
)
|
|
547
|
+
if not {"tier:frontier", "tier:workhorse", "start", "exit"} <= set(option_by_value):
|
|
548
|
+
mark_fail("agent-launch Custom hub is missing tier or final actions")
|
|
549
|
+
if (
|
|
550
|
+
option_by_value["start"].label != "Start with these settings"
|
|
551
|
+
or option_by_value["start"].description
|
|
552
|
+
!= "Confirm the complete setup shown above and continue to launch."
|
|
553
|
+
or option_by_value["exit"].label != "Exit without launching"
|
|
554
|
+
or option_by_value["exit"].description
|
|
555
|
+
!= "Discard this launch and return to the shell."
|
|
556
|
+
):
|
|
557
|
+
mark_fail("agent-launch Custom final actions have mismatched semantics")
|
|
558
|
+
self.checked = True
|
|
559
|
+
return "start"
|
|
560
|
+
if title == "Main tier":
|
|
561
|
+
if preview is not None:
|
|
562
|
+
summary = launcher_module.setup_summary_lines(preview("sweep"))
|
|
563
|
+
if "Main SWEEP | Review native-panel" not in summary:
|
|
564
|
+
mark_fail(
|
|
565
|
+
"agent-launch Main tier menu does not live-preview the "
|
|
566
|
+
"highlighted tier in the setup panel"
|
|
567
|
+
)
|
|
568
|
+
return "workhorse"
|
|
569
|
+
raise AssertionError(f"unexpected scripted menu: {title}")
|
|
570
|
+
|
|
571
|
+
scripted_ui = ScriptedUI()
|
|
572
|
+
scripted_plan = launcher_module.select_plan(
|
|
573
|
+
launch_profile,
|
|
574
|
+
"codex",
|
|
575
|
+
None,
|
|
576
|
+
False,
|
|
577
|
+
scripted_ui,
|
|
578
|
+
)
|
|
579
|
+
if not scripted_ui.checked or not scripted_plan.get("_launch_confirmed"):
|
|
580
|
+
mark_fail("agent-launch scripted Custom hub did not reach final confirmation")
|
|
581
|
+
|
|
582
|
+
with tempfile.TemporaryDirectory() as raw_tmp:
|
|
583
|
+
tmp = pathlib.Path(raw_tmp)
|
|
584
|
+
backend = tmp / "backend"
|
|
585
|
+
argv_log = tmp / "backend.argv"
|
|
586
|
+
backend.write_text(
|
|
587
|
+
"#!/usr/bin/env bash\n"
|
|
588
|
+
"printf '%s\\n' \"$@\" > \"$FAKE_LAUNCH_ARGV\"\n"
|
|
589
|
+
"printf 'backend-output\\n'\n"
|
|
590
|
+
"exit \"${FAKE_LAUNCH_STATUS:-0}\"\n"
|
|
591
|
+
)
|
|
592
|
+
backend.chmod(0o755)
|
|
593
|
+
fake_onto = tmp / "onto"
|
|
594
|
+
fake_ultracode = tmp / "ultracode-for-codex"
|
|
595
|
+
for capability in (fake_onto, fake_ultracode):
|
|
596
|
+
capability.write_text("#!/usr/bin/env bash\nexit 0\n")
|
|
597
|
+
capability.chmod(0o755)
|
|
598
|
+
fake_profile = tmp / "profiles.toml"
|
|
599
|
+
profile_text = launch_profile_path.read_text()
|
|
600
|
+
profile_text = profile_text.replace('command = "codex"', f"command = {json.dumps(str(backend))}", 1)
|
|
601
|
+
profile_text = profile_text.replace('command = "claude"', f"command = {json.dumps(str(backend))}", 1)
|
|
602
|
+
profile_text = profile_text.replace('command = "onto"', f"command = {json.dumps(str(fake_onto))}", 1)
|
|
603
|
+
profile_text = profile_text.replace(
|
|
604
|
+
'command = "ultracode-for-codex"',
|
|
605
|
+
f"command = {json.dumps(str(fake_ultracode))}",
|
|
606
|
+
1,
|
|
607
|
+
)
|
|
608
|
+
portable_profile_text = profile_text
|
|
609
|
+
for tier in ("frontier", "workhorse", "sweep"):
|
|
610
|
+
profile_text = profile_text.replace(
|
|
611
|
+
f'${{CODEX_HOME}}/agents/{tier}.toml', str((agent_dir / f"{tier}.toml").resolve()), 1
|
|
612
|
+
)
|
|
613
|
+
fake_profile.write_text(profile_text)
|
|
614
|
+
fake_data = tomllib.loads(profile_text)
|
|
615
|
+
fake_commands = {
|
|
616
|
+
host: binding.get("command") for host, binding in fake_data.get("backends", {}).items()
|
|
617
|
+
}
|
|
618
|
+
if set(fake_commands) != {"codex", "claude"} or any(
|
|
619
|
+
command != str(backend) for command in fake_commands.values()
|
|
620
|
+
):
|
|
621
|
+
mark_fail(f"fake launch profile did not replace every backend: {fake_commands!r}")
|
|
622
|
+
fake_profile = pathlib.Path("/nonexistent/unsafe-fixture")
|
|
623
|
+
env = os.environ.copy()
|
|
624
|
+
env.update(
|
|
625
|
+
FAKE_LAUNCH_ARGV=str(argv_log),
|
|
626
|
+
AGENT_LAUNCH_CONFIG=str(fake_profile),
|
|
627
|
+
XDG_CACHE_HOME=str(tmp / "cache"),
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
pty_env = env.copy()
|
|
631
|
+
|
|
632
|
+
def run_picker_scenario(
|
|
633
|
+
name,
|
|
634
|
+
steps,
|
|
635
|
+
timeout=3,
|
|
636
|
+
term="xterm-256color",
|
|
637
|
+
host="codex",
|
|
638
|
+
config_path=fake_profile,
|
|
639
|
+
dry_run=True,
|
|
640
|
+
):
|
|
641
|
+
scenario_env = pty_env.copy()
|
|
642
|
+
scenario_env["TERM"] = term
|
|
643
|
+
picker_pid, master = pty.fork()
|
|
644
|
+
if picker_pid == 0:
|
|
645
|
+
try:
|
|
646
|
+
child_argv = [
|
|
647
|
+
sys.executable,
|
|
648
|
+
str(launcher),
|
|
649
|
+
"--config",
|
|
650
|
+
str(config_path),
|
|
651
|
+
]
|
|
652
|
+
if dry_run:
|
|
653
|
+
child_argv.append("--dry-run")
|
|
654
|
+
child_argv.append(host)
|
|
655
|
+
os.execve(
|
|
656
|
+
sys.executable,
|
|
657
|
+
child_argv,
|
|
658
|
+
scenario_env,
|
|
659
|
+
)
|
|
660
|
+
except Exception:
|
|
661
|
+
os._exit(127)
|
|
662
|
+
fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", 24, 100, 0, 0))
|
|
663
|
+
transcript = bytearray()
|
|
664
|
+
picker_status = None
|
|
665
|
+
search_offset = 0
|
|
666
|
+
|
|
667
|
+
def read_until(token):
|
|
668
|
+
nonlocal search_offset
|
|
669
|
+
deadline = time.monotonic() + timeout
|
|
670
|
+
while time.monotonic() < deadline:
|
|
671
|
+
found = transcript.find(token, search_offset)
|
|
672
|
+
if found >= 0:
|
|
673
|
+
search_offset = found + len(token)
|
|
674
|
+
return True
|
|
675
|
+
ready, _, _ = select.select([master], [], [], 0.1)
|
|
676
|
+
if not ready:
|
|
677
|
+
continue
|
|
678
|
+
try:
|
|
679
|
+
chunk = os.read(master, 4096)
|
|
680
|
+
except OSError:
|
|
681
|
+
break
|
|
682
|
+
if not chunk:
|
|
683
|
+
break
|
|
684
|
+
transcript.extend(chunk)
|
|
685
|
+
found = transcript.find(token, search_offset)
|
|
686
|
+
if found >= 0:
|
|
687
|
+
search_offset = found + len(token)
|
|
688
|
+
return True
|
|
689
|
+
return False
|
|
690
|
+
|
|
691
|
+
try:
|
|
692
|
+
for expected, key in steps:
|
|
693
|
+
if not read_until(expected):
|
|
694
|
+
mark_fail(f"agent-launch {name} missing {expected.decode()!r}")
|
|
695
|
+
break
|
|
696
|
+
if isinstance(key, tuple):
|
|
697
|
+
rows, columns = key[:2]
|
|
698
|
+
if len(key) == 3:
|
|
699
|
+
os.write(master, key[2])
|
|
700
|
+
time.sleep(0.1)
|
|
701
|
+
fcntl.ioctl(
|
|
702
|
+
master,
|
|
703
|
+
termios.TIOCSWINSZ,
|
|
704
|
+
struct.pack("HHHH", rows, columns, 0, 0),
|
|
705
|
+
)
|
|
706
|
+
try:
|
|
707
|
+
os.kill(picker_pid, signal.SIGWINCH)
|
|
708
|
+
except ProcessLookupError:
|
|
709
|
+
pass
|
|
710
|
+
else:
|
|
711
|
+
os.write(master, key)
|
|
712
|
+
deadline = time.monotonic() + timeout
|
|
713
|
+
while time.monotonic() < deadline:
|
|
714
|
+
ready, _, _ = select.select([master], [], [], 0.05)
|
|
715
|
+
if ready:
|
|
716
|
+
try:
|
|
717
|
+
chunk = os.read(master, 4096)
|
|
718
|
+
except OSError:
|
|
719
|
+
chunk = b""
|
|
720
|
+
if chunk:
|
|
721
|
+
transcript.extend(chunk)
|
|
722
|
+
waited_pid, raw_status = os.waitpid(picker_pid, os.WNOHANG)
|
|
723
|
+
if waited_pid:
|
|
724
|
+
picker_status = os.waitstatus_to_exitcode(raw_status)
|
|
725
|
+
break
|
|
726
|
+
if picker_status is not None:
|
|
727
|
+
while select.select([master], [], [], 0)[0]:
|
|
728
|
+
try:
|
|
729
|
+
chunk = os.read(master, 4096)
|
|
730
|
+
except OSError:
|
|
731
|
+
break
|
|
732
|
+
if not chunk:
|
|
733
|
+
break
|
|
734
|
+
transcript.extend(chunk)
|
|
735
|
+
finally:
|
|
736
|
+
if picker_status is None:
|
|
737
|
+
try:
|
|
738
|
+
os.kill(picker_pid, signal.SIGKILL)
|
|
739
|
+
except ProcessLookupError:
|
|
740
|
+
pass
|
|
741
|
+
_, raw_status = os.waitpid(picker_pid, 0)
|
|
742
|
+
picker_status = os.waitstatus_to_exitcode(raw_status)
|
|
743
|
+
os.close(master)
|
|
744
|
+
return bytes(transcript), picker_status
|
|
745
|
+
|
|
746
|
+
open_custom_steps = (
|
|
747
|
+
(b"native multi-perspective review", b""),
|
|
748
|
+
(b"Esc cancel | q cancel", b"\x1b[B"),
|
|
749
|
+
(b"hybrid onto", b"\x1b[B"),
|
|
750
|
+
(b"high-volume", b"\x1b[B"),
|
|
751
|
+
(b"Open a settings hub", b"\r"),
|
|
752
|
+
)
|
|
753
|
+
down = b"\x1b[B"
|
|
754
|
+
|
|
755
|
+
def hub_choice(offset):
|
|
756
|
+
return (b"Custom settings", down * offset + b"\r")
|
|
757
|
+
|
|
758
|
+
transcript, picker_status = run_picker_scenario(
|
|
759
|
+
"fixed layout",
|
|
760
|
+
((b"Esc cancel | q cancel", b"q"),),
|
|
761
|
+
)
|
|
762
|
+
if picker_status != 130:
|
|
763
|
+
mark_fail(f"agent-launch fixed layout returned {picker_status}, want 130")
|
|
764
|
+
layout_tokens = (
|
|
765
|
+
b"Current setup",
|
|
766
|
+
b"FRONTIER",
|
|
767
|
+
b"HELM",
|
|
768
|
+
b"WORKHORSE",
|
|
769
|
+
b"SWEEP",
|
|
770
|
+
b"About highlighted option",
|
|
771
|
+
b"HELM default for everyday work",
|
|
772
|
+
b"Options (",
|
|
773
|
+
)
|
|
774
|
+
positions = {token: transcript.find(token) for token in layout_tokens}
|
|
775
|
+
if any(position < 0 for position in positions.values()):
|
|
776
|
+
missing = [token.decode() for token, position in positions.items() if position < 0]
|
|
777
|
+
mark_fail(f"agent-launch fixed layout missing sections: {missing!r}")
|
|
778
|
+
elif not (
|
|
779
|
+
positions[b"Current setup"]
|
|
780
|
+
< min(positions[token] for token in (b"FRONTIER", b"HELM", b"WORKHORSE", b"SWEEP"))
|
|
781
|
+
< positions[b"About highlighted option"]
|
|
782
|
+
< positions[b"HELM default for everyday work"]
|
|
783
|
+
< positions[b"Options ("]
|
|
784
|
+
):
|
|
785
|
+
mark_fail("agent-launch layout is not setup then description then options")
|
|
786
|
+
|
|
787
|
+
_, picker_status = run_picker_scenario(
|
|
788
|
+
"root escape cancellation",
|
|
789
|
+
((b"Esc cancel | q cancel", b"\x1b"),),
|
|
790
|
+
)
|
|
791
|
+
if picker_status != 130:
|
|
792
|
+
mark_fail(f"agent-launch root Esc returned {picker_status}, want 130")
|
|
793
|
+
|
|
794
|
+
if argv_log.exists():
|
|
795
|
+
argv_log.unlink()
|
|
796
|
+
_, picker_status = run_picker_scenario(
|
|
797
|
+
"launch confirmation q cancellation",
|
|
798
|
+
(
|
|
799
|
+
(b"Esc cancel | q cancel", b"\r"),
|
|
800
|
+
(b"Launch? [Y/n/q]:", b"q\r"),
|
|
801
|
+
),
|
|
802
|
+
dry_run=False,
|
|
803
|
+
)
|
|
804
|
+
if picker_status != 130 or argv_log.exists():
|
|
805
|
+
mark_fail("agent-launch final confirmation q executed the backend")
|
|
806
|
+
|
|
807
|
+
if argv_log.exists():
|
|
808
|
+
argv_log.unlink()
|
|
809
|
+
transcript, picker_status = run_picker_scenario(
|
|
810
|
+
"Custom final start",
|
|
811
|
+
(
|
|
812
|
+
*open_custom_steps,
|
|
813
|
+
(b"Custom settings", down * 8),
|
|
814
|
+
(b"Confirm the complete setup shown above", b"\r"),
|
|
815
|
+
),
|
|
816
|
+
dry_run=False,
|
|
817
|
+
)
|
|
818
|
+
if (
|
|
819
|
+
picker_status != 0
|
|
820
|
+
or not argv_log.exists()
|
|
821
|
+
or b"Launch? [Y/n/q]:" in transcript
|
|
822
|
+
or b"Start with these settings" not in transcript
|
|
823
|
+
):
|
|
824
|
+
mark_fail("agent-launch Custom Start was not the final launch confirmation")
|
|
825
|
+
|
|
826
|
+
if argv_log.exists():
|
|
827
|
+
argv_log.unlink()
|
|
828
|
+
transcript, picker_status = run_picker_scenario(
|
|
829
|
+
"Custom explicit exit",
|
|
830
|
+
(
|
|
831
|
+
*open_custom_steps,
|
|
832
|
+
(b"Custom settings", down * 9),
|
|
833
|
+
(b"Discard this launch and return to the shell", b"\r"),
|
|
834
|
+
),
|
|
835
|
+
dry_run=False,
|
|
836
|
+
)
|
|
837
|
+
if (
|
|
838
|
+
picker_status != 130
|
|
839
|
+
or argv_log.exists()
|
|
840
|
+
or b"Exit without launching" not in transcript
|
|
841
|
+
):
|
|
842
|
+
mark_fail("agent-launch Custom Exit executed the backend")
|
|
843
|
+
|
|
844
|
+
transcript, picker_status = run_picker_scenario(
|
|
845
|
+
"Custom escape back",
|
|
846
|
+
(
|
|
847
|
+
*open_custom_steps,
|
|
848
|
+
(b"Custom settings", b"\x1b"),
|
|
849
|
+
(b"HELM default for everyday work", b"\r"),
|
|
850
|
+
),
|
|
851
|
+
)
|
|
852
|
+
if picker_status != 0 or b"Preset Balanced" not in transcript:
|
|
853
|
+
mark_fail("agent-launch Custom Esc did not return to the preset picker")
|
|
854
|
+
|
|
855
|
+
_, picker_status = run_picker_scenario(
|
|
856
|
+
"Custom interior escape back",
|
|
857
|
+
(
|
|
858
|
+
*open_custom_steps,
|
|
859
|
+
(b"Custom settings", b"\r"),
|
|
860
|
+
(b"Primary orchestrator for planning, delegation", b"\x1b"),
|
|
861
|
+
(b"Choose the primary orchestrator used for this session", b"q"),
|
|
862
|
+
),
|
|
863
|
+
)
|
|
864
|
+
if picker_status != 130:
|
|
865
|
+
mark_fail(
|
|
866
|
+
f"agent-launch interior Esc back returned {picker_status}, want 130"
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
_, picker_status = run_picker_scenario(
|
|
870
|
+
"current setup update",
|
|
871
|
+
(
|
|
872
|
+
*open_custom_steps,
|
|
873
|
+
(b"Custom settings", b"\r"),
|
|
874
|
+
(b"Primary orchestrator for planning, delegation", b"\x1b[B"),
|
|
875
|
+
(b"high-volume implementation", b"\r"),
|
|
876
|
+
(b"Main tier: WORKHORSE", b"q"),
|
|
877
|
+
),
|
|
878
|
+
)
|
|
879
|
+
if picker_status != 130:
|
|
880
|
+
mark_fail("agent-launch main-tier selection flow did not reach Review setup")
|
|
881
|
+
|
|
882
|
+
transcript, picker_status = run_picker_scenario(
|
|
883
|
+
"Custom tier hub return",
|
|
884
|
+
(
|
|
885
|
+
*open_custom_steps,
|
|
886
|
+
hub_choice(3),
|
|
887
|
+
(b"Other (enter a model id)", down + b"\r"),
|
|
888
|
+
(b"strongest standard reasoning mode", down + b"\r"),
|
|
889
|
+
(b"FRONTIER: gpt-5.6-terra / ultra", down * 2 + b"\r"),
|
|
890
|
+
(b"Other (enter a model id)", down * 2 + b"\r"),
|
|
891
|
+
(b"Current value: gpt-5.6-terra", b"workhorse-local\r"),
|
|
892
|
+
(b"complex implementation and analysis", down + b"\r"),
|
|
893
|
+
(b"WORKHORSE: workhorse-local / xhigh", down * 3 + b"\r"),
|
|
894
|
+
),
|
|
895
|
+
)
|
|
896
|
+
if picker_status != 0:
|
|
897
|
+
mark_fail(
|
|
898
|
+
f"agent-launch tier-to-hub flow returned {picker_status}, want 0"
|
|
899
|
+
)
|
|
900
|
+
for expected in (
|
|
901
|
+
"FRONTIER gpt-5.6-terra · ultra".encode(),
|
|
902
|
+
"WORKHORSE workhorse-local · xhigh".encode(),
|
|
903
|
+
):
|
|
904
|
+
if expected not in transcript:
|
|
905
|
+
mark_fail(
|
|
906
|
+
"agent-launch tier-to-hub flow did not visit "
|
|
907
|
+
f"{expected.decode()!r}"
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
numbered_interior_back = invoke(
|
|
911
|
+
[
|
|
912
|
+
sys.executable,
|
|
913
|
+
str(launcher),
|
|
914
|
+
"--config",
|
|
915
|
+
str(fake_profile),
|
|
916
|
+
"--preset",
|
|
917
|
+
"balanced",
|
|
918
|
+
"--custom",
|
|
919
|
+
"--dry-run",
|
|
920
|
+
"codex",
|
|
921
|
+
],
|
|
922
|
+
input_text="1\nb\n9\n",
|
|
923
|
+
env=env,
|
|
924
|
+
)
|
|
925
|
+
if (
|
|
926
|
+
numbered_interior_back.returncode != 0
|
|
927
|
+
or "b back | q cancel" not in numbered_interior_back.stdout
|
|
928
|
+
or numbered_interior_back.stdout.count("\nCustom settings\n") < 2
|
|
929
|
+
or "Preset Custom (Balanced)" not in numbered_interior_back.stdout
|
|
930
|
+
):
|
|
931
|
+
mark_fail("agent-launch numbered interior back did not return to Custom hub")
|
|
932
|
+
|
|
933
|
+
_, picker_status = run_picker_scenario(
|
|
934
|
+
"Custom model q cancellation",
|
|
935
|
+
(
|
|
936
|
+
*open_custom_steps,
|
|
937
|
+
hub_choice(3),
|
|
938
|
+
(b"Other (enter a model id)", down * 3 + b"\r"),
|
|
939
|
+
(b"Current value: gpt-5.6-sol", b"q\r"),
|
|
940
|
+
),
|
|
941
|
+
)
|
|
942
|
+
if picker_status != 130:
|
|
943
|
+
mark_fail(
|
|
944
|
+
f"agent-launch curses model q returned {picker_status}, want 130"
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
_, picker_status = run_picker_scenario(
|
|
948
|
+
"Custom model Esc cancellation",
|
|
949
|
+
(
|
|
950
|
+
*open_custom_steps,
|
|
951
|
+
hub_choice(3),
|
|
952
|
+
(b"Other (enter a model id)", down * 3 + b"\r"),
|
|
953
|
+
(b"Current value: gpt-5.6-sol", b"\x1b"),
|
|
954
|
+
),
|
|
955
|
+
)
|
|
956
|
+
if picker_status != 130:
|
|
957
|
+
mark_fail(
|
|
958
|
+
f"agent-launch curses model Esc returned {picker_status}, want 130"
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
_, picker_status = run_picker_scenario(
|
|
962
|
+
"Custom q-prefixed model preservation",
|
|
963
|
+
(
|
|
964
|
+
*open_custom_steps,
|
|
965
|
+
hub_choice(3),
|
|
966
|
+
(b"Other (enter a model id)", down * 3 + b"\r"),
|
|
967
|
+
(b"Current value: gpt-5.6-sol", b"qwen\r"),
|
|
968
|
+
(b"Options (", b"\x1b"),
|
|
969
|
+
(b"Other (enter a model id)", b"\r"),
|
|
970
|
+
(b"Current value: qwen", b"\x1b"),
|
|
971
|
+
),
|
|
972
|
+
)
|
|
973
|
+
if picker_status != 130:
|
|
974
|
+
mark_fail(
|
|
975
|
+
f"agent-launch q-prefixed model returned {picker_status}, want 130"
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
_, picker_status = run_picker_scenario(
|
|
979
|
+
"Custom model Ctrl-C cancellation",
|
|
980
|
+
(
|
|
981
|
+
*open_custom_steps,
|
|
982
|
+
hub_choice(3),
|
|
983
|
+
(b"Other (enter a model id)", down * 3 + b"\r"),
|
|
984
|
+
(b"Current value: gpt-5.6-sol", b"\x03"),
|
|
985
|
+
),
|
|
986
|
+
)
|
|
987
|
+
if picker_status != 130:
|
|
988
|
+
mark_fail(
|
|
989
|
+
f"agent-launch curses model Ctrl-C returned {picker_status}, want 130"
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
numbered_model_q = invoke(
|
|
993
|
+
[
|
|
994
|
+
sys.executable,
|
|
995
|
+
str(launcher),
|
|
996
|
+
"--config",
|
|
997
|
+
str(fake_profile),
|
|
998
|
+
"--preset",
|
|
999
|
+
"balanced",
|
|
1000
|
+
"--custom",
|
|
1001
|
+
"--dry-run",
|
|
1002
|
+
"codex",
|
|
1003
|
+
],
|
|
1004
|
+
input_text="4\nq\n",
|
|
1005
|
+
env=env,
|
|
1006
|
+
)
|
|
1007
|
+
if numbered_model_q.returncode != 130 or "Traceback" in numbered_model_q.stderr:
|
|
1008
|
+
mark_fail("agent-launch numbered model q did not cancel cleanly")
|
|
1009
|
+
|
|
1010
|
+
_, picker_status = run_picker_scenario(
|
|
1011
|
+
"arrow picker cancellation",
|
|
1012
|
+
(*open_custom_steps, (b"Custom settings", b"\x03")),
|
|
1013
|
+
)
|
|
1014
|
+
if picker_status != 130:
|
|
1015
|
+
mark_fail(f"agent-launch arrow picker returned {picker_status}, want 130")
|
|
1016
|
+
|
|
1017
|
+
transcript, picker_status = run_picker_scenario(
|
|
1018
|
+
"Custom completion",
|
|
1019
|
+
(*open_custom_steps, hub_choice(8)),
|
|
1020
|
+
)
|
|
1021
|
+
if picker_status != 0:
|
|
1022
|
+
mark_fail(f"agent-launch Custom completion returned {picker_status}, want 0")
|
|
1023
|
+
for expected in (
|
|
1024
|
+
b"Preset Custom (Balanced)",
|
|
1025
|
+
b"Main HELM",
|
|
1026
|
+
b"Review setup native-panel",
|
|
1027
|
+
b"Execution Codex bypass",
|
|
1028
|
+
):
|
|
1029
|
+
if expected not in transcript:
|
|
1030
|
+
mark_fail(f"agent-launch Custom projection missing {expected.decode()!r}")
|
|
1031
|
+
|
|
1032
|
+
claude_auto_profile = tmp / "claude-auto.toml"
|
|
1033
|
+
claude_auto_profile.write_text(
|
|
1034
|
+
profile_text.replace(
|
|
1035
|
+
'claude_permission_mode = "bypassPermissions"',
|
|
1036
|
+
'claude_permission_mode = "auto"',
|
|
1037
|
+
1,
|
|
1038
|
+
)
|
|
1039
|
+
)
|
|
1040
|
+
transcript, picker_status = run_picker_scenario(
|
|
1041
|
+
"Claude Custom auto preservation",
|
|
1042
|
+
(*open_custom_steps, hub_choice(8)),
|
|
1043
|
+
host="claude",
|
|
1044
|
+
config_path=claude_auto_profile,
|
|
1045
|
+
)
|
|
1046
|
+
if picker_status != 0:
|
|
1047
|
+
mark_fail(
|
|
1048
|
+
f"agent-launch Claude Custom auto returned {picker_status}, want 0"
|
|
1049
|
+
)
|
|
1050
|
+
for expected in (
|
|
1051
|
+
b"Preset Custom (Balanced)",
|
|
1052
|
+
b"Execution Claude auto",
|
|
1053
|
+
b'"--permission-mode", "auto"',
|
|
1054
|
+
):
|
|
1055
|
+
if expected not in transcript:
|
|
1056
|
+
mark_fail(
|
|
1057
|
+
"agent-launch Claude Custom auto projection missing "
|
|
1058
|
+
f"{expected.decode()!r}"
|
|
1059
|
+
)
|
|
1060
|
+
if b'"--dangerously-skip-permissions"' in transcript:
|
|
1061
|
+
mark_fail("agent-launch Claude Custom auto escalated to permission bypass")
|
|
1062
|
+
|
|
1063
|
+
claude_dont_ask_profile = tmp / "claude-dont-ask.toml"
|
|
1064
|
+
claude_dont_ask_profile.write_text(
|
|
1065
|
+
profile_text.replace(
|
|
1066
|
+
'claude_permission_mode = "bypassPermissions"',
|
|
1067
|
+
'claude_permission_mode = "dontAsk"',
|
|
1068
|
+
1,
|
|
1069
|
+
)
|
|
1070
|
+
)
|
|
1071
|
+
dont_ask = invoke(
|
|
1072
|
+
[
|
|
1073
|
+
sys.executable,
|
|
1074
|
+
str(launcher),
|
|
1075
|
+
"--config",
|
|
1076
|
+
str(claude_dont_ask_profile),
|
|
1077
|
+
"--preset",
|
|
1078
|
+
"balanced",
|
|
1079
|
+
"--custom",
|
|
1080
|
+
"--dry-run",
|
|
1081
|
+
"claude",
|
|
1082
|
+
],
|
|
1083
|
+
input_text="9\n",
|
|
1084
|
+
env=env,
|
|
1085
|
+
)
|
|
1086
|
+
if dont_ask.returncode != 0:
|
|
1087
|
+
mark_fail(
|
|
1088
|
+
"agent-launch numbered Claude Custom dontAsk failed: "
|
|
1089
|
+
f"{dont_ask.stderr.strip()}"
|
|
1090
|
+
)
|
|
1091
|
+
else:
|
|
1092
|
+
dont_ask_argv = json.loads(dont_ask.stdout.splitlines()[-1])
|
|
1093
|
+
if dont_ask_argv[-2:] != ["--permission-mode", "dontAsk"]:
|
|
1094
|
+
mark_fail(
|
|
1095
|
+
"agent-launch numbered Claude Custom did not preserve dontAsk"
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
# TERM=dumb routes to numbered prompts (textual renders on any usable
|
|
1099
|
+
# terminal, so the numbered fallback is gated on TERM/non-TTY/textual
|
|
1100
|
+
# availability, not a terminfo probe). 'b' is the numbered back command.
|
|
1101
|
+
transcript, picker_status = run_picker_scenario(
|
|
1102
|
+
"numbered fallback (TERM=dumb)",
|
|
1103
|
+
(
|
|
1104
|
+
(b"Open a settings hub", b"4\n"),
|
|
1105
|
+
(b"Custom settings", b"b\n"),
|
|
1106
|
+
(b"HELM default for everyday work", b"1\n"),
|
|
1107
|
+
),
|
|
1108
|
+
term="dumb",
|
|
1109
|
+
)
|
|
1110
|
+
if (
|
|
1111
|
+
picker_status != 0
|
|
1112
|
+
or b"Traceback" in transcript
|
|
1113
|
+
or b"Preset Balanced" not in transcript
|
|
1114
|
+
):
|
|
1115
|
+
mark_fail("agent-launch numbered fallback (TERM=dumb) did not preserve numbered back")
|
|
1116
|
+
|
|
1117
|
+
passed = invoke([
|
|
1118
|
+
sys.executable, str(launcher), "--no-tui", "codex", "--", "exec", "--json", "probe"
|
|
1119
|
+
], env=env)
|
|
1120
|
+
if passed.returncode != 0 or argv_log.read_text().splitlines() != ["exec", "--json", "probe"]:
|
|
1121
|
+
mark_fail("agent-launch Codex argument-bearing bypass changed forwarded argv")
|
|
1122
|
+
|
|
1123
|
+
passed = invoke([
|
|
1124
|
+
sys.executable, str(launcher), "--no-tui", "claude", "--", "-p", "probe"
|
|
1125
|
+
], env=env)
|
|
1126
|
+
if passed.returncode != 0 or argv_log.read_text().splitlines() != [
|
|
1127
|
+
"--dangerously-skip-permissions", "-p", "probe"
|
|
1128
|
+
]:
|
|
1129
|
+
mark_fail("agent-launch Claude bypass did not preserve the existing backend default")
|
|
1130
|
+
|
|
1131
|
+
env["FAKE_LAUNCH_STATUS"] = "9"
|
|
1132
|
+
failed = invoke([sys.executable, str(launcher), "--no-tui", "codex", "--", "probe"], env=env)
|
|
1133
|
+
if failed.returncode != 9:
|
|
1134
|
+
mark_fail(f"agent-launch returns {failed.returncode} instead of backend exit status 9")
|
|
1135
|
+
env.pop("FAKE_LAUNCH_STATUS")
|
|
1136
|
+
|
|
1137
|
+
sleeper = tmp / "sleeper"
|
|
1138
|
+
sleeper.write_text("#!/usr/bin/env bash\nexec sleep 30\n")
|
|
1139
|
+
sleeper.chmod(0o755)
|
|
1140
|
+
signal_profile = tmp / "signal.toml"
|
|
1141
|
+
signal_profile.write_text(profile_text.replace(str(backend), str(sleeper)))
|
|
1142
|
+
signal_process = subprocess.Popen([
|
|
1143
|
+
sys.executable, str(launcher), "--config", str(signal_profile),
|
|
1144
|
+
"--no-tui", "codex",
|
|
1145
|
+
], env=env)
|
|
1146
|
+
time.sleep(0.2)
|
|
1147
|
+
signal_process.terminate()
|
|
1148
|
+
try:
|
|
1149
|
+
signal_status = signal_process.wait(timeout=3)
|
|
1150
|
+
except subprocess.TimeoutExpired:
|
|
1151
|
+
signal_process.kill()
|
|
1152
|
+
signal_process.wait()
|
|
1153
|
+
mark_fail("agent-launch did not forward process lifetime through exec")
|
|
1154
|
+
else:
|
|
1155
|
+
if signal_status != -15:
|
|
1156
|
+
mark_fail(f"agent-launch SIGTERM status is {signal_status}, want -15")
|
|
1157
|
+
|
|
1158
|
+
invalid_args_profile = tmp / "invalid-args.toml"
|
|
1159
|
+
invalid_args_profile.write_text(
|
|
1160
|
+
profile_text.replace("passthrough_args = []", 'passthrough_args = "--bad"', 1)
|
|
1161
|
+
)
|
|
1162
|
+
invalid = invoke([
|
|
1163
|
+
sys.executable, str(launcher), "--config", str(invalid_args_profile),
|
|
1164
|
+
"--no-tui", "codex",
|
|
1165
|
+
], env=env)
|
|
1166
|
+
if invalid.returncode != 2 or "passthrough_args" not in invalid.stderr:
|
|
1167
|
+
mark_fail("agent-launch accepted string passthrough_args instead of failing closed")
|
|
1168
|
+
|
|
1169
|
+
invalid_delegation_profile = tmp / "invalid-delegation.toml"
|
|
1170
|
+
invalid_delegation_profile.write_text(
|
|
1171
|
+
profile_text.replace("delegation = true", 'delegation = "false"', 1)
|
|
1172
|
+
)
|
|
1173
|
+
invalid = invoke([
|
|
1174
|
+
sys.executable, str(launcher), "--config", str(invalid_delegation_profile),
|
|
1175
|
+
"--preset", "balanced", "--dry-run", "codex",
|
|
1176
|
+
], env=env)
|
|
1177
|
+
if invalid.returncode != 2 or "delegation must be boolean" not in invalid.stderr:
|
|
1178
|
+
mark_fail("agent-launch accepted non-boolean delegation instead of failing closed")
|
|
1179
|
+
|
|
1180
|
+
no_delegation_profile = tmp / "no-delegation.toml"
|
|
1181
|
+
no_delegation_profile.write_text(
|
|
1182
|
+
profile_text.replace(
|
|
1183
|
+
'review_setup = "native-panel"\ndelegation = true',
|
|
1184
|
+
'review_setup = "none"\ndelegation = false',
|
|
1185
|
+
1,
|
|
1186
|
+
)
|
|
1187
|
+
)
|
|
1188
|
+
no_delegation = invoke([
|
|
1189
|
+
sys.executable, str(launcher), "--config", str(no_delegation_profile),
|
|
1190
|
+
"--preset", "balanced", "--dry-run", "claude",
|
|
1191
|
+
], env=env)
|
|
1192
|
+
if no_delegation.returncode != 0:
|
|
1193
|
+
mark_fail(f"agent-launch rejected valid delegation=false: {no_delegation.stderr.strip()}")
|
|
1194
|
+
else:
|
|
1195
|
+
no_delegation_argv = json.loads(no_delegation.stdout.splitlines()[-1])
|
|
1196
|
+
if "--agents" in no_delegation_argv or "Delegation=off" not in " ".join(no_delegation_argv):
|
|
1197
|
+
mark_fail("agent-launch Claude delegation=false still projected child agents")
|
|
1198
|
+
|
|
1199
|
+
# The native-requires-delegation contradiction is a same-family concept
|
|
1200
|
+
# (cross native does not gate on delegation); test it under review_family=same.
|
|
1201
|
+
for review_setup in ("native-panel", "hybrid"):
|
|
1202
|
+
incompatible_review_profile = tmp / f"no-delegation-{review_setup}.toml"
|
|
1203
|
+
incompatible_review_profile.write_text(
|
|
1204
|
+
profile_text.replace(
|
|
1205
|
+
'review_setup = "native-panel"\ndelegation = true',
|
|
1206
|
+
f'review_setup = "{review_setup}"\nreview_family = "same"\ndelegation = false',
|
|
1207
|
+
1,
|
|
1208
|
+
)
|
|
1209
|
+
)
|
|
1210
|
+
incompatible_review = invoke([
|
|
1211
|
+
sys.executable,
|
|
1212
|
+
str(launcher),
|
|
1213
|
+
"--config",
|
|
1214
|
+
str(incompatible_review_profile),
|
|
1215
|
+
"--preset",
|
|
1216
|
+
"balanced",
|
|
1217
|
+
"--dry-run",
|
|
1218
|
+
"claude",
|
|
1219
|
+
], env=env)
|
|
1220
|
+
if (
|
|
1221
|
+
incompatible_review.returncode != 2
|
|
1222
|
+
or "requires delegation" not in incompatible_review.stderr
|
|
1223
|
+
):
|
|
1224
|
+
mark_fail(
|
|
1225
|
+
"agent-launch accepted delegation=false with review setup "
|
|
1226
|
+
f"{review_setup}"
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
for field in ("codex_execution_policy", "claude_permission_mode"):
|
|
1230
|
+
missing_policy_profile = tmp / f"missing-{field}.toml"
|
|
1231
|
+
missing_policy_profile.write_text(
|
|
1232
|
+
profile_text.replace(f'{field} = "bypass"\n', "", 1)
|
|
1233
|
+
if field == "codex_execution_policy"
|
|
1234
|
+
else profile_text.replace(f'{field} = "bypassPermissions"\n', "", 1)
|
|
1235
|
+
)
|
|
1236
|
+
missing_policy = invoke([
|
|
1237
|
+
sys.executable, str(launcher), "--config", str(missing_policy_profile),
|
|
1238
|
+
"--preset", "balanced", "--dry-run", "codex",
|
|
1239
|
+
], env=env)
|
|
1240
|
+
if missing_policy.returncode != 2 or field not in missing_policy.stderr:
|
|
1241
|
+
mark_fail(f"agent-launch accepted missing security policy: {field}")
|
|
1242
|
+
|
|
1243
|
+
for field, valid_value in (
|
|
1244
|
+
("codex_execution_policy", "bypass"),
|
|
1245
|
+
("claude_permission_mode", "bypassPermissions"),
|
|
1246
|
+
):
|
|
1247
|
+
invalid_policy_profile = tmp / f"invalid-{field}.toml"
|
|
1248
|
+
invalid_policy_profile.write_text(
|
|
1249
|
+
profile_text.replace(
|
|
1250
|
+
f'{field} = "{valid_value}"',
|
|
1251
|
+
f'{field} = "unsupported-policy"',
|
|
1252
|
+
1,
|
|
1253
|
+
)
|
|
1254
|
+
)
|
|
1255
|
+
invalid_policy = invoke([
|
|
1256
|
+
sys.executable,
|
|
1257
|
+
str(launcher),
|
|
1258
|
+
"--config",
|
|
1259
|
+
str(invalid_policy_profile),
|
|
1260
|
+
"--preset",
|
|
1261
|
+
"balanced",
|
|
1262
|
+
"--dry-run",
|
|
1263
|
+
"codex",
|
|
1264
|
+
], env=env)
|
|
1265
|
+
if invalid_policy.returncode != 2 or field not in invalid_policy.stderr:
|
|
1266
|
+
mark_fail(f"agent-launch accepted invalid security policy: {field}")
|
|
1267
|
+
|
|
1268
|
+
invalid_schema_profile = tmp / "invalid-schema.toml"
|
|
1269
|
+
invalid_schema_profile.write_text(profile_text.replace("schema_version = 1", "schema_version = true", 1))
|
|
1270
|
+
invalid = invoke([
|
|
1271
|
+
sys.executable, str(launcher), "--config", str(invalid_schema_profile),
|
|
1272
|
+
"--no-tui", "codex",
|
|
1273
|
+
], env=env)
|
|
1274
|
+
if invalid.returncode != 2 or "unsupported schema_version" not in invalid.stderr:
|
|
1275
|
+
mark_fail("agent-launch accepted boolean schema_version instead of failing closed")
|
|
1276
|
+
|
|
1277
|
+
invalid_review_profile = tmp / "invalid-review.toml"
|
|
1278
|
+
invalid_review_profile.write_text(
|
|
1279
|
+
profile_text.replace('review_setup = "native-panel"', 'review_setup = ["native-panel"]', 1)
|
|
1280
|
+
)
|
|
1281
|
+
invalid = invoke([
|
|
1282
|
+
sys.executable, str(launcher), "--config", str(invalid_review_profile),
|
|
1283
|
+
"--preset", "balanced", "--dry-run", "codex",
|
|
1284
|
+
], env=env)
|
|
1285
|
+
if invalid.returncode != 2 or "unknown review setup" not in invalid.stderr:
|
|
1286
|
+
mark_fail("agent-launch raised outside LaunchError for malformed review_setup")
|
|
1287
|
+
|
|
1288
|
+
invalid_models_profile = tmp / "invalid-models.toml"
|
|
1289
|
+
invalid_models_profile.write_text(
|
|
1290
|
+
profile_text.replace(
|
|
1291
|
+
'models = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]',
|
|
1292
|
+
"models = []",
|
|
1293
|
+
1,
|
|
1294
|
+
)
|
|
1295
|
+
)
|
|
1296
|
+
invalid = invoke([
|
|
1297
|
+
sys.executable, str(launcher), "--config", str(invalid_models_profile),
|
|
1298
|
+
"--preset", "balanced", "--dry-run", "codex",
|
|
1299
|
+
], env=env)
|
|
1300
|
+
if invalid.returncode != 2 or "must be a non-empty list of strings" not in invalid.stderr:
|
|
1301
|
+
mark_fail("agent-launch accepted an empty models catalog instead of failing closed")
|
|
1302
|
+
|
|
1303
|
+
invalid_claude_effort_profile = tmp / "invalid-claude-effort.toml"
|
|
1304
|
+
invalid_claude_effort_profile.write_text(
|
|
1305
|
+
profile_text.replace('codex = "ultra", claude = "max"', 'codex = "ultra", claude = "ultra"')
|
|
1306
|
+
)
|
|
1307
|
+
invalid = invoke([
|
|
1308
|
+
sys.executable, str(launcher), "--config", str(invalid_claude_effort_profile),
|
|
1309
|
+
"--preset", "deep-review", "--dry-run", "claude",
|
|
1310
|
+
], env=env)
|
|
1311
|
+
if invalid.returncode != 2 or "unsupported effort" not in invalid.stderr:
|
|
1312
|
+
mark_fail("agent-launch accepted Claude Ultra instead of failing closed")
|
|
1313
|
+
|
|
1314
|
+
bare_dry_run = invoke([
|
|
1315
|
+
sys.executable, str(launcher), "--dry-run", "codex",
|
|
1316
|
+
], env=env)
|
|
1317
|
+
if bare_dry_run.returncode != 0 or "Preset Balanced" not in bare_dry_run.stdout:
|
|
1318
|
+
mark_fail("agent-launch non-TTY bare --dry-run did not select Balanced")
|
|
1319
|
+
|
|
1320
|
+
no_balanced_profile = tmp / "no-balanced.toml"
|
|
1321
|
+
no_balanced_profile.write_text(
|
|
1322
|
+
profile_text.replace("[presets.balanced]", "[presets.daily]", 1)
|
|
1323
|
+
)
|
|
1324
|
+
no_balanced = invoke([
|
|
1325
|
+
sys.executable,
|
|
1326
|
+
str(launcher),
|
|
1327
|
+
"--config",
|
|
1328
|
+
str(no_balanced_profile),
|
|
1329
|
+
"--dry-run",
|
|
1330
|
+
"codex",
|
|
1331
|
+
], env=env)
|
|
1332
|
+
if (
|
|
1333
|
+
no_balanced.returncode != 2
|
|
1334
|
+
or "requires a 'balanced' preset" not in no_balanced.stderr
|
|
1335
|
+
):
|
|
1336
|
+
mark_fail(
|
|
1337
|
+
"agent-launch bare non-TTY dry-run selected an arbitrary custom preset"
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
configured_runtime = invoke([
|
|
1341
|
+
sys.executable, str(launcher), "--preset", "balanced", "--yes", "codex", "--", "probe"
|
|
1342
|
+
], env=env)
|
|
1343
|
+
if configured_runtime.stdout != "backend-output\n":
|
|
1344
|
+
mark_fail("configured non-TTY launch polluted backend stdout with its summary")
|
|
1345
|
+
if "Launch summary" not in configured_runtime.stderr:
|
|
1346
|
+
mark_fail("configured non-TTY launch did not disclose its summary on stderr")
|
|
1347
|
+
projected_agent_files = sorted((tmp / "cache/agent-launch/codex-agents").glob("*/*.toml"))
|
|
1348
|
+
if len(projected_agent_files) != 3:
|
|
1349
|
+
mark_fail("configured Codex launch did not materialize three pinned child agents")
|
|
1350
|
+
for path in projected_agent_files:
|
|
1351
|
+
data = tomllib.loads(path.read_text())
|
|
1352
|
+
tier = path.stem
|
|
1353
|
+
expected_model, expected_effort = expected_launch_tiers[tier]
|
|
1354
|
+
if (data.get("model"), data.get("model_reasoning_effort")) != (
|
|
1355
|
+
expected_model, expected_effort
|
|
1356
|
+
):
|
|
1357
|
+
mark_fail(f"materialized Codex {tier} binding drifted: {path}")
|
|
1358
|
+
|
|
1359
|
+
custom_codex_home = tmp / "custom-codex-home"
|
|
1360
|
+
(custom_codex_home / "agents").mkdir(parents=True)
|
|
1361
|
+
for tier in ("frontier", "workhorse", "sweep"):
|
|
1362
|
+
(custom_codex_home / "agents" / f"{tier}.toml").write_text(
|
|
1363
|
+
(agent_dir / f"{tier}.toml").read_text()
|
|
1364
|
+
)
|
|
1365
|
+
portable_profile = tmp / "portable.toml"
|
|
1366
|
+
portable_profile.write_text(portable_profile_text)
|
|
1367
|
+
portable_env = env.copy()
|
|
1368
|
+
portable_env.update(
|
|
1369
|
+
CODEX_HOME=str(custom_codex_home),
|
|
1370
|
+
XDG_CACHE_HOME=str(tmp / "portable-cache"),
|
|
1371
|
+
)
|
|
1372
|
+
portable = invoke([
|
|
1373
|
+
sys.executable, str(launcher), "--config", str(portable_profile),
|
|
1374
|
+
"--preset", "balanced", "--yes", "codex", "--", "probe",
|
|
1375
|
+
], env=portable_env)
|
|
1376
|
+
if portable.returncode != 0 or len(
|
|
1377
|
+
list((tmp / "portable-cache/agent-launch/codex-agents").glob("*/*.toml"))
|
|
1378
|
+
) != 3:
|
|
1379
|
+
mark_fail("configured Codex launch did not honor non-default CODEX_HOME")
|
|
1380
|
+
|
|
1381
|
+
env["AGENT_LAUNCH_DEBUG"] = "1"
|
|
1382
|
+
configured = invoke([
|
|
1383
|
+
sys.executable, str(launcher), "--preset", "balanced", "--yes", "--dry-run",
|
|
1384
|
+
"codex", "--", "exec", "probe",
|
|
1385
|
+
], env=env)
|
|
1386
|
+
expect_text(
|
|
1387
|
+
configured,
|
|
1388
|
+
"agent-launch Codex configured projection",
|
|
1389
|
+
(
|
|
1390
|
+
"configured/requested · completed: not enforced",
|
|
1391
|
+
'model_reasoning_effort=\\"xhigh\\"',
|
|
1392
|
+
"features.multi_agent=true",
|
|
1393
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
1394
|
+
"forwarded backend args appended last; may supersede defaults",
|
|
1395
|
+
'"exec", "probe"',
|
|
1396
|
+
),
|
|
1397
|
+
)
|
|
1398
|
+
if configured.returncode == 0:
|
|
1399
|
+
codex_argv = json.loads(configured.stdout.splitlines()[-1])
|
|
1400
|
+
if codex_argv[-2:] != ["exec", "probe"]:
|
|
1401
|
+
mark_fail("agent-launch Codex expert overrides were not appended last")
|
|
1402
|
+
configured = invoke([
|
|
1403
|
+
sys.executable, str(launcher), "--preset", "balanced", "--yes", "--dry-run",
|
|
1404
|
+
"claude", "--", "-p", "probe",
|
|
1405
|
+
], env=env)
|
|
1406
|
+
expect_text(
|
|
1407
|
+
configured,
|
|
1408
|
+
"agent-launch Claude configured projection",
|
|
1409
|
+
(
|
|
1410
|
+
"configured/requested · completed: not enforced",
|
|
1411
|
+
"--append-system-prompt",
|
|
1412
|
+
"--agents",
|
|
1413
|
+
"--dangerously-skip-permissions",
|
|
1414
|
+
'"-p", "probe"',
|
|
1415
|
+
),
|
|
1416
|
+
)
|
|
1417
|
+
if configured.returncode == 0:
|
|
1418
|
+
try:
|
|
1419
|
+
projected_argv = json.loads(configured.stdout.splitlines()[-1])
|
|
1420
|
+
agents_arg = json.loads(projected_argv[projected_argv.index("--agents") + 1])
|
|
1421
|
+
except (ValueError, IndexError, json.JSONDecodeError) as exc:
|
|
1422
|
+
mark_fail(f"agent-launch Claude --agents projection is not valid JSON: {exc}")
|
|
1423
|
+
else:
|
|
1424
|
+
for tier, (model, effort) in expected_claude_tiers.items():
|
|
1425
|
+
role = agents_arg.get(tier, {})
|
|
1426
|
+
if role.get("model") != model or role.get("effort") != effort:
|
|
1427
|
+
mark_fail(f"agent-launch Claude {tier} role projection drifted: {role!r}")
|
|
1428
|
+
if projected_argv[-2:] != ["-p", "probe"]:
|
|
1429
|
+
mark_fail(
|
|
1430
|
+
"agent-launch Claude expert overrides were not appended last"
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
# Cross-family default: each main routes every review route to the OPPOSITE
|
|
1434
|
+
# model family, with concrete tools/paths/bindings named in the contract.
|
|
1435
|
+
# The cross CODEX_HOME needs the reviewer wrappers (bin) and the same-family
|
|
1436
|
+
# agent templates (agents) that the codex fallback floor still materializes.
|
|
1437
|
+
cross_home = tmp / "cross-codex-home"
|
|
1438
|
+
(cross_home / "bin").mkdir(parents=True)
|
|
1439
|
+
(cross_home / "agents").mkdir(parents=True)
|
|
1440
|
+
for wrapper in ("codex-run", "codex-helm"):
|
|
1441
|
+
wrapper_path = cross_home / "bin" / wrapper
|
|
1442
|
+
wrapper_path.write_text("#!/usr/bin/env bash\nexit 0\n")
|
|
1443
|
+
wrapper_path.chmod(0o755)
|
|
1444
|
+
for tier in ("frontier", "workhorse", "sweep"):
|
|
1445
|
+
(cross_home / "agents" / f"{tier}.toml").write_text(
|
|
1446
|
+
(agent_dir / f"{tier}.toml").read_text()
|
|
1447
|
+
)
|
|
1448
|
+
cross_env = env.copy()
|
|
1449
|
+
cross_env["CODEX_HOME"] = str(cross_home)
|
|
1450
|
+
|
|
1451
|
+
claude_cross = invoke([
|
|
1452
|
+
sys.executable, str(launcher), "--preset", "deep-review", "--yes",
|
|
1453
|
+
"--dry-run", "claude",
|
|
1454
|
+
], env=cross_env)
|
|
1455
|
+
expect_text(
|
|
1456
|
+
claude_cross,
|
|
1457
|
+
"agent-launch Claude cross-family (gpt/codex) review projection",
|
|
1458
|
+
(
|
|
1459
|
+
"Review family=cross",
|
|
1460
|
+
"run EVERY review route on OpenAI/Codex",
|
|
1461
|
+
"codex-run --profile hermetic",
|
|
1462
|
+
"onto: call onto_review",
|
|
1463
|
+
"so onto runs OpenAI/Codex",
|
|
1464
|
+
f"{fake_ultracode} (the $ultracode-for-codex Codex skill)",
|
|
1465
|
+
"frontier=gpt-5.6-sol/max",
|
|
1466
|
+
),
|
|
1467
|
+
)
|
|
1468
|
+
codex_cross = invoke([
|
|
1469
|
+
sys.executable, str(launcher), "--preset", "deep-review", "--yes",
|
|
1470
|
+
"--dry-run", "codex",
|
|
1471
|
+
], env=cross_env)
|
|
1472
|
+
expect_text(
|
|
1473
|
+
codex_cross,
|
|
1474
|
+
"agent-launch Codex cross-family (anthropic/claude) review projection",
|
|
1475
|
+
(
|
|
1476
|
+
"Review family=cross",
|
|
1477
|
+
"run EVERY review route on Anthropic/Claude",
|
|
1478
|
+
"-p --model <review tier>",
|
|
1479
|
+
"--permission-mode plan",
|
|
1480
|
+
"onto: call onto_review",
|
|
1481
|
+
"so onto runs Anthropic/Claude",
|
|
1482
|
+
"--effort ultracode -p",
|
|
1483
|
+
"Claude Code /workflows ultracode mode, headless",
|
|
1484
|
+
"frontier=claude-fable-5/max",
|
|
1485
|
+
),
|
|
1486
|
+
)
|
|
1487
|
+
|
|
1488
|
+
# Module-level: the cross contract pins the exact onto llmOverride family+model.
|
|
1489
|
+
cross_cfg = tomllib.loads(profile_text)
|
|
1490
|
+
for main_host, provider, model in (
|
|
1491
|
+
("claude", "openai", "gpt-5.6-sol"),
|
|
1492
|
+
("codex", "anthropic", "claude-fable-5"),
|
|
1493
|
+
):
|
|
1494
|
+
cross_plan = launcher_module.build_plan(cross_cfg, main_host, "deep-review")
|
|
1495
|
+
contract = launcher_module.run_contract(cross_plan)
|
|
1496
|
+
expected = f'llmOverride={{"provider":"{provider}","model":"{model}"}}'
|
|
1497
|
+
if expected not in contract:
|
|
1498
|
+
mark_fail(f"agent-launch {main_host} cross onto llmOverride missing: {expected}")
|
|
1499
|
+
|
|
1500
|
+
# A config with only one host degrades cross review to same-family instead of
|
|
1501
|
+
# crashing on the absent opposite host.
|
|
1502
|
+
single_host_cfg = tomllib.loads(profile_text)
|
|
1503
|
+
single_host_cfg["hosts"].pop("claude", None)
|
|
1504
|
+
single_host_cfg["backends"].pop("claude", None)
|
|
1505
|
+
try:
|
|
1506
|
+
single_plan = launcher_module.build_plan(single_host_cfg, "codex", "balanced")
|
|
1507
|
+
if single_plan["review_family"] != "same":
|
|
1508
|
+
mark_fail("agent-launch single-host config did not coerce cross review to same")
|
|
1509
|
+
launcher_module.run_contract(single_plan)
|
|
1510
|
+
except launcher_module.LaunchError as exc:
|
|
1511
|
+
mark_fail(f"agent-launch single-host config raised instead of degrading: {exc}")
|
|
1512
|
+
except Exception as exc:
|
|
1513
|
+
mark_fail(f"agent-launch single-host config crashed (not a clean degrade): {exc!r}")
|
|
1514
|
+
|
|
1515
|
+
# An all-disabled option list raises a clean LaunchError, not a hang.
|
|
1516
|
+
try:
|
|
1517
|
+
launcher_module.choose(
|
|
1518
|
+
"empty", [launcher_module.MenuOption("a", "A", "d", enabled=False)], "a"
|
|
1519
|
+
)
|
|
1520
|
+
mark_fail("agent-launch choose accepted an all-disabled option list")
|
|
1521
|
+
except launcher_module.LaunchError:
|
|
1522
|
+
pass
|
|
1523
|
+
|
|
1524
|
+
# review_family=same reproduces the earlier same-family hybrid contract verbatim.
|
|
1525
|
+
same_family_profile = tmp / "same-family.toml"
|
|
1526
|
+
same_family_profile.write_text(
|
|
1527
|
+
profile_text.replace(
|
|
1528
|
+
'review_setup = "hybrid"', 'review_setup = "hybrid"\nreview_family = "same"', 1
|
|
1529
|
+
)
|
|
1530
|
+
)
|
|
1531
|
+
same_family_review = invoke([
|
|
1532
|
+
sys.executable, str(launcher), "--config", str(same_family_profile),
|
|
1533
|
+
"--preset", "deep-review", "--yes", "--dry-run", "claude",
|
|
1534
|
+
], env=env)
|
|
1535
|
+
expect_text(
|
|
1536
|
+
same_family_review,
|
|
1537
|
+
"agent-launch review_family=same restores same-family contract",
|
|
1538
|
+
(
|
|
1539
|
+
"Review family=same",
|
|
1540
|
+
"Codex-backed Ultracode review kinds",
|
|
1541
|
+
f"Ultracode executable: {fake_ultracode}",
|
|
1542
|
+
),
|
|
1543
|
+
("cross-family review",),
|
|
1544
|
+
)
|
|
1545
|
+
|
|
1546
|
+
frontier_profile = tmp / "frontier.toml"
|
|
1547
|
+
frontier_profile.write_text(
|
|
1548
|
+
profile_text.replace(
|
|
1549
|
+
'main_tier = "helm"\nfrontier_effort = "max"',
|
|
1550
|
+
'main_tier = "frontier"\nfrontier_effort = "ultra"',
|
|
1551
|
+
1,
|
|
1552
|
+
)
|
|
1553
|
+
)
|
|
1554
|
+
configured = invoke([
|
|
1555
|
+
sys.executable, str(launcher), "--config", str(frontier_profile),
|
|
1556
|
+
"--preset", "balanced", "--yes", "--dry-run", "codex",
|
|
1557
|
+
], env=env)
|
|
1558
|
+
expect_text(
|
|
1559
|
+
configured,
|
|
1560
|
+
"agent-launch FRONTIER main effort projection",
|
|
1561
|
+
('Main FRONTIER · gpt-5.6-sol · ultra', 'main=frontier (gpt-5.6-sol/ultra)'),
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
unavailable_profile = tmp / "unavailable.toml"
|
|
1565
|
+
unavailable_profile.write_text(
|
|
1566
|
+
profile_text.replace(
|
|
1567
|
+
f"command = {json.dumps(str(fake_onto))}",
|
|
1568
|
+
f"command = {json.dumps(str(tmp / 'missing-onto'))}",
|
|
1569
|
+
1,
|
|
1570
|
+
).replace(
|
|
1571
|
+
f"command = {json.dumps(str(fake_ultracode))}",
|
|
1572
|
+
f"command = {json.dumps(str(tmp / 'missing-ultracode'))}",
|
|
1573
|
+
1,
|
|
1574
|
+
).replace(
|
|
1575
|
+
'review_setup = "hybrid"', 'review_setup = "hybrid"\nreview_family = "same"', 1
|
|
1576
|
+
)
|
|
1577
|
+
)
|
|
1578
|
+
# Under review_family=same, missing external review capabilities (onto/ultracode
|
|
1579
|
+
# CLI absent) degrade to native same-model review instead of failing closed.
|
|
1580
|
+
unavailable = invoke([
|
|
1581
|
+
sys.executable, str(launcher), "--config", str(unavailable_profile),
|
|
1582
|
+
"--preset", "deep-review", "--yes", "--dry-run", "codex",
|
|
1583
|
+
], env=env)
|
|
1584
|
+
expect_text(
|
|
1585
|
+
unavailable,
|
|
1586
|
+
"agent-launch codex degrade-to-native review",
|
|
1587
|
+
(
|
|
1588
|
+
"Review setup hybrid → effective native (onto,ultracode unavailable)",
|
|
1589
|
+
"degraded to native same-model",
|
|
1590
|
+
"fall back to native same-model subagent review",
|
|
1591
|
+
),
|
|
1592
|
+
("mcp_servers.onto.enabled=true", "Ultracode executable:"),
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
unavailable_claude = invoke([
|
|
1596
|
+
sys.executable, str(launcher), "--config", str(unavailable_profile),
|
|
1597
|
+
"--preset", "deep-review", "--yes", "--dry-run", "claude",
|
|
1598
|
+
], env=env)
|
|
1599
|
+
if expect_text(
|
|
1600
|
+
unavailable_claude,
|
|
1601
|
+
"agent-launch claude degrade-to-native review",
|
|
1602
|
+
(
|
|
1603
|
+
"Review setup hybrid → effective native (onto,ultracode unavailable)",
|
|
1604
|
+
"fall back to native same-model subagent review",
|
|
1605
|
+
),
|
|
1606
|
+
("--mcp-config",),
|
|
1607
|
+
):
|
|
1608
|
+
claude_degraded_argv = json.loads(unavailable_claude.stdout.splitlines()[-1])
|
|
1609
|
+
if "--agents" not in claude_degraded_argv:
|
|
1610
|
+
mark_fail("agent-launch claude degrade dropped native --agents review")
|
|
1611
|
+
|
|
1612
|
+
# Stage C: a named preset save round-trips and stays host-scoped.
|
|
1613
|
+
save_target = tmp / "save-target.toml"
|
|
1614
|
+
save_target.write_text(profile_text)
|
|
1615
|
+
save_cfg = tomllib.loads(save_target.read_text())
|
|
1616
|
+
save_plan = launcher_module.build_plan(save_cfg, "codex", "balanced")
|
|
1617
|
+
save_plan["main_tier"] = "workhorse"
|
|
1618
|
+
save_plan["tiers"]["workhorse"]["model"] = "custom-wh"
|
|
1619
|
+
save_plan["tiers"]["workhorse"]["effort"] = "xhigh"
|
|
1620
|
+
save_plan["frontier_effort"] = "ultra"
|
|
1621
|
+
launcher_module.save_preset(save_plan, save_cfg, save_target, "mysetup")
|
|
1622
|
+
reloaded = launcher_module.load_config(save_target)
|
|
1623
|
+
if "mysetup" not in reloaded["presets"] or "balanced" not in reloaded["presets"]:
|
|
1624
|
+
mark_fail("save_preset did not persist the preset or clobbered existing ones")
|
|
1625
|
+
else:
|
|
1626
|
+
round_trip = launcher_module.build_plan(reloaded, "codex", "mysetup")
|
|
1627
|
+
if (
|
|
1628
|
+
round_trip["main_tier"] != "workhorse"
|
|
1629
|
+
or round_trip["tiers"]["workhorse"] != {"model": "custom-wh", "effort": "xhigh"}
|
|
1630
|
+
or launcher_module.tier_effort(round_trip, "frontier") != "ultra"
|
|
1631
|
+
):
|
|
1632
|
+
mark_fail(f"saved preset did not round-trip: {round_trip['tiers']}")
|
|
1633
|
+
claude_view = launcher_module.build_plan(reloaded, "claude", "mysetup")
|
|
1634
|
+
if (
|
|
1635
|
+
claude_view["tiers"]["workhorse"]["model"]
|
|
1636
|
+
!= save_cfg["hosts"]["claude"]["tiers"]["workhorse"]["model"]
|
|
1637
|
+
):
|
|
1638
|
+
mark_fail("saved codex tier override leaked into the claude host")
|
|
1639
|
+
launcher_module.save_preset(round_trip, reloaded, save_target, "mysetup")
|
|
1640
|
+
if save_target.read_text().count("[presets.mysetup]") != 1:
|
|
1641
|
+
mark_fail("re-saving a preset duplicated its block")
|
|
1642
|
+
|
|
1643
|
+
# The Custom hub 'save' action persists the named preset then launches.
|
|
1644
|
+
ui_save_target = tmp / "ui-save.toml"
|
|
1645
|
+
ui_save_target.write_text(profile_text)
|
|
1646
|
+
if argv_log.exists():
|
|
1647
|
+
argv_log.unlink()
|
|
1648
|
+
ui_save = invoke([
|
|
1649
|
+
sys.executable, str(launcher), "--config", str(ui_save_target),
|
|
1650
|
+
"--preset", "balanced", "--custom", "codex",
|
|
1651
|
+
], input_text="8\ndaily-driver\n", env=env)
|
|
1652
|
+
if ui_save.returncode != 0 or not argv_log.exists():
|
|
1653
|
+
mark_fail(f"agent-launch custom save-and-start did not launch: {ui_save.stderr.strip()}")
|
|
1654
|
+
elif "daily-driver" not in launcher_module.load_config(ui_save_target)["presets"]:
|
|
1655
|
+
mark_fail("agent-launch custom hub save did not persist the named preset")
|
|
1656
|
+
|
|
1657
|
+
shell_bin = tmp / "shell-bin"
|
|
1658
|
+
shell_bin.mkdir()
|
|
1659
|
+
dispatch_log = tmp / "dispatch.argv"
|
|
1660
|
+
shell_backend = shell_bin / "backend"
|
|
1661
|
+
shell_backend.write_text(
|
|
1662
|
+
"#!/usr/bin/env bash\n"
|
|
1663
|
+
"printf '%s\\n' \"$(basename \"$0\")\" \"$@\" > \"$FAKE_DISPATCH_ARGV\"\n"
|
|
1664
|
+
)
|
|
1665
|
+
shell_backend.chmod(0o755)
|
|
1666
|
+
(shell_bin / "codex").symlink_to(shell_backend)
|
|
1667
|
+
(shell_bin / "claude").symlink_to(shell_backend)
|
|
1668
|
+
shell_env = env.copy()
|
|
1669
|
+
shell_env.update(
|
|
1670
|
+
PATH=f"{shell_bin}{os.pathsep}{shell_env.get('PATH', '')}",
|
|
1671
|
+
FAKE_DISPATCH_ARGV=str(dispatch_log),
|
|
1672
|
+
)
|
|
1673
|
+
for command, expected in (
|
|
1674
|
+
("codex exec probe", ["codex", "exec", "probe"]),
|
|
1675
|
+
("claude --no-tui -p probe", ["claude", "--dangerously-skip-permissions", "-p", "probe"]),
|
|
1676
|
+
("codex", ["codex"]),
|
|
1677
|
+
):
|
|
1678
|
+
shell_result = invoke([
|
|
1679
|
+
"zsh", "-f", "-c", f"source {shell_init}; {command}"
|
|
1680
|
+
], env=shell_env)
|
|
1681
|
+
received = dispatch_log.read_text().splitlines() if dispatch_log.is_file() else []
|
|
1682
|
+
if shell_result.returncode != 0 or received != expected:
|
|
1683
|
+
mark_fail(f"shell dispatch for {command!r} is {received!r}, want {expected!r}")
|
|
1684
|
+
alias_result = invoke([
|
|
1685
|
+
"zsh", "-f", "-c",
|
|
1686
|
+
f"alias codex='false'; alias claude='false'; source {shell_init}; codex exec alias-probe",
|
|
1687
|
+
], env=shell_env)
|
|
1688
|
+
received = dispatch_log.read_text().splitlines() if dispatch_log.is_file() else []
|
|
1689
|
+
if alias_result.returncode != 0 or received != [
|
|
1690
|
+
"codex", "exec", "alias-probe"
|
|
1691
|
+
]:
|
|
1692
|
+
mark_fail("shell init did not replace pre-existing codex/claude aliases")
|
|
1693
|
+
|
|
1694
|
+
if fail == 0:
|
|
1695
|
+
print("LAUNCH/BINDINGS OK: profile projections, bypass paths, role slots, and wrappers aligned")
|
|
1696
|
+
sys.exit(fail)
|
|
1697
|
+
PY
|
|
1698
|
+
then
|
|
1699
|
+
fail=1
|
|
1700
|
+
fi
|
|
1701
|
+
|
|
1702
|
+
[ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
|
|
1703
|
+
exit "$fail"
|