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.
Files changed (35) hide show
  1. package/DEPENDENCIES.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +86 -0
  4. package/claude/CLAUDE.md +138 -0
  5. package/claude/guides/cli-multi-model-workflow.md +194 -0
  6. package/claude/guides/coding-staged-workflow.md +70 -0
  7. package/claude/guides/implementation-map.md +34 -0
  8. package/claude/guides/llm-capability-boundary-examples.md +123 -0
  9. package/claude/guides/llm-capability-boundary-patterns.md +339 -0
  10. package/claude/guides/llm-capability-boundary.md +255 -0
  11. package/claude/guides/mock-realization-boundary.md +275 -0
  12. package/claude/guides/svg-visualization-guide.md +321 -0
  13. package/codex/AGENTS.md +139 -0
  14. package/codex/agents/frontier.toml +8 -0
  15. package/codex/agents/reviewer.toml +9 -0
  16. package/codex/agents/sweep.toml +9 -0
  17. package/codex/agents/workhorse.toml +8 -0
  18. package/codex/guides/cli-multi-model-workflow.md +194 -0
  19. package/codex/guides/coding-staged-workflow.md +70 -0
  20. package/codex/guides/implementation-map.md +34 -0
  21. package/codex/guides/llm-capability-boundary-examples.md +123 -0
  22. package/codex/guides/llm-capability-boundary-patterns.md +339 -0
  23. package/codex/guides/llm-capability-boundary.md +255 -0
  24. package/codex/guides/mock-realization-boundary.md +275 -0
  25. package/codex/guides/svg-visualization-guide.md +321 -0
  26. package/config/agent-launch.toml +94 -0
  27. package/package.json +54 -0
  28. package/scripts/agent-launch.py +1742 -0
  29. package/scripts/check-parity.sh +1703 -0
  30. package/scripts/codex-helm.sh +370 -0
  31. package/scripts/codex-run.sh +176 -0
  32. package/scripts/install.sh +310 -0
  33. package/scripts/provision-venv.sh +28 -0
  34. package/scripts/session-cost.py +106 -0
  35. package/shell/agent-launch.zsh +38 -0
@@ -0,0 +1,1742 @@
1
+ #!/usr/bin/env python3
2
+ """Interactive preflight launcher for Codex and Claude."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import copy
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import pathlib
12
+ import shutil
13
+ import sys
14
+ import textwrap
15
+ import tomllib
16
+ from dataclasses import dataclass
17
+ from typing import Any, NoReturn
18
+
19
+
20
+ TIER_ORDER = ("frontier", "helm", "workhorse", "sweep")
21
+ EFFORT_ORDER = ("low", "medium", "high", "xhigh", "max", "ultra")
22
+ HOST_EFFORTS = {
23
+ "codex": {"low", "medium", "high", "xhigh", "max", "ultra"},
24
+ "claude": {"low", "medium", "high", "xhigh", "max"},
25
+ }
26
+
27
+ TIER_DESCRIPTIONS = {
28
+ "frontier": "Highest-capability specialist for hard, high-impact work and deep review.",
29
+ "helm": "Primary orchestrator for planning, delegation, integration, and final decisions.",
30
+ "workhorse": "Cost-conscious executor for high-volume implementation and analysis.",
31
+ "sweep": "Fast, low-cost worker for narrow searches, checks, and mechanical tasks.",
32
+ }
33
+ EFFORT_DESCRIPTIONS = {
34
+ "low": "Minimize reasoning cost and latency for straightforward work.",
35
+ "medium": "Use moderate reasoning for routine work with some ambiguity.",
36
+ "high": "Spend more reasoning on complex implementation and analysis.",
37
+ "xhigh": "Use deeper reasoning for difficult coordination and high-impact decisions.",
38
+ "max": "Use the host's strongest standard reasoning mode for frontier work.",
39
+ "ultra": "Use Codex Ultra for exceptionally deep, divisible frontier work.",
40
+ }
41
+ CUSTOM_PRESET = "__custom__"
42
+ OTHER_MODEL = "__other_model__"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class MenuOption:
47
+ value: str
48
+ label: str
49
+ description: str
50
+ enabled: bool = True
51
+ unavailable_reason: str = ""
52
+ CODEX_POLICIES = {"bypass", "workspace-write", "read-only"}
53
+ CLAUDE_POLICIES = {"acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"}
54
+ REVIEW_SETUPS = {
55
+ "none": {
56
+ "label": "None",
57
+ "requirements": {"codex": (), "claude": ()},
58
+ "contract": "No additional review route requested.",
59
+ },
60
+ "native-panel": {
61
+ "label": "Native panel",
62
+ "requirements": {"codex": (), "claude": ()},
63
+ "contract": "Use native multi-perspective subagents when review gates fire.",
64
+ },
65
+ "onto": {
66
+ "label": "onto-mcp",
67
+ "requirements": {"codex": ("onto",), "claude": ("onto",)},
68
+ "contract": "Use onto-mcp as the structured review lens when review gates fire.",
69
+ },
70
+ "ultracode": {
71
+ "label": "Ultracode",
72
+ "requirements": {"codex": ("ultracode",), "claude": ("ultracode",)},
73
+ "contract": (
74
+ "Run the configured Ultracode CLI as a Codex-backed review route when "
75
+ "review gates fire."
76
+ ),
77
+ },
78
+ "hybrid": {
79
+ "label": "Hybrid",
80
+ "requirements": {
81
+ "codex": ("onto", "ultracode"),
82
+ "claude": ("onto", "ultracode"),
83
+ },
84
+ "contract": {
85
+ "codex": "Use onto plus native and Codex-backed Ultracode review kinds; act on their union.",
86
+ "claude": "Use onto plus native multi-perspective and Codex-backed Ultracode review kinds; act on their union.",
87
+ },
88
+ },
89
+ }
90
+
91
+ # Each review setup decomposes into review routes. "native" is same-model
92
+ # multi-perspective subagent review (needs delegation); "onto"/"ultracode" are
93
+ # external routes gated on a resolvable capability command. A missing external
94
+ # route degrades to native instead of failing closed; only the delegation
95
+ # contradiction (native required with delegation off) stays fail-closed.
96
+ REVIEW_ROUTES = {
97
+ "none": (),
98
+ "native-panel": ("native",),
99
+ "onto": ("onto",),
100
+ "ultracode": ("ultracode",),
101
+ "hybrid": ("onto", "native", "ultracode"),
102
+ }
103
+ ROUTE_LABELS = {
104
+ "native": "native same-model multi-perspective review",
105
+ "onto": "onto structured-lens review",
106
+ "ultracode": "Codex-backed Ultracode review",
107
+ }
108
+ # review_family selects whether review runs on the opposite model family (cross,
109
+ # the default) or the same family as the main (same, today's projection).
110
+ REVIEW_FAMILIES = {"cross", "same"}
111
+ # onto llmOverride provider enum (onto MCP tool schema, settings-chain).
112
+ ONTO_PROVIDERS = {"openai", "anthropic", "grok", "lmstudio"}
113
+ # The opposite model family for cross review, keyed on the launch host.
114
+ REVIEW_HOST = {"codex": "claude", "claude": "codex"}
115
+
116
+
117
+ class LaunchError(RuntimeError):
118
+ pass
119
+
120
+
121
+ class BackRequested(RuntimeError):
122
+ pass
123
+
124
+
125
+ def resolve_command(value: str) -> str:
126
+ expanded = os.path.expanduser(value)
127
+ if os.path.sep in expanded:
128
+ command = expanded if pathlib.Path(expanded).is_file() and os.access(expanded, os.X_OK) else None
129
+ else:
130
+ command = shutil.which(expanded)
131
+ if not command:
132
+ raise LaunchError(f"executable not found: {value}")
133
+ return command
134
+
135
+
136
+ def expand_config_path(value: str) -> pathlib.Path:
137
+ codex_home = os.environ.get("CODEX_HOME", str(pathlib.Path.home() / ".codex"))
138
+ expanded = value.replace("${CODEX_HOME}", codex_home)
139
+ return pathlib.Path(os.path.expandvars(os.path.expanduser(expanded)))
140
+
141
+
142
+ def exec_backend(command: str, args: list[str], env: dict[str, str] | None = None) -> NoReturn:
143
+ os.execve(command, [command, *args], os.environ.copy() if env is None else env)
144
+
145
+
146
+ def default_config_path() -> pathlib.Path:
147
+ explicit = os.environ.get("AGENT_LAUNCH_CONFIG")
148
+ if explicit:
149
+ return pathlib.Path(explicit).expanduser()
150
+ installed = pathlib.Path.home() / ".config/agent-launch/profiles.toml"
151
+ if installed.is_file():
152
+ return installed
153
+ return pathlib.Path(__file__).resolve().parent.parent / "config/agent-launch.toml"
154
+
155
+
156
+ def load_config(path: pathlib.Path) -> dict[str, Any]:
157
+ try:
158
+ data = tomllib.loads(path.read_text())
159
+ except (OSError, tomllib.TOMLDecodeError) as exc:
160
+ raise LaunchError(f"cannot load config {path}: {exc}") from exc
161
+ if type(data.get("schema_version")) is not int or data["schema_version"] != 1:
162
+ raise LaunchError(f"unsupported schema_version in {path}")
163
+ for key in ("backends", "hosts", "presets"):
164
+ if not isinstance(data.get(key), dict) or not data[key]:
165
+ raise LaunchError(f"config requires non-empty [{key}]")
166
+ capabilities = data.get("capabilities", {})
167
+ if not isinstance(capabilities, dict):
168
+ raise LaunchError("[capabilities] must be a table")
169
+ for name, capability in capabilities.items():
170
+ if not isinstance(capability, dict):
171
+ raise LaunchError(f"capability must be a table: {name}")
172
+ onto = capabilities.get("onto")
173
+ if onto is not None and (
174
+ not isinstance(onto.get("command"), str) or not onto["command"]
175
+ ):
176
+ raise LaunchError("capabilities.onto.command must be a non-empty string")
177
+ ultracode = capabilities.get("ultracode")
178
+ if ultracode is not None and (
179
+ not isinstance(ultracode.get("command"), str) or not ultracode["command"]
180
+ ):
181
+ raise LaunchError("capabilities.ultracode.command must be a non-empty string")
182
+ for host_name, host_data in data["hosts"].items():
183
+ if not isinstance(host_data, dict):
184
+ raise LaunchError(f"host must be a table: {host_name}")
185
+ models = host_data.get("models")
186
+ if models is not None and (
187
+ not isinstance(models, list)
188
+ or not models
189
+ or not all(isinstance(model, str) and model for model in models)
190
+ ):
191
+ raise LaunchError(
192
+ f"hosts.{host_name}.models must be a non-empty list of strings"
193
+ )
194
+ onto_review = host_data.get("onto_review")
195
+ if onto_review is not None:
196
+ if not isinstance(onto_review, dict):
197
+ raise LaunchError(f"hosts.{host_name}.onto_review must be a table")
198
+ if onto_review.get("provider") not in ONTO_PROVIDERS:
199
+ choices = ", ".join(sorted(ONTO_PROVIDERS))
200
+ raise LaunchError(
201
+ f"hosts.{host_name}.onto_review.provider must be one of: {choices}"
202
+ )
203
+ if not isinstance(onto_review.get("model"), str) or not onto_review["model"]:
204
+ raise LaunchError(
205
+ f"hosts.{host_name}.onto_review.model must be a non-empty string"
206
+ )
207
+ for name, preset in data["presets"].items():
208
+ if not isinstance(preset, dict):
209
+ raise LaunchError(f"preset must be a table: {name}")
210
+ label = preset.get("label")
211
+ if not isinstance(label, str) or not label:
212
+ raise LaunchError(f"presets.{name}.label must be a non-empty string")
213
+ description = preset.get("description")
214
+ if description is not None and (
215
+ not isinstance(description, str) or not description
216
+ ):
217
+ raise LaunchError(f"presets.{name}.description must be a non-empty string")
218
+ for field, allowed in (
219
+ ("codex_execution_policy", CODEX_POLICIES),
220
+ ("claude_permission_mode", CLAUDE_POLICIES),
221
+ ):
222
+ value = preset.get(field)
223
+ if not isinstance(value, str) or value not in allowed:
224
+ choices = ", ".join(sorted(allowed))
225
+ raise LaunchError(f"presets.{name}.{field} must be one of: {choices}")
226
+ review_family = preset.get("review_family")
227
+ if review_family is not None and review_family not in REVIEW_FAMILIES:
228
+ raise LaunchError(f"presets.{name}.review_family must be one of: cross, same")
229
+ return data
230
+
231
+
232
+ def resolve_backend(config: dict[str, Any], host: str) -> tuple[str, list[str]]:
233
+ try:
234
+ backend = config["backends"][host]
235
+ command_value = backend["command"]
236
+ passthrough_value = backend.get("passthrough_args", [])
237
+ except (KeyError, TypeError) as exc:
238
+ raise LaunchError(f"backend is not configured for {host}") from exc
239
+ if not isinstance(command_value, str) or not command_value:
240
+ raise LaunchError(f"backend command must be a non-empty string: {host}")
241
+ command = resolve_command(command_value)
242
+ if not isinstance(passthrough_value, list) or not all(
243
+ isinstance(arg, str) for arg in passthrough_value
244
+ ):
245
+ raise LaunchError(f"backend passthrough_args must be strings: {host}")
246
+ return command, passthrough_value
247
+
248
+
249
+ def validate_effort(host: str, model: str, effort: Any, context: str) -> str:
250
+ if not isinstance(effort, str) or effort not in HOST_EFFORTS[host]:
251
+ raise LaunchError(f"unsupported effort for {context}: {effort!r}")
252
+ if host == "codex" and model == "gpt-5.6-luna" and effort == "ultra":
253
+ raise LaunchError(f"unsupported effort for {context}: gpt-5.6-luna/ultra")
254
+ return effort
255
+
256
+
257
+ def _resolves(value: str) -> bool:
258
+ try:
259
+ resolve_command(value)
260
+ return True
261
+ except LaunchError:
262
+ return False
263
+
264
+
265
+ def cross_native_command(plan: dict[str, Any]) -> str | None:
266
+ """Absolute command a cross-family main dispatches to for native review, or
267
+ None if unresolvable. The codex reviewer wrapper lives off PATH under
268
+ CODEX_HOME/bin; the claude reviewer is the claude backend on PATH."""
269
+ if plan["review_host"] == "codex":
270
+ path = expand_config_path("${CODEX_HOME}/bin/codex-run")
271
+ return str(path) if path.is_file() and os.access(path, os.X_OK) else None
272
+ try:
273
+ return resolve_command(plan["review_backend"])
274
+ except LaunchError:
275
+ return None
276
+
277
+
278
+ def cross_helm_command(plan: dict[str, Any]) -> str | None:
279
+ """The codex fan-out reviewer wrapper (hybrid), off PATH under CODEX_HOME/bin."""
280
+ if plan["review_host"] != "codex":
281
+ return None
282
+ path = expand_config_path("${CODEX_HOME}/bin/codex-helm")
283
+ return str(path) if path.is_file() and os.access(path, os.X_OK) else None
284
+
285
+
286
+ def cross_ultracode_command(plan: dict[str, Any]) -> str | None:
287
+ """The cross-family ultracode reviewer command, or None. For a claude main it
288
+ is the Codex-backed ultracode-for-codex (the $ultracode-for-codex skill); for
289
+ a codex main it is the claude backend (Claude Code /workflows ultracode)."""
290
+ if plan["review_host"] == "codex":
291
+ value = plan.get("capabilities", {}).get("ultracode", {}).get("command", "")
292
+ else:
293
+ value = plan["review_backend"]
294
+ try:
295
+ return resolve_command(value)
296
+ except LaunchError:
297
+ return None
298
+
299
+
300
+ def route_availability(plan: dict[str, Any]) -> dict[str, bool]:
301
+ """Which review routes can run right now. In same-family mode native gates on
302
+ delegation and onto/ultracode on a resolvable capability. In cross-family mode
303
+ native/ultracode gate on the opposite-family dispatcher resolving, and onto on
304
+ a mounted onto plus a review_onto pin to flip its family."""
305
+ capabilities = plan.get("capabilities", {})
306
+ if plan.get("review_family", "cross") == "same":
307
+ available = {"native": bool(plan["delegation"])}
308
+ for route, capability in (("onto", "onto"), ("ultracode", "ultracode")):
309
+ available[route] = _resolves(capabilities.get(capability, {}).get("command", ""))
310
+ return available
311
+ onto_ok = _resolves(capabilities.get("onto", {}).get("command", "")) and bool(
312
+ plan.get("review_onto")
313
+ )
314
+ return {
315
+ "native": cross_native_command(plan) is not None,
316
+ "onto": onto_ok,
317
+ "ultracode": cross_ultracode_command(plan) is not None,
318
+ }
319
+
320
+
321
+ def effective_review(plan: dict[str, Any]) -> tuple[list[str], list[str], str | None]:
322
+ """Resolve the requested review setup to the routes that can actually run.
323
+
324
+ Returns (effective_routes, dropped_routes, floor). In same-family mode this
325
+ reproduces the earlier degrade-to-native behavior (floor None; a dropped
326
+ external route degrades to native in effective; native requires delegation).
327
+ In cross-family mode the effective routes run on the opposite family; when no
328
+ cross route resolves and delegation is on, floor='native' marks a same-family
329
+ PROPOSED fallback, and a requested non-none setup with no cross route and no
330
+ fallback (delegation off) fails closed."""
331
+ requested = plan["review_setup"]
332
+ if requested not in REVIEW_ROUTES:
333
+ raise LaunchError(f"unknown review setup: {requested!r}")
334
+ wanted = list(REVIEW_ROUTES[requested])
335
+ available = route_availability(plan)
336
+ if plan.get("review_family", "cross") == "same":
337
+ if "native" in wanted and not plan["delegation"]:
338
+ raise LaunchError(f"review setup {requested!r} requires delegation")
339
+ effective = [route for route in wanted if available[route]]
340
+ dropped = [route for route in wanted if not available[route]]
341
+ if dropped and "native" not in effective and plan["delegation"]:
342
+ effective.append("native")
343
+ return effective, dropped, None
344
+ effective = [route for route in wanted if available[route]]
345
+ dropped = [route for route in wanted if not available[route]]
346
+ floor = None
347
+ if not effective and requested != "none":
348
+ if plan["delegation"]:
349
+ floor = "native"
350
+ else:
351
+ raise LaunchError(
352
+ f"review setup {requested!r} has no available cross-family route "
353
+ "and no same-family fallback (delegation off)"
354
+ )
355
+ return effective, dropped, floor
356
+
357
+
358
+ def setup_summary_lines(plan: dict[str, Any] | None) -> list[str]:
359
+ if plan is None:
360
+ return ["No setup selected."]
361
+ host = plan["host"]
362
+ execution = (
363
+ plan["codex_execution_policy"]
364
+ if host == "codex"
365
+ else plan["claude_permission_mode"]
366
+ )
367
+ lines = [
368
+ f"Host {host} | Preset {plan['label']}",
369
+ f"Main {plan['main_tier'].upper()} | Review {plan['review_setup']}",
370
+ f"Delegation {'on' if plan['delegation'] else 'off'} | Execution {execution}",
371
+ ]
372
+ for tier in TIER_ORDER:
373
+ binding = plan["tiers"][tier]
374
+ effort = (
375
+ plan["frontier_effort"] if tier == "frontier" else binding["effort"]
376
+ )
377
+ lines.append(f"{tier.upper():<10} {binding['model']} / {effort}")
378
+ return lines
379
+
380
+
381
+ def textual_importable() -> bool:
382
+ try:
383
+ import textual # noqa: F401
384
+ except Exception:
385
+ return False
386
+ return True
387
+
388
+
389
+ def venv_python() -> pathlib.Path | None:
390
+ """Locate a managed venv interpreter that provides textual. AGENT_LAUNCH_VENV
391
+ overrides the default install location."""
392
+ roots = []
393
+ override = os.environ.get("AGENT_LAUNCH_VENV")
394
+ if override:
395
+ roots.append(pathlib.Path(override))
396
+ roots.append(pathlib.Path.home() / ".local/share/agent-launch/venv")
397
+ for root in roots:
398
+ interpreter = root.expanduser() / "bin" / "python"
399
+ if interpreter.is_file() and os.access(interpreter, os.X_OK):
400
+ return interpreter
401
+ return None
402
+
403
+
404
+ def maybe_reexec_into_venv() -> None:
405
+ """When textual is not importable under the current interpreter, re-exec once
406
+ into the managed venv that provides it. Guarded against infinite re-exec and
407
+ only ever reached on the interactive TUI path, so direct/non-TTY launches keep
408
+ running under the system interpreter."""
409
+ if os.environ.get("AGENT_LAUNCH_REEXEC") == "1":
410
+ return
411
+ interpreter = venv_python()
412
+ if interpreter is None:
413
+ return
414
+ env = os.environ.copy()
415
+ env["AGENT_LAUNCH_REEXEC"] = "1"
416
+ os.execve(str(interpreter), [str(interpreter), *sys.argv], env)
417
+
418
+
419
+ # --- Textual preflight UI (optional; the numbered fallback covers its absence) ---
420
+ # select_plan/customize call the same choose()/prompt_text() seam regardless of
421
+ # renderer. TextualUI drives Textual screens from a worker thread via
422
+ # call_from_thread(push_screen_wait), preserving the synchronous controller flow
423
+ # and its BackRequested/KeyboardInterrupt contract.
424
+
425
+ _UI_BACK = "\x00back"
426
+ _UI_CANCEL = "\x00cancel"
427
+
428
+
429
+ def _build_app_class():
430
+ """Import textual lazily and build the App/Screen classes, so importing this
431
+ module and every non-interactive path stays free of the textual dependency."""
432
+ from textual import work
433
+ from textual.app import App
434
+ from textual.binding import Binding
435
+ from textual.screen import ModalScreen
436
+ from textual.widgets import Input, OptionList, Static
437
+ from textual.widgets.option_list import Option
438
+ from textual.theme import Theme
439
+
440
+ # Host-matched palettes so the preflight reads as the CLI it launches.
441
+ # Both are taken from the host's own artifact: Codex from its ~/.codex dark
442
+ # appearance theme (accent #339cff, surface #181818, diff #40c977/#fa423e),
443
+ # Claude from its binary's dark palette (coral #da7756, surface #1a1a19,
444
+ # ink scale #f9f9f7 / #c3c2b7 / #898781, grid #2c2c2a).
445
+ host_themes = {
446
+ "codex": Theme(
447
+ name="codex", primary="#339cff", secondary="#ad7bf9",
448
+ accent="#5fb0ff", foreground="#ffffff", background="#141414",
449
+ surface="#181818", panel="#222222", success="#40c977",
450
+ warning="#e6a700", error="#fa423e", dark=True,
451
+ ),
452
+ "claude": Theme(
453
+ name="claude", primary="#da7756", secondary="#c3c2b7",
454
+ accent="#da7756", foreground="#f9f9f7", background="#0d0d0d",
455
+ surface="#1a1a19", panel="#2c2c2a", success="#0ca30c",
456
+ warning="#fab219", error="#e06c6c", dark=True,
457
+ ),
458
+ }
459
+
460
+ app_css = """
461
+ Screen { background: $surface; }
462
+ #al-title { color: $accent; text-style: bold; padding: 0 1; }
463
+ #al-setup {
464
+ border: round $primary; border-title-color: $primary;
465
+ border-title-style: bold; padding: 0 1; height: auto;
466
+ }
467
+ #al-detail {
468
+ border: round $secondary; border-title-color: $secondary;
469
+ border-title-style: bold; padding: 0 1; height: 5;
470
+ }
471
+ #al-hdr { color: $text-muted; text-style: bold; padding: 0 1; }
472
+ OptionList { height: 1fr; border: none; padding: 0 1; }
473
+ #al-footer { color: $text-muted; dock: bottom; padding: 0 1; background: $panel; }
474
+ Input { margin: 0 1; }
475
+ """
476
+
477
+ def setup_panel(plan):
478
+ panel = Static("\n".join(setup_summary_lines(plan)), id="al-setup")
479
+ panel.border_title = "Current setup"
480
+ return panel
481
+
482
+ class MenuScreen(ModalScreen):
483
+ BINDINGS = [
484
+ Binding("escape", "back", "back", priority=True),
485
+ Binding("q", "cancel", "cancel", priority=True),
486
+ Binding("ctrl+c", "cancel", "cancel", priority=True),
487
+ ]
488
+
489
+ def __init__(self, title, options, default, allow_back, plan, preview=None):
490
+ super().__init__()
491
+ self._title = title
492
+ self._options = options
493
+ self._default = default
494
+ self._allow_back = allow_back
495
+ self._plan = plan
496
+ self._preview = preview
497
+
498
+ def compose(self):
499
+ yield Static(self._title, id="al-title")
500
+ yield setup_panel(self._plan)
501
+ detail = Static("", id="al-detail")
502
+ detail.border_title = "About highlighted option"
503
+ yield detail
504
+ yield Static(
505
+ f"Options (1-{len(self._options)} of {len(self._options)})", id="al-hdr"
506
+ )
507
+ option_list = OptionList()
508
+ for option in self._options:
509
+ label = option.label + ("" if option.enabled else " [unavailable]")
510
+ option_list.add_option(
511
+ Option(label, id=option.value, disabled=not option.enabled)
512
+ )
513
+ yield option_list
514
+ footer = (
515
+ "Up/Down move | Enter select | Esc back | q cancel"
516
+ if self._allow_back
517
+ else "Up/Down move | Enter select | Esc cancel | q cancel"
518
+ )
519
+ yield Static(footer, id="al-footer")
520
+
521
+ def on_mount(self):
522
+ option_list = self.query_one(OptionList)
523
+ index = next(
524
+ (
525
+ position
526
+ for position, option in enumerate(self._options)
527
+ if option.value == self._default and option.enabled
528
+ ),
529
+ next(
530
+ (
531
+ position
532
+ for position, option in enumerate(self._options)
533
+ if option.enabled
534
+ ),
535
+ 0,
536
+ ),
537
+ )
538
+ option_list.highlighted = index
539
+ option_list.focus()
540
+ self._describe(index)
541
+
542
+ def on_option_list_option_highlighted(self, event):
543
+ self._describe(event.option_index)
544
+
545
+ def _describe(self, index):
546
+ option = self._options[index]
547
+ detail = option.description
548
+ if not option.enabled and option.unavailable_reason:
549
+ detail = f"{detail} Unavailable: {option.unavailable_reason}"
550
+ self.query_one("#al-detail", Static).update(detail)
551
+ # Live-preview the highlighted option's effect in the setup panel.
552
+ if self._preview is not None:
553
+ try:
554
+ preview_plan = self._preview(option.value)
555
+ self.query_one("#al-setup", Static).update(
556
+ "\n".join(setup_summary_lines(preview_plan))
557
+ )
558
+ except Exception:
559
+ pass
560
+
561
+ def on_option_list_option_selected(self, event):
562
+ self.dismiss(event.option.id)
563
+
564
+ def action_back(self):
565
+ self.dismiss(_UI_BACK if self._allow_back else _UI_CANCEL)
566
+
567
+ def action_cancel(self):
568
+ self.dismiss(_UI_CANCEL)
569
+
570
+ class InputScreen(ModalScreen):
571
+ BINDINGS = [
572
+ Binding("escape", "cancel", "cancel", priority=True),
573
+ Binding("ctrl+c", "cancel", "cancel", priority=True),
574
+ ]
575
+
576
+ def __init__(self, label, default, plan):
577
+ super().__init__()
578
+ self._label = label
579
+ self._default = default
580
+ self._plan = plan
581
+
582
+ def compose(self):
583
+ yield Static(self._label, id="al-title")
584
+ yield setup_panel(self._plan)
585
+ box = Static(
586
+ f"Current value: {self._default}\n"
587
+ "New value (leave blank to keep current):",
588
+ id="al-detail",
589
+ )
590
+ box.border_title = "Edit model"
591
+ yield box
592
+ yield Static("", id="al-hdr")
593
+ yield Input(placeholder="type a model id, or leave blank to keep current")
594
+ yield Static(
595
+ "Enter confirm | Esc/Ctrl-C cancel | q + Enter cancel", id="al-footer"
596
+ )
597
+
598
+ def on_mount(self):
599
+ self.query_one(Input).focus()
600
+
601
+ def on_input_submitted(self, event):
602
+ value = event.value.strip()
603
+ if value.lower() == "q":
604
+ self.dismiss(_UI_CANCEL)
605
+ else:
606
+ self.dismiss(value or self._default)
607
+
608
+ def action_cancel(self):
609
+ self.dismiss(_UI_CANCEL)
610
+
611
+ class PreflightApp(App):
612
+ ENABLE_COMMAND_PALETTE = False
613
+ CSS = app_css
614
+ BINDINGS = [Binding("ctrl+q", "noop", show=False)]
615
+
616
+ def __init__(self, config, host, preset_name, custom_requested, config_path):
617
+ super().__init__()
618
+ self._flow_args = (config, host, preset_name, custom_requested, config_path)
619
+ self._host = host
620
+ self.outcome = None
621
+
622
+ def action_noop(self):
623
+ pass
624
+
625
+ def on_mount(self):
626
+ theme = host_themes.get(self._host)
627
+ if theme is not None:
628
+ self.register_theme(theme)
629
+ self.theme = theme.name
630
+ self.run_flow()
631
+
632
+ @work(thread=True)
633
+ def run_flow(self):
634
+ config, host, preset_name, custom_requested, config_path = self._flow_args
635
+ ui = TextualUI(self, MenuScreen, InputScreen)
636
+ try:
637
+ self.outcome = (
638
+ "plan",
639
+ select_plan(
640
+ config, host, preset_name, custom_requested, ui, config_path
641
+ ),
642
+ )
643
+ except BaseException as exc: # surfaced to main; re-raised for the exit code
644
+ self.outcome = ("error", exc)
645
+ finally:
646
+ self.call_from_thread(self.exit)
647
+
648
+ return PreflightApp
649
+
650
+
651
+ class TextualUI:
652
+ def __init__(self, app, menu_screen, input_screen):
653
+ self.app = app
654
+ self._menu_screen = menu_screen
655
+ self._input_screen = input_screen
656
+ self.plan: dict[str, Any] | None = None
657
+
658
+ def set_plan(self, plan: dict[str, Any]) -> None:
659
+ self.plan = plan
660
+
661
+ def choose(
662
+ self,
663
+ title: str,
664
+ options: list[MenuOption],
665
+ default: str,
666
+ allow_back: bool,
667
+ preview=None,
668
+ ) -> str:
669
+ result = self.app.call_from_thread(
670
+ self.app.push_screen_wait,
671
+ self._menu_screen(title, options, default, allow_back, self.plan, preview),
672
+ )
673
+ if result == _UI_BACK:
674
+ raise BackRequested
675
+ if result == _UI_CANCEL:
676
+ raise KeyboardInterrupt
677
+ return result
678
+
679
+ def prompt_text(self, label: str, default: str) -> str:
680
+ result = self.app.call_from_thread(
681
+ self.app.push_screen_wait,
682
+ self._input_screen(label, default, self.plan),
683
+ )
684
+ if result == _UI_CANCEL:
685
+ raise KeyboardInterrupt
686
+ return result
687
+
688
+
689
+ def run_textual_flow(
690
+ config: dict[str, Any],
691
+ host: str,
692
+ preset_name: str | None,
693
+ custom_requested: bool,
694
+ config_path: pathlib.Path,
695
+ ) -> dict[str, Any]:
696
+ app = _build_app_class()(config, host, preset_name, custom_requested, config_path)
697
+ app.run()
698
+ if app.outcome is None:
699
+ raise KeyboardInterrupt
700
+ kind, value = app.outcome
701
+ if kind == "error":
702
+ raise value
703
+ return value
704
+
705
+
706
+ def choose_lines(
707
+ title: str, options: list[MenuOption], default: str, allow_back: bool
708
+ ) -> str:
709
+ print(f"\n{title}")
710
+ for index, option in enumerate(options, 1):
711
+ marker = "" if option.enabled else " [unavailable]"
712
+ selected = " *" if option.value == default and option.enabled else ""
713
+ print(f" {index}. {option.label}{marker}{selected} - {option.description}")
714
+ if not option.enabled:
715
+ print(f" Unavailable: {option.unavailable_reason}")
716
+ while True:
717
+ actions = "b back | q cancel" if allow_back else "q cancel"
718
+ raw = read_input(f"Select [{default}] ({actions}): ").strip()
719
+ if not raw:
720
+ default_option = next((option for option in options if option.value == default), None)
721
+ if default_option and default_option.enabled:
722
+ return default
723
+ reason = default_option.unavailable_reason if default_option else "unknown option"
724
+ print(f"Unavailable: {reason}. Select an enabled option.")
725
+ continue
726
+ if raw.lower() in {"q", "quit"}:
727
+ raise KeyboardInterrupt
728
+ if raw.lower() in {"b", "back"}:
729
+ if allow_back:
730
+ raise BackRequested
731
+ print("No previous menu. Use q to cancel.")
732
+ continue
733
+ if raw.isdigit() and 1 <= int(raw) <= len(options):
734
+ option = options[int(raw) - 1]
735
+ if option.enabled:
736
+ return option.value
737
+ print(f"Unavailable: {option.unavailable_reason}")
738
+
739
+
740
+ def choose(
741
+ title: str,
742
+ options: list[MenuOption],
743
+ default: str,
744
+ ui: TextualUI | None = None,
745
+ allow_back: bool = False,
746
+ preview=None,
747
+ ) -> str:
748
+ if not any(option.enabled for option in options):
749
+ raise LaunchError(f"no available options for {title}")
750
+ if ui is not None:
751
+ return ui.choose(title, options, default, allow_back, preview)
752
+ return choose_lines(title, options, default, allow_back)
753
+
754
+
755
+ def read_input(prompt: str) -> str:
756
+ try:
757
+ return input(prompt)
758
+ except EOFError as exc:
759
+ raise LaunchError(
760
+ "interactive input ended; use --preset NAME for a non-interactive configured launch"
761
+ ) from exc
762
+
763
+
764
+ def prompt_text(label: str, default: str, ui: TextualUI | None = None) -> str:
765
+ if ui is not None:
766
+ return ui.prompt_text(label, default)
767
+ value = read_input(f"{label} [{default}] (q cancel): ").strip()
768
+ if value.lower() == "q":
769
+ raise KeyboardInterrupt
770
+ return value or default
771
+
772
+
773
+ def host_models(config: dict[str, Any], host: str, tiers: dict[str, Any]) -> list[str]:
774
+ """Selectable model catalog for a host: the configured [hosts.<host>].models
775
+ list when present, else the distinct models already bound to the tiers."""
776
+ models = config["hosts"][host].get("models")
777
+ if models:
778
+ return list(models)
779
+ catalog: list[str] = []
780
+ for tier in TIER_ORDER:
781
+ model = tiers[tier]["model"]
782
+ if model not in catalog:
783
+ catalog.append(model)
784
+ return catalog
785
+
786
+
787
+ def build_plan(config: dict[str, Any], host: str, preset_name: str) -> dict[str, Any]:
788
+ try:
789
+ preset = copy.deepcopy(config["presets"][preset_name])
790
+ tiers = copy.deepcopy(config["hosts"][host]["tiers"])
791
+ except (KeyError, TypeError) as exc:
792
+ raise LaunchError(f"invalid preset or host binding: {host}/{preset_name}") from exc
793
+ if not isinstance(preset, dict) or not isinstance(tiers, dict):
794
+ raise LaunchError(f"invalid preset or host binding: {host}/{preset_name}")
795
+ if set(tiers) != set(TIER_ORDER):
796
+ raise LaunchError(f"{host} tiers must be exactly: {', '.join(TIER_ORDER)}")
797
+ for tier, binding in tiers.items():
798
+ if not isinstance(binding, dict):
799
+ raise LaunchError(f"invalid binding: {host}.{tier}")
800
+ model = binding.get("model")
801
+ if not isinstance(model, str) or not model:
802
+ raise LaunchError(f"invalid binding: {host}.{tier}")
803
+ validate_effort(host, model, binding.get("effort"), f"{host}.{tier}")
804
+ all_overrides = preset.get("tier_overrides", {})
805
+ if not isinstance(all_overrides, dict):
806
+ raise LaunchError(f"tier_overrides must be a table in preset {preset_name}")
807
+ host_overrides = all_overrides.get(host, {})
808
+ if not isinstance(host_overrides, dict):
809
+ raise LaunchError(f"tier_overrides.{host} must be a table in preset {preset_name}")
810
+ for tier, override in host_overrides.items():
811
+ if tier not in tiers:
812
+ raise LaunchError(f"unknown tier in {preset_name}.tier_overrides.{host}: {tier}")
813
+ if not isinstance(override, dict):
814
+ raise LaunchError(f"tier override must be a table: {preset_name}.tier_overrides.{host}.{tier}")
815
+ override_model = override.get("model", tiers[tier]["model"])
816
+ if not isinstance(override_model, str) or not override_model:
817
+ raise LaunchError(f"invalid override model: {preset_name}.tier_overrides.{host}.{tier}")
818
+ override_effort = override.get("effort", tiers[tier]["effort"])
819
+ validate_effort(host, override_model, override_effort, f"{preset_name}.tier_overrides.{host}.{tier}")
820
+ tiers[tier] = {"model": override_model, "effort": override_effort}
821
+ main_tier = preset.get("main_tier")
822
+ if not isinstance(main_tier, str) or main_tier not in tiers:
823
+ raise LaunchError(f"invalid main_tier in preset {preset_name}: {main_tier}")
824
+ frontier_effort = preset.get("frontier_effort", tiers["frontier"]["effort"])
825
+ if isinstance(frontier_effort, dict):
826
+ frontier_effort = frontier_effort.get(host)
827
+ validate_effort(host, tiers["frontier"]["model"], frontier_effort, f"{preset_name}.frontier")
828
+ delegation = preset.get("delegation", True)
829
+ if not isinstance(delegation, bool):
830
+ raise LaunchError(f"delegation must be boolean in preset {preset_name}")
831
+ review_setup = preset.get("review_setup", "none")
832
+ if not isinstance(review_setup, str) or review_setup not in REVIEW_SETUPS:
833
+ raise LaunchError(f"unknown review setup in preset {preset_name}: {review_setup!r}")
834
+ codex_policy = preset.get("codex_execution_policy")
835
+ claude_policy = preset.get("claude_permission_mode")
836
+ if not isinstance(codex_policy, str) or codex_policy not in CODEX_POLICIES:
837
+ raise LaunchError(f"invalid Codex policy in preset {preset_name}: {codex_policy!r}")
838
+ if not isinstance(claude_policy, str) or claude_policy not in CLAUDE_POLICIES:
839
+ raise LaunchError(f"invalid Claude policy in preset {preset_name}: {claude_policy!r}")
840
+ label = preset.get("label", preset_name)
841
+ if not isinstance(label, str) or not label:
842
+ raise LaunchError(f"invalid label in preset {preset_name}")
843
+ review_family = preset.get("review_family", "cross")
844
+ review_host = REVIEW_HOST[host]
845
+ opposite = config.get("hosts", {}).get(review_host)
846
+ opposite_backend = config.get("backends", {}).get(review_host, {})
847
+ if (
848
+ not isinstance(opposite, dict)
849
+ or not isinstance(opposite.get("tiers"), dict)
850
+ or not isinstance(opposite_backend, dict)
851
+ or not opposite_backend.get("command")
852
+ ):
853
+ # Cross review needs the opposite host + backend; without them fall back to
854
+ # same-family review (surfaced as review_family=same) rather than crashing.
855
+ review_family = "same"
856
+ review_tiers, review_onto, review_backend = {}, None, ""
857
+ else:
858
+ review_tiers = copy.deepcopy(opposite["tiers"])
859
+ review_onto = copy.deepcopy(opposite.get("onto_review"))
860
+ review_backend = opposite_backend["command"]
861
+ return {
862
+ "host": host,
863
+ "preset": preset_name,
864
+ "label": label,
865
+ "description": preset.get("description", f"Launch the {label} preset."),
866
+ "main_tier": main_tier,
867
+ "frontier_effort": frontier_effort,
868
+ "review_setup": review_setup,
869
+ "delegation": delegation,
870
+ "codex_execution_policy": codex_policy,
871
+ "claude_permission_mode": claude_policy,
872
+ "tiers": tiers,
873
+ "available_models": host_models(config, host, tiers),
874
+ "review_family": review_family,
875
+ "review_host": review_host,
876
+ "review_tiers": review_tiers,
877
+ "review_onto": review_onto,
878
+ "review_backend": review_backend,
879
+ "agent_templates": copy.deepcopy(config["hosts"][host].get("agent_templates")),
880
+ "capabilities": copy.deepcopy(config.get("capabilities", {})),
881
+ }
882
+
883
+
884
+ def review_description(host: str, name: str) -> str:
885
+ contract = REVIEW_SETUPS[name]["contract"]
886
+ return contract[host] if isinstance(contract, dict) else contract
887
+
888
+
889
+ def effort_options(host: str, model: str) -> list[MenuOption]:
890
+ options = []
891
+ for effort in EFFORT_ORDER:
892
+ enabled = effort in HOST_EFFORTS[host]
893
+ reason = f"not supported by {host}" if not enabled else ""
894
+ if enabled and host == "codex" and model == "gpt-5.6-luna" and effort == "ultra":
895
+ enabled = False
896
+ reason = "not supported by gpt-5.6-luna"
897
+ options.append(
898
+ MenuOption(effort, effort, EFFORT_DESCRIPTIONS[effort], enabled, reason)
899
+ )
900
+ return options
901
+
902
+
903
+ def model_options(plan: dict[str, Any]) -> list[MenuOption]:
904
+ options = [
905
+ MenuOption(model, model, f"Use {model} as this tier's model.")
906
+ for model in plan["available_models"]
907
+ ]
908
+ options.append(
909
+ MenuOption(
910
+ OTHER_MODEL,
911
+ "Other (enter a model id)",
912
+ "Type any model id the backend accepts, even if it is not in the catalog.",
913
+ )
914
+ )
915
+ return options
916
+
917
+
918
+ def valid_preset_name(name: str) -> bool:
919
+ return bool(name) and name[0].isalnum() and all(
920
+ character.isalnum() or character in "-_" for character in name
921
+ )
922
+
923
+
924
+ def preset_from_plan(
925
+ plan: dict[str, Any], config: dict[str, Any], name: str
926
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
927
+ """Project the plan into a saveable named preset plus host-scoped tier
928
+ overrides. Only tier bindings that differ from the host defaults are recorded,
929
+ scoped to the plan's host so the preset stays correct on the other host, which
930
+ falls back to its own defaults."""
931
+ host = plan["host"]
932
+ default_tiers = config["hosts"][host]["tiers"]
933
+ fields = {
934
+ "label": name,
935
+ "description": "Saved custom launch setup.",
936
+ "main_tier": plan["main_tier"],
937
+ "review_setup": plan["review_setup"],
938
+ "review_family": plan.get("review_family", "cross"),
939
+ "delegation": plan["delegation"],
940
+ "codex_execution_policy": plan["codex_execution_policy"],
941
+ "claude_permission_mode": plan["claude_permission_mode"],
942
+ }
943
+ overrides: dict[str, dict[str, str]] = {}
944
+ for tier in TIER_ORDER:
945
+ override: dict[str, str] = {}
946
+ if plan["tiers"][tier]["model"] != default_tiers[tier]["model"]:
947
+ override["model"] = plan["tiers"][tier]["model"]
948
+ if tier_effort(plan, tier) != default_tiers[tier]["effort"]:
949
+ override["effort"] = tier_effort(plan, tier)
950
+ if override:
951
+ overrides[tier] = override
952
+ return fields, ({host: overrides} if overrides else {})
953
+
954
+
955
+ def _toml_scalar(value: Any) -> str:
956
+ if isinstance(value, bool):
957
+ return "true" if value else "false"
958
+ if isinstance(value, str):
959
+ return json.dumps(value, ensure_ascii=False)
960
+ raise LaunchError(f"cannot serialize preset value: {value!r}")
961
+
962
+
963
+ def render_preset_block(
964
+ name: str, fields: dict[str, Any], tier_overrides: dict[str, Any]
965
+ ) -> str:
966
+ lines = [f"[presets.{name}]"]
967
+ for key, value in fields.items():
968
+ lines.append(f"{key} = {_toml_scalar(value)}")
969
+ for host, tiers in tier_overrides.items():
970
+ for tier, override in tiers.items():
971
+ lines.append("")
972
+ lines.append(f"[presets.{name}.tier_overrides.{host}.{tier}]")
973
+ for key, value in override.items():
974
+ lines.append(f"{key} = {_toml_scalar(value)}")
975
+ return "\n".join(lines) + "\n"
976
+
977
+
978
+ def remove_preset_block(text: str, name: str) -> str:
979
+ """Drop an existing [presets.<name>] table and its sub-tables, leaving the
980
+ rest of the file (other presets, comments, host bindings) intact."""
981
+ exact = f"[presets.{name}]"
982
+ sub_prefix = f"[presets.{name}."
983
+ kept = []
984
+ skipping = False
985
+ for line in text.splitlines(keepends=True):
986
+ header = line.strip()
987
+ if header.startswith("["):
988
+ skipping = header == exact or header.startswith(sub_prefix)
989
+ if skipping:
990
+ continue
991
+ kept.append(line)
992
+ return "".join(kept)
993
+
994
+
995
+ def save_preset(
996
+ plan: dict[str, Any], config: dict[str, Any], config_path: pathlib.Path, name: str
997
+ ) -> None:
998
+ if not valid_preset_name(name):
999
+ raise LaunchError(
1000
+ "preset name must start alphanumeric and use only letters, digits, '-' or '_'"
1001
+ )
1002
+ fields, tier_overrides = preset_from_plan(plan, config, name)
1003
+ block = render_preset_block(name, fields, tier_overrides)
1004
+ text = remove_preset_block(config_path.read_text(), name).rstrip("\n") + "\n\n" + block
1005
+ temporary = config_path.with_name(f".{config_path.name}.{os.getpid()}.tmp")
1006
+ try:
1007
+ temporary.write_text(text)
1008
+ os.replace(temporary, config_path)
1009
+ except OSError as exc:
1010
+ raise LaunchError(f"cannot save preset to {config_path}: {exc}") from exc
1011
+
1012
+
1013
+ def customize(
1014
+ plan: dict[str, Any],
1015
+ config: dict[str, Any],
1016
+ config_path: pathlib.Path,
1017
+ ui: TextualUI | None = None,
1018
+ ) -> None:
1019
+ tier_options = [
1020
+ MenuOption(tier, tier.upper(), TIER_DESCRIPTIONS[tier]) for tier in TIER_ORDER
1021
+ ]
1022
+ available_routes = route_availability(plan)
1023
+ cross = plan.get("review_family", "cross") == "cross"
1024
+ review_options = []
1025
+ for name, spec in REVIEW_SETUPS.items():
1026
+ description = review_description(plan["host"], name)
1027
+ if cross and name != "none":
1028
+ description = f"Runs cross-family on {plan['review_host']} models. {description}"
1029
+ missing = [
1030
+ route
1031
+ for route in REVIEW_ROUTES[name]
1032
+ if route in ("onto", "ultracode") and not available_routes[route]
1033
+ ]
1034
+ if missing:
1035
+ note = "degrades to same-family native (PROPOSED)" if cross else "degrades to native"
1036
+ description = f"{description} ({', '.join(missing)} unavailable now; {note})"
1037
+ review_options.append(MenuOption(name, spec["label"], description))
1038
+ if plan["host"] == "codex":
1039
+ policies = [
1040
+ MenuOption(
1041
+ "bypass",
1042
+ "Bypass approvals and sandbox",
1043
+ "Run without Codex approval or sandbox restrictions.",
1044
+ ),
1045
+ MenuOption(
1046
+ "workspace-write",
1047
+ "Workspace write sandbox",
1048
+ "Allow workspace writes under Codex's workspace-write sandbox.",
1049
+ ),
1050
+ MenuOption(
1051
+ "read-only",
1052
+ "Read-only sandbox",
1053
+ "Allow inspection while blocking file writes through the Codex sandbox.",
1054
+ ),
1055
+ ]
1056
+ policy_field = "codex_execution_policy"
1057
+ policy_title = "Codex execution policy"
1058
+ else:
1059
+ policies = [
1060
+ MenuOption(
1061
+ "bypassPermissions",
1062
+ "Bypass permissions",
1063
+ "Skip Claude permission prompts; this is not an OS sandbox setting.",
1064
+ ),
1065
+ MenuOption(
1066
+ "acceptEdits",
1067
+ "Accept edits",
1068
+ "Automatically accept file edits while retaining other permission checks.",
1069
+ ),
1070
+ MenuOption(
1071
+ "auto",
1072
+ "Auto permissions",
1073
+ "Use Claude's automatic permission mode.",
1074
+ ),
1075
+ MenuOption(
1076
+ "manual",
1077
+ "Manual permissions",
1078
+ "Ask before actions according to Claude's manual permission flow.",
1079
+ ),
1080
+ MenuOption(
1081
+ "dontAsk",
1082
+ "Do not ask",
1083
+ "Use Claude's dontAsk permission mode.",
1084
+ ),
1085
+ MenuOption(
1086
+ "plan",
1087
+ "Plan mode",
1088
+ "Start Claude in planning mode without direct implementation.",
1089
+ ),
1090
+ ]
1091
+ policy_field = "claude_permission_mode"
1092
+ policy_title = "Claude permission mode (not an OS sandbox)"
1093
+ selected_action = "main"
1094
+ while True:
1095
+ policy_label = next(
1096
+ option.label for option in policies if option.value == plan[policy_field]
1097
+ )
1098
+ hub_options = [
1099
+ MenuOption(
1100
+ "main",
1101
+ f"Main tier: {plan['main_tier'].upper()}",
1102
+ "Choose the primary orchestrator used for this session.",
1103
+ ),
1104
+ MenuOption(
1105
+ "review",
1106
+ f"Review setup: {REVIEW_SETUPS[plan['review_setup']]['label']}",
1107
+ "Choose the review routes requested when review gates fire.",
1108
+ ),
1109
+ MenuOption(
1110
+ "policy",
1111
+ f"Execution policy: {policy_label}",
1112
+ "Choose the host-specific permission or sandbox behavior.",
1113
+ ),
1114
+ ]
1115
+ for tier in TIER_ORDER:
1116
+ binding = plan["tiers"][tier]
1117
+ hub_options.append(
1118
+ MenuOption(
1119
+ f"tier:{tier}",
1120
+ f"{tier.upper()}: {binding['model']} / {tier_effort(plan, tier)}",
1121
+ f"Edit the {tier.upper()} model and reasoning effort, then return here.",
1122
+ )
1123
+ )
1124
+ hub_options += [
1125
+ MenuOption(
1126
+ "save",
1127
+ "Save these settings globally and start",
1128
+ "Save this setup as a named preset in your user config for reuse elsewhere, then launch.",
1129
+ ),
1130
+ MenuOption(
1131
+ "start",
1132
+ "Start with these settings",
1133
+ "Confirm the complete setup shown above and continue to launch.",
1134
+ ),
1135
+ MenuOption(
1136
+ "exit",
1137
+ "Exit without launching",
1138
+ "Discard this launch and return to the shell.",
1139
+ ),
1140
+ ]
1141
+
1142
+ try:
1143
+ action = choose(
1144
+ "Custom settings",
1145
+ hub_options,
1146
+ selected_action,
1147
+ ui,
1148
+ allow_back=True,
1149
+ )
1150
+ except BackRequested:
1151
+ raise
1152
+ selected_action = action
1153
+
1154
+ try:
1155
+ if action == "main":
1156
+ plan["main_tier"] = choose(
1157
+ "Main tier",
1158
+ tier_options,
1159
+ plan["main_tier"],
1160
+ ui,
1161
+ allow_back=True,
1162
+ preview=lambda value: {**plan, "main_tier": value},
1163
+ )
1164
+ elif action == "review":
1165
+ plan["review_setup"] = choose(
1166
+ "Review setup",
1167
+ review_options,
1168
+ plan["review_setup"],
1169
+ ui,
1170
+ allow_back=True,
1171
+ preview=lambda value: {**plan, "review_setup": value},
1172
+ )
1173
+ elif action == "policy":
1174
+ plan[policy_field] = choose(
1175
+ policy_title,
1176
+ policies,
1177
+ plan[policy_field],
1178
+ ui,
1179
+ allow_back=True,
1180
+ preview=lambda value: {**plan, policy_field: value},
1181
+ )
1182
+ except BackRequested:
1183
+ continue
1184
+
1185
+ if action.startswith("tier:"):
1186
+ tier = action.split(":", 1)[1]
1187
+ binding = plan["tiers"][tier]
1188
+ while True:
1189
+ default_model = (
1190
+ binding["model"]
1191
+ if binding["model"] in plan["available_models"]
1192
+ else OTHER_MODEL
1193
+ )
1194
+ try:
1195
+ chosen_model = choose(
1196
+ f"{tier.upper()} model",
1197
+ model_options(plan),
1198
+ default_model,
1199
+ ui,
1200
+ allow_back=True,
1201
+ preview=lambda value: {
1202
+ **plan,
1203
+ "tiers": {
1204
+ **plan["tiers"],
1205
+ tier: {
1206
+ **binding,
1207
+ "model": binding["model"]
1208
+ if value == OTHER_MODEL
1209
+ else value,
1210
+ },
1211
+ },
1212
+ },
1213
+ )
1214
+ except BackRequested:
1215
+ break
1216
+ if chosen_model == OTHER_MODEL:
1217
+ binding["model"] = prompt_text(
1218
+ f"{tier.upper()} model", binding["model"], ui
1219
+ )
1220
+ else:
1221
+ binding["model"] = chosen_model
1222
+ current_effort = tier_effort(plan, tier)
1223
+ try:
1224
+ binding["effort"] = choose(
1225
+ f"{tier.upper()} effort",
1226
+ effort_options(plan["host"], binding["model"]),
1227
+ current_effort,
1228
+ ui,
1229
+ allow_back=True,
1230
+ preview=lambda value: {
1231
+ **plan,
1232
+ "frontier_effort": value
1233
+ if tier == "frontier"
1234
+ else plan["frontier_effort"],
1235
+ "tiers": {
1236
+ **plan["tiers"],
1237
+ tier: {**binding, "effort": value},
1238
+ },
1239
+ },
1240
+ )
1241
+ except BackRequested:
1242
+ continue
1243
+ break
1244
+ if tier == "frontier":
1245
+ plan["frontier_effort"] = binding["effort"]
1246
+ continue
1247
+
1248
+ if action in ("save", "start"):
1249
+ for tier, binding in plan["tiers"].items():
1250
+ validate_effort(
1251
+ plan["host"],
1252
+ binding["model"],
1253
+ tier_effort(plan, tier),
1254
+ f"custom.{tier}",
1255
+ )
1256
+ validate_review_setup(plan)
1257
+ if action == "save":
1258
+ name = prompt_text(
1259
+ "Save as preset name", f"{plan['preset']}-custom", ui
1260
+ )
1261
+ save_preset(plan, config, config_path, name)
1262
+ plan["_launch_confirmed"] = True
1263
+ return
1264
+ if action == "exit":
1265
+ raise KeyboardInterrupt
1266
+
1267
+
1268
+ def select_plan(
1269
+ config: dict[str, Any],
1270
+ host: str,
1271
+ preset_name: str | None,
1272
+ custom_requested: bool,
1273
+ ui: TextualUI | None = None,
1274
+ config_path: pathlib.Path | None = None,
1275
+ ) -> dict[str, Any]:
1276
+ presets = config["presets"]
1277
+ explicit_preset = preset_name
1278
+ show_picker = explicit_preset is None
1279
+ while True:
1280
+ selected_name = explicit_preset
1281
+ selected_custom = custom_requested
1282
+ if show_picker:
1283
+ options = [
1284
+ MenuOption(
1285
+ name,
1286
+ data["label"],
1287
+ data.get("description", f"Launch the {data['label']} preset."),
1288
+ )
1289
+ for name, data in presets.items()
1290
+ ]
1291
+ options.append(
1292
+ MenuOption(
1293
+ CUSTOM_PRESET,
1294
+ "Custom",
1295
+ "Open a settings hub for tiers, review setup, policy, and final confirmation.",
1296
+ )
1297
+ )
1298
+ default = "balanced" if "balanced" in presets else next(iter(presets))
1299
+ if ui is not None:
1300
+ ui.set_plan(build_plan(config, host, default))
1301
+ selected = choose(
1302
+ "Preset",
1303
+ options,
1304
+ default,
1305
+ ui,
1306
+ preview=lambda value: build_plan(
1307
+ config, host, default if value == CUSTOM_PRESET else value
1308
+ ),
1309
+ )
1310
+ if selected == CUSTOM_PRESET:
1311
+ selected_name = default
1312
+ selected_custom = True
1313
+ else:
1314
+ selected_name = selected
1315
+ if selected_name not in presets:
1316
+ raise LaunchError(f"unknown preset: {selected_name}")
1317
+ plan = build_plan(config, host, selected_name)
1318
+ if selected_custom:
1319
+ plan["label"] = f"Custom ({plan['label']})"
1320
+ if ui is not None:
1321
+ ui.set_plan(plan)
1322
+ if selected_custom:
1323
+ try:
1324
+ customize(plan, config, config_path, ui)
1325
+ except BackRequested:
1326
+ if show_picker:
1327
+ continue
1328
+ raise KeyboardInterrupt
1329
+ return plan
1330
+
1331
+
1332
+ def validate_review_setup(plan: dict[str, Any]) -> None:
1333
+ # Fail-closed only on the delegation contradiction (or an unknown setup); a
1334
+ # missing external capability degrades to native in effective_review rather
1335
+ # than raising here.
1336
+ effective_review(plan)
1337
+
1338
+
1339
+ def tier_effort(plan: dict[str, Any], tier: str) -> str:
1340
+ return plan["frontier_effort"] if tier == "frontier" else plan["tiers"][tier]["effort"]
1341
+
1342
+
1343
+ def codex_agent_configs(
1344
+ plan: dict[str, Any], materialize: bool
1345
+ ) -> dict[str, tuple[pathlib.Path, str]]:
1346
+ templates = plan.get("agent_templates")
1347
+ if not isinstance(templates, dict):
1348
+ raise LaunchError("Codex delegation requires [hosts.codex.agent_templates]")
1349
+ rendered: dict[str, tuple[str, str]] = {}
1350
+ for tier in ("frontier", "workhorse", "sweep"):
1351
+ source_value = templates.get(tier)
1352
+ if not isinstance(source_value, str) or not source_value:
1353
+ raise LaunchError(f"Codex agent template missing: {tier}")
1354
+ source = expand_config_path(source_value)
1355
+ try:
1356
+ data = tomllib.loads(source.read_text())
1357
+ except (OSError, tomllib.TOMLDecodeError) as exc:
1358
+ raise LaunchError(f"cannot load Codex agent template {source}: {exc}") from exc
1359
+ description = data.get("description")
1360
+ if not isinstance(description, str) or not description:
1361
+ raise LaunchError(f"Codex agent template requires description: {source}")
1362
+ data["model"] = plan["tiers"][tier]["model"]
1363
+ data["model_reasoning_effort"] = tier_effort(plan, tier)
1364
+ lines = []
1365
+ for key, value in data.items():
1366
+ if not isinstance(value, (str, int, float, bool)):
1367
+ raise LaunchError(f"unsupported Codex agent template value: {source}:{key}")
1368
+ lines.append(f"{key} = {json.dumps(value)}")
1369
+ rendered[tier] = ("\n".join(lines) + "\n", description)
1370
+ digest = hashlib.sha256(
1371
+ json.dumps(rendered, sort_keys=True, separators=(",", ":")).encode()
1372
+ ).hexdigest()[:20]
1373
+ cache_root = pathlib.Path(
1374
+ os.environ.get("XDG_CACHE_HOME", pathlib.Path.home() / ".cache")
1375
+ ) / "agent-launch/codex-agents" / digest
1376
+ if materialize:
1377
+ cache_root.mkdir(parents=True, exist_ok=True)
1378
+ result = {}
1379
+ for tier, (content, description) in rendered.items():
1380
+ target = cache_root / f"{tier}.toml"
1381
+ if materialize and (not target.is_file() or target.read_text() != content):
1382
+ temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
1383
+ temporary.write_text(content)
1384
+ temporary.chmod(0o600)
1385
+ os.replace(temporary, target)
1386
+ result[tier] = (target, description)
1387
+ return result
1388
+
1389
+
1390
+ def _same_review_route(plan: dict[str, Any], effective: list[str], dropped: list[str]) -> str:
1391
+ requested = plan["review_setup"]
1392
+ if dropped:
1393
+ kept = ", ".join(ROUTE_LABELS[route] for route in effective) or "no additional review route"
1394
+ route = f"{', '.join(dropped)} unavailable at launch; degraded to {kept}."
1395
+ else:
1396
+ route = REVIEW_SETUPS[requested]["contract"]
1397
+ if isinstance(route, dict):
1398
+ route = route[plan["host"]]
1399
+ if "ultracode" in effective:
1400
+ route = f"{route} Ultracode executable: {resolve_command(plan['capabilities']['ultracode']['command'])}."
1401
+ if "ultracode" in REVIEW_ROUTES[requested]:
1402
+ route = (
1403
+ f"{route} If the Codex-backed route is unavailable or unauthenticated "
1404
+ "at use time, fall back to native same-model subagent review."
1405
+ )
1406
+ return route
1407
+
1408
+
1409
+ def _cross_review_route(
1410
+ plan: dict[str, Any], effective: list[str], dropped: list[str], floor: str | None
1411
+ ) -> str:
1412
+ review_host = plan["review_host"]
1413
+ main_family = "Anthropic/Claude" if plan["host"] == "claude" else "OpenAI/Codex"
1414
+ review_family = "OpenAI/Codex" if review_host == "codex" else "Anthropic/Claude"
1415
+ review_bindings = ", ".join(
1416
+ f"{tier}={plan['review_tiers'][tier]['model']}/{plan['review_tiers'][tier]['effort']}"
1417
+ for tier in TIER_ORDER
1418
+ if tier in plan["review_tiers"]
1419
+ )
1420
+ parts = [
1421
+ f"Cross-family review: this main is {main_family}; run EVERY review route on "
1422
+ f"{review_family} ({review_host}) models. Do not use your own same-family "
1423
+ "subagents for primary review — they are the delegation/fallback floor only.",
1424
+ f"Reviewer tier bindings ({review_host}): {review_bindings}.",
1425
+ ]
1426
+ if "native" in effective:
1427
+ native = cross_native_command(plan)
1428
+ if review_host == "codex":
1429
+ helm = cross_helm_command(plan)
1430
+ fanout = f" (or {helm} --mode review for hybrid fan-out)" if helm else ""
1431
+ parts.append(
1432
+ f"native: dispatch {native} --profile hermetic --model <review tier> "
1433
+ f"--effort <e> --sandbox read-only{fanout}, self-contained packet on stdin, "
1434
+ "bounded read-only report."
1435
+ )
1436
+ else:
1437
+ parts.append(
1438
+ f"native: dispatch {native} -p --model <review tier> --effort <e> "
1439
+ "--permission-mode plan --append-system-prompt <read-only reviewer role>, "
1440
+ "self-contained packet, bounded report."
1441
+ )
1442
+ if "onto" in effective:
1443
+ onto = plan["review_onto"]
1444
+ parts.append(
1445
+ f'onto: call onto_review/onto_prepare_review with llmOverride='
1446
+ f'{{"provider":"{onto["provider"]}","model":"{onto["model"]}"}} so onto runs {review_family}.'
1447
+ )
1448
+ if "ultracode" in effective:
1449
+ ultracode = cross_ultracode_command(plan)
1450
+ if review_host == "codex":
1451
+ parts.append(
1452
+ f"ultracode: run {ultracode} (the $ultracode-for-codex Codex skill) "
1453
+ f"for {review_family} workflow-orchestration review."
1454
+ )
1455
+ else:
1456
+ parts.append(
1457
+ f"ultracode: run {ultracode} --effort ultracode -p <self-contained review packet> "
1458
+ f"(Claude Code /workflows ultracode mode, headless) for {review_family} "
1459
+ "workflow-orchestration review."
1460
+ )
1461
+ if dropped:
1462
+ detail = [
1463
+ f"onto (add a [hosts.{review_host}].onto_review pin)"
1464
+ if route == "onto" and not plan.get("review_onto")
1465
+ else route
1466
+ for route in dropped
1467
+ ]
1468
+ parts.append(f"Unavailable cross-family route(s) at launch: {', '.join(detail)}.")
1469
+ if floor == "native":
1470
+ parts.append(
1471
+ "No cross-family route resolved at launch; using same-family native "
1472
+ "subagent review labeled PROPOSED (family collapse)."
1473
+ )
1474
+ parts.append(
1475
+ "Cross-family reviewers are dispatched as read-only subprocesses, not "
1476
+ "CLI-native subagents; spawning them needs this main's execution policy to "
1477
+ "permit subprocesses, so a read-only or restrictive policy blocks the dispatch "
1478
+ "and collapses to same-family native. If a route is unavailable or "
1479
+ "unauthenticated at use time, fall back to native same-model subagent review "
1480
+ "via the configured child agents and label those verdicts PROPOSED (family collapse)."
1481
+ )
1482
+ return " ".join(parts)
1483
+
1484
+
1485
+ def run_contract(plan: dict[str, Any]) -> str:
1486
+ bindings = ", ".join(
1487
+ f"{tier}={data['model']}/{tier_effort(plan, tier)}"
1488
+ for tier, data in plan["tiers"].items()
1489
+ )
1490
+ requested = plan["review_setup"]
1491
+ family = plan.get("review_family", "cross")
1492
+ effective, dropped, floor = effective_review(plan)
1493
+ if family == "same":
1494
+ route = _same_review_route(plan, effective, dropped)
1495
+ else:
1496
+ route = _cross_review_route(plan, effective, dropped, floor)
1497
+ authority = (
1498
+ "Main and native child model/effort defaults are config-projected. Use the installed "
1499
+ "codex-run adapter when a separate child root or stricter reach boundary matters."
1500
+ if plan["host"] == "codex"
1501
+ else "Main and child model/effort defaults are CLI-projected."
1502
+ )
1503
+ return (
1504
+ f"LaunchPlan: main={plan['main_tier']} ({plan['tiers'][plan['main_tier']]['model']}/"
1505
+ f"{tier_effort(plan, plan['main_tier'])}); tiers: {bindings}. "
1506
+ f"Delegation={'on' if plan['delegation'] else 'off'}. Review family={family}. "
1507
+ f"Review setup={requested}: {route} "
1508
+ f"{authority} Forwarded backend arguments are expert overrides and may supersede these "
1509
+ "defaults. Status is configured/requested; completion is not enforced by "
1510
+ "this interactive launcher."
1511
+ )
1512
+
1513
+
1514
+ def claude_agents(plan: dict[str, Any]) -> str:
1515
+ roles = {}
1516
+ for tier in TIER_ORDER:
1517
+ binding = plan["tiers"][tier]
1518
+ roles[tier] = {
1519
+ "description": f"{tier.upper()} tier: {binding['model']} at {tier_effort(plan, tier)}",
1520
+ "prompt": (
1521
+ f"Act as the bounded {tier.upper()} role at requested effort "
1522
+ f"{tier_effort(plan, tier)}. Return evidence and verification; stay in scope."
1523
+ ),
1524
+ "model": binding["model"],
1525
+ "effort": tier_effort(plan, tier),
1526
+ }
1527
+ return json.dumps(roles, separators=(",", ":"))
1528
+
1529
+
1530
+ def project_args(plan: dict[str, Any], materialize_agents: bool = True) -> list[str]:
1531
+ host = plan["host"]
1532
+ main = plan["tiers"][plan["main_tier"]]
1533
+ main_effort = tier_effort(plan, plan["main_tier"])
1534
+ contract = run_contract(plan)
1535
+ effective_routes, _, _ = effective_review(plan)
1536
+ if host == "codex":
1537
+ args = [
1538
+ "--model", main["model"],
1539
+ "-c", f'model_reasoning_effort="{main_effort}"',
1540
+ "-c", f"developer_instructions={json.dumps(contract)}",
1541
+ "-c", f"features.multi_agent={'true' if plan['delegation'] else 'false'}",
1542
+ ]
1543
+ policy = plan["codex_execution_policy"]
1544
+ policy_args = (
1545
+ ["--dangerously-bypass-approvals-and-sandbox"]
1546
+ if policy == "bypass"
1547
+ else ["--sandbox", policy]
1548
+ )
1549
+ args += policy_args
1550
+ if plan["delegation"]:
1551
+ for tier, (path, description) in codex_agent_configs(plan, materialize_agents).items():
1552
+ args += [
1553
+ "-c", f"agents.{tier}.description={json.dumps(description)}",
1554
+ "-c", f"agents.{tier}.config_file={json.dumps(str(path))}",
1555
+ ]
1556
+ if "onto" in effective_routes:
1557
+ onto_command = resolve_command(plan["capabilities"]["onto"]["command"])
1558
+ args += [
1559
+ "-c", "mcp_servers.onto.enabled=true",
1560
+ "-c", f"mcp_servers.onto.command={json.dumps(onto_command)}",
1561
+ "-c", 'mcp_servers.onto.args=["mcp"]',
1562
+ ]
1563
+ return args
1564
+ args = [
1565
+ "--model", main["model"],
1566
+ "--effort", main_effort,
1567
+ "--append-system-prompt", contract,
1568
+ ]
1569
+ if plan["delegation"]:
1570
+ args += ["--agents", claude_agents(plan)]
1571
+ policy = plan["claude_permission_mode"]
1572
+ policy_args = (
1573
+ ["--dangerously-skip-permissions"]
1574
+ if policy == "bypassPermissions"
1575
+ else ["--permission-mode", policy]
1576
+ )
1577
+ args += policy_args
1578
+ if "onto" in effective_routes:
1579
+ onto_command = resolve_command(plan["capabilities"]["onto"]["command"])
1580
+ mcp_config = {
1581
+ "mcpServers": {"onto": {"command": onto_command, "args": ["mcp"]}}
1582
+ }
1583
+ args += [
1584
+ "--mcp-config",
1585
+ json.dumps(mcp_config, separators=(",", ":")),
1586
+ ]
1587
+ return args
1588
+
1589
+
1590
+ def print_summary(
1591
+ plan: dict[str, Any],
1592
+ command: str,
1593
+ args: list[str],
1594
+ stream: Any = sys.stdout,
1595
+ forwarded_args: bool = False,
1596
+ ) -> None:
1597
+ main = plan["tiers"][plan["main_tier"]]
1598
+ print("\nLaunch summary", file=stream)
1599
+ print(f" Host {plan['host']}", file=stream)
1600
+ print(f" Preset {plan['label']}", file=stream)
1601
+ print(
1602
+ f" Main {plan['main_tier'].upper()} · {main['model']} · "
1603
+ f"{tier_effort(plan, plan['main_tier'])}",
1604
+ file=stream,
1605
+ )
1606
+ for tier in TIER_ORDER:
1607
+ binding = plan["tiers"][tier]
1608
+ effort = tier_effort(plan, tier)
1609
+ print(f" {tier.upper():<14} {binding['model']} · {effort}", file=stream)
1610
+ effective, dropped, floor = effective_review(plan)
1611
+ family = plan.get("review_family", "cross")
1612
+ review_display = plan["review_setup"]
1613
+ if family == "cross":
1614
+ review_display += f" · cross-family review on {plan['review_host']}"
1615
+ if effective:
1616
+ review_display += f" via {'+'.join(effective)}"
1617
+ if dropped:
1618
+ review_display += f" (dropped {','.join(dropped)})"
1619
+ if floor == "native":
1620
+ review_display += " → same-family native PROPOSED"
1621
+ elif dropped:
1622
+ review_display += (
1623
+ f" → effective {'+'.join(effective) or 'none'} "
1624
+ f"({','.join(dropped)} unavailable)"
1625
+ )
1626
+ print(
1627
+ f" Review setup {review_display} · "
1628
+ "configured/requested · completed: not enforced",
1629
+ file=stream,
1630
+ )
1631
+ tier_authority = (
1632
+ "base main + native child model/effort configured"
1633
+ if plan["host"] == "codex"
1634
+ else "base main + child model/effort configured"
1635
+ )
1636
+ print(f" Tier authority {tier_authority}", file=stream)
1637
+ if forwarded_args:
1638
+ print(
1639
+ " Overrides forwarded backend args appended last; may supersede defaults",
1640
+ file=stream,
1641
+ )
1642
+ if plan["host"] == "codex":
1643
+ print(f" Execution Codex {plan['codex_execution_policy']}", file=stream)
1644
+ else:
1645
+ print(
1646
+ f" Execution Claude {plan['claude_permission_mode']} "
1647
+ "(permission mode, not OS sandbox)",
1648
+ file=stream,
1649
+ )
1650
+ print(f" Backend {command}", file=stream)
1651
+ if os.environ.get("AGENT_LAUNCH_DEBUG") == "1":
1652
+ print(" Argv " + json.dumps([command, *args]), file=stream)
1653
+
1654
+
1655
+ def parse_args(argv: list[str]) -> argparse.Namespace:
1656
+ parser = argparse.ArgumentParser(description=__doc__)
1657
+ parser.add_argument("--config", type=pathlib.Path, default=default_config_path())
1658
+ parser.add_argument(
1659
+ "--no-tui",
1660
+ action="store_true",
1661
+ help="disable the rich terminal preflight; configured-launch flags still apply",
1662
+ )
1663
+ parser.add_argument("--preset", help="launch a named preset without the preset picker")
1664
+ parser.add_argument(
1665
+ "--custom",
1666
+ action="store_true",
1667
+ help="open customization after preset selection",
1668
+ )
1669
+ parser.add_argument("--yes", action="store_true", help="skip launch confirmation")
1670
+ parser.add_argument("--dry-run", action="store_true", help="print projection without launching")
1671
+ parser.add_argument("host", choices=("codex", "claude"))
1672
+ parser.add_argument("forward", nargs=argparse.REMAINDER)
1673
+ args = parser.parse_args(argv)
1674
+ if args.forward[:1] == ["--"]:
1675
+ args.forward = args.forward[1:]
1676
+ return args
1677
+
1678
+
1679
+ def main(argv: list[str]) -> int:
1680
+ args = parse_args(argv)
1681
+ config_path = args.config.expanduser()
1682
+ config = load_config(config_path)
1683
+ command, passthrough = resolve_backend(config, args.host)
1684
+ tty = sys.stdin.isatty() and sys.stdout.isatty()
1685
+ if not tty and args.dry_run and not args.preset and not args.custom:
1686
+ if "balanced" not in config["presets"]:
1687
+ raise LaunchError(
1688
+ "bare non-TTY --dry-run requires a 'balanced' preset; use --preset NAME"
1689
+ )
1690
+ args.preset = "balanced"
1691
+ bypass = args.no_tui or bool(args.forward) or os.environ.get("AGENT_LAUNCH_TUI") == "0"
1692
+ if (bypass or not tty) and not args.preset and not args.custom and not args.dry_run:
1693
+ exec_backend(command, [*passthrough, *args.forward])
1694
+
1695
+ interactive_setup = not args.preset or args.custom
1696
+ use_textual = (
1697
+ interactive_setup
1698
+ and tty
1699
+ and os.environ.get("TERM", "") not in {"", "dumb"}
1700
+ )
1701
+ if use_textual and not textual_importable():
1702
+ maybe_reexec_into_venv()
1703
+ if not textual_importable():
1704
+ use_textual = False
1705
+ print(
1706
+ "agent-launch: rich terminal UI unavailable; using numbered prompts.",
1707
+ file=sys.stderr,
1708
+ )
1709
+ if use_textual:
1710
+ plan = run_textual_flow(
1711
+ config, args.host, args.preset, args.custom, config_path
1712
+ )
1713
+ else:
1714
+ plan = select_plan(
1715
+ config, args.host, args.preset, args.custom, config_path=config_path
1716
+ )
1717
+ validate_review_setup(plan)
1718
+ projected = [*project_args(plan, materialize_agents=not args.dry_run), *args.forward]
1719
+ summary_stream = sys.stdout if tty or args.dry_run else sys.stderr
1720
+ print_summary(plan, command, projected, summary_stream, bool(args.forward))
1721
+ if args.dry_run:
1722
+ print(json.dumps([command, *projected], ensure_ascii=False))
1723
+ return 0
1724
+ if tty and not args.yes and not plan.get("_launch_confirmed", False):
1725
+ confirmation = read_input("Launch? [Y/n/q]: ").strip().lower()
1726
+ if confirmation in {"n", "no", "q", "quit"}:
1727
+ return 130
1728
+ env = os.environ.copy()
1729
+ env["AGENT_LAUNCH_ACTIVE"] = "1"
1730
+ summary_stream.flush()
1731
+ exec_backend(command, projected, env)
1732
+
1733
+
1734
+ if __name__ == "__main__":
1735
+ try:
1736
+ raise SystemExit(main(sys.argv[1:]))
1737
+ except LaunchError as exc:
1738
+ print(f"agent-launch: {exc}", file=sys.stderr)
1739
+ raise SystemExit(2)
1740
+ except KeyboardInterrupt:
1741
+ print("\nCancelled.", file=sys.stderr)
1742
+ raise SystemExit(130)