agent-bios 0.9.0 → 0.9.2
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/README.md +1 -1
- package/config/agent-launch.toml +1 -1
- package/package.json +2 -1
- package/scripts/agent-launch.py +64 -20
- package/scripts/check-parity.sh +115 -3
- package/scripts/install.sh +18 -0
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ agent-bios uninstall # remove deployed files and the zsh hook
|
|
|
68
68
|
|
|
69
69
|
From a git clone, run `./scripts/install.sh install` directly (the same CLI). `install` respects `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `AGENT_LAUNCH_VENV`, and `ZDOTDIR`; `--dry-run` prints actions without changing anything. Deploy to **every active environment in one sitting** — a partial deploy leaves a shared global pointing at a guide some environment lacks; globals are English only. Replaced files are backed up under `~/.local/share/agent-bios/backups/<timestamp>/`, and the installed set is recorded in a manifest that `uninstall` consumes. The published npm package ships only the deploy set (never `settings.json`, `config.toml`, `ko/`, or `benchmarks/`).
|
|
70
70
|
|
|
71
|
-
In a TTY, zero-argument `codex` or `claude` opens the launch preflight. Every arrow-key TUI selection screen keeps the complete current setup in a fixed top panel, followed by the highlighted option's description and then the option list. Move with Up/Down, select with Enter, use Esc to return to the previous menu, and use `q` to cancel; Esc at the mode root also cancels. Each tier's model is chosen from the host's configured catalog, or via **Other** to type any model id the backend accepts; that text input preserves values that start with `q`, so Esc or Ctrl-C cancels immediately there, while submitting `q` cancels after Enter. The Textual preflight reflows to the terminal size, so there is no fixed minimum geometry. The root menu picks a mode — **
|
|
71
|
+
In a TTY, zero-argument `codex` or `claude` opens the launch preflight. Every arrow-key TUI selection screen keeps the complete current setup in a fixed top panel, followed by the highlighted option's description and then the option list. Move with Up/Down, select with Enter, use Esc to return to the previous menu, and use `q` to cancel; Esc at the mode root also cancels. Each tier's model is chosen from the host's configured catalog, or via **Other** to type any model id the backend accepts; that text input preserves values that start with `q`, so Esc or Ctrl-C cancels immediately there, while submitting `q` cancels after Enter. The Textual preflight reflows to the terminal size, so there is no fixed minimum geometry. The root menu picks a mode — **Software Engineer** (repo-scoped work that defers to the project's own AGENTS.md/CLAUDE.md: **Vanilla**, the bare CLI with no launch contract, tier bindings, or applied permissions, plus **Custom**; SE-specific review defaults arrive later), **Builder** (the tier presets: Balanced, Deep review, Fast batch, Solo with delegation off, plus Custom), **Session distill** — then a preset within it. Select **Custom** (in Software Engineer or Builder) to open a persistent settings hub for the main tier, review setup, host policy, and each tier binding. Every edit returns to that hub; **Start with these settings** is the final launch confirmation, **Save these settings globally and start** additionally persists the setup as a named preset (with host-scoped tier overrides) in your user config for reuse elsewhere, and **Exit without launching** cancels the launch. The rich preflight runs from a managed virtualenv (`scripts/provision-venv.sh`, at `~/.local/share/agent-launch/venv`) that the launcher re-execs into on the interactive path; when that venv is unavailable, or the call is non-interactive, or `TERM` is `dumb`/unset, the launcher falls back to numbered prompts, where `b` is the back command. Backend command names are resolved from the calling environment's `PATH`; shell functions are not re-entered. Codex child bindings are materialized as session-selected agent configs under the user cache, while Claude receives model and effort in `--agents` JSON when delegation is enabled. Review runs cross-family by default (`review_family`, default `cross`; `same` restores today's same-family projection): because the main's tiers are one model family, every dispatchable review route — native, onto, and ultracode — runs on the opposite family. The exception is `slash-review`, the host's own built-in review command (`/code-review` on Claude, with `ultra` for its deep multi-agent pass; `/review` on Codex): it needs no dependency and always resolves, but being the main's own command it cannot be dispatched cross-family, so under `cross` it runs as the same-family floor and its verdicts are labeled PROPOSED. A Claude main dispatches gpt/codex review (native via the `codex-run` reviewer wrapper resolved under `$CODEX_HOME/bin`, onto via an `llmOverride` to the configured openai seat, ultracode via the `$ultracode-for-codex` Codex skill); a Codex main dispatches Anthropic/Claude review (native via `claude -p --permission-mode plan`, onto via an `llmOverride` to the anthropic seat, ultracode via `claude --effort ultracode -p` — Claude Code's headless `/workflows` ultracode mode, verified accepted on claude 2.1.210). The concrete reviewer command, resolved absolute path, `llmOverride`, and opposite-family tier bindings are named in the injected session-start contract; cross-family reviewers are dispatched as read-only subprocesses, not CLI-native subagents, since neither CLI hosts the other family as a native subagent. When a cross-family route is unavailable at launch or unauthenticated at use time it degrades to same-family native subagent review labeled PROPOSED (family collapse) rather than blocking; a requested non-none review with no cross-family route and no same-family fallback (delegation off) stays fail-closed. Review setup means configured/requested; this launcher does not claim that review completed, and unavailable runtimes such as Ultrawork are not offered until integrated.
|
|
72
72
|
|
|
73
73
|
At the shell-wrapper boundary, every argument-bearing command (`codex exec ...`, `claude -p ...`) and every non-TTY invocation skips launch-profile projection and preserves caller arguments. The Claude direct path intentionally retains its wrapper default, `--dangerously-skip-permissions`. `codex --no-tui ...` / `claude --no-tui ...` explicitly take that direct path, and `AGENT_LAUNCH_TUI=0` disables zero-argument TUI interception for a process tree.
|
|
74
74
|
|
package/config/agent-launch.toml
CHANGED
|
@@ -116,7 +116,7 @@ claude_permission_mode = "bypassPermissions"
|
|
|
116
116
|
[presets.vanilla]
|
|
117
117
|
label = "Vanilla"
|
|
118
118
|
description = "Plain CLI session: no launch contract, no tier bindings, standard permissions — exactly what the bare backend gives you."
|
|
119
|
-
mode = "
|
|
119
|
+
mode = "software-engineer"
|
|
120
120
|
main_tier = "helm"
|
|
121
121
|
frontier_effort = "max"
|
|
122
122
|
review_setup = "none"
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-bios",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
|
+
"releaseDate": "2026-07-23",
|
|
4
5
|
"description": "A thin, low-level instruction layer for LLM CLI agents: one set of principles and behavior whichever model you run. Deploys into $HOME by copy via an explicit `agent-bios install`.",
|
|
5
6
|
"bin": {
|
|
6
7
|
"agent-bios": "scripts/install.sh"
|
package/scripts/agent-launch.py
CHANGED
|
@@ -44,17 +44,19 @@ EFFORT_DESCRIPTIONS = {
|
|
|
44
44
|
}
|
|
45
45
|
CUSTOM_PRESET = "__custom__"
|
|
46
46
|
OTHER_MODEL = "__other_model__"
|
|
47
|
-
# Root-menu grouping for presets: "builder" (tunable
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
47
|
+
# Root-menu grouping for presets: "builder" (tunable tier presets, includes
|
|
48
|
+
# Custom), "software-engineer" (repo-scoped work under the project's own
|
|
49
|
+
# AGENTS.md/CLAUDE.md — the bare Vanilla session plus Custom), "distill" (opens
|
|
50
|
+
# the Session Distill hub). A preset with a missing/unknown mode defaults to
|
|
51
|
+
# "builder" so presets written before this field existed (including saved/local
|
|
52
|
+
# user presets, and any that still carry the former "general" value) keep working.
|
|
53
|
+
SWE_MODE = "software-engineer"
|
|
52
54
|
DEFAULT_PRESET_MODE = "builder"
|
|
53
55
|
# The mode value that opens the Session Distill hub instead of a preset
|
|
54
56
|
# submenu; doubles as the option value on the root Mode menu so selecting it
|
|
55
57
|
# needs no translation.
|
|
56
58
|
DISTILL_MODE = "distill"
|
|
57
|
-
PRESET_MODES = (
|
|
59
|
+
PRESET_MODES = (SWE_MODE, DEFAULT_PRESET_MODE, DISTILL_MODE)
|
|
58
60
|
# User-saved presets live beside the deployed config, in a file the installer
|
|
59
61
|
# neither deploys nor verifies, so they survive `agent-bios install`.
|
|
60
62
|
USER_PRESETS_NAME = "presets.local.toml"
|
|
@@ -607,6 +609,9 @@ def _build_app_class():
|
|
|
607
609
|
def setup_panel(plan):
|
|
608
610
|
panel = Static("\n".join(setup_summary_lines(plan)), id="al-setup")
|
|
609
611
|
panel.border_title = "Current setup"
|
|
612
|
+
release = version_label()
|
|
613
|
+
if release:
|
|
614
|
+
panel.border_subtitle = release
|
|
610
615
|
return panel
|
|
611
616
|
|
|
612
617
|
class MenuScreen(ModalScreen):
|
|
@@ -1458,6 +1463,13 @@ CORPUS_STATUS_PATH = pathlib.Path(
|
|
|
1458
1463
|
)
|
|
1459
1464
|
)
|
|
1460
1465
|
|
|
1466
|
+
VERSION_INFO_PATH = pathlib.Path(
|
|
1467
|
+
os.environ.get(
|
|
1468
|
+
"AGENT_LAUNCH_VERSION_FILE",
|
|
1469
|
+
str(pathlib.Path.home() / ".local/share/agent-bios/version.json"),
|
|
1470
|
+
)
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1461
1473
|
|
|
1462
1474
|
def load_corpus_status() -> dict[str, Any] | None:
|
|
1463
1475
|
try:
|
|
@@ -1466,6 +1478,22 @@ def load_corpus_status() -> dict[str, Any] | None:
|
|
|
1466
1478
|
return None
|
|
1467
1479
|
|
|
1468
1480
|
|
|
1481
|
+
def version_label() -> str | None:
|
|
1482
|
+
"""The deployed agent-bios version + release date for the TUI, or None when
|
|
1483
|
+
the marker is absent (uninstalled / dev checkout). `agent-bios install`
|
|
1484
|
+
writes it from package.json (version + releaseDate). This is the deploy /
|
|
1485
|
+
system version — distinct from the corpus content version in the distill hub."""
|
|
1486
|
+
try:
|
|
1487
|
+
info = json.loads(VERSION_INFO_PATH.read_text())
|
|
1488
|
+
except (OSError, ValueError):
|
|
1489
|
+
return None
|
|
1490
|
+
version = info.get("version") if isinstance(info, dict) else None
|
|
1491
|
+
if not version:
|
|
1492
|
+
return None
|
|
1493
|
+
released = info.get("releaseDate")
|
|
1494
|
+
return f"agent-bios v{version} · {released}" if released else f"agent-bios v{version}"
|
|
1495
|
+
|
|
1496
|
+
|
|
1469
1497
|
def corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
1470
1498
|
"""Panel body for the Session Distill area: which corpus content is
|
|
1471
1499
|
live, through which mechanisms, and whether the corpus is rolled back."""
|
|
@@ -1683,8 +1711,9 @@ def pick_mode_and_preset(
|
|
|
1683
1711
|
ui: TextualUI | None,
|
|
1684
1712
|
resume_mode: str | None,
|
|
1685
1713
|
) -> tuple[str, bool, str]:
|
|
1686
|
-
"""Root menu: a 3-way mode picker (
|
|
1687
|
-
distill), then that mode's preset submenu (
|
|
1714
|
+
"""Root menu: a 3-way mode picker (Software Engineer / Builder / Session
|
|
1715
|
+
distill), then that mode's preset submenu (Software Engineer and Builder
|
|
1716
|
+
both list Custom).
|
|
1688
1717
|
|
|
1689
1718
|
Esc in the submenu returns to the mode picker; Esc at the mode picker
|
|
1690
1719
|
cancels the launcher, matching the picker's prior root Esc/q semantics.
|
|
@@ -1698,10 +1727,11 @@ def pick_mode_and_preset(
|
|
|
1698
1727
|
if mode is None:
|
|
1699
1728
|
mode_options = [
|
|
1700
1729
|
MenuOption(
|
|
1701
|
-
|
|
1702
|
-
"
|
|
1703
|
-
"
|
|
1704
|
-
"
|
|
1730
|
+
SWE_MODE,
|
|
1731
|
+
"Software Engineer",
|
|
1732
|
+
"Repo-scoped work that defers to the project's own "
|
|
1733
|
+
"AGENTS.md/CLAUDE.md: a bare Vanilla session, or Custom to "
|
|
1734
|
+
"configure one.",
|
|
1705
1735
|
),
|
|
1706
1736
|
MenuOption(
|
|
1707
1737
|
DEFAULT_PRESET_MODE,
|
|
@@ -1752,7 +1782,7 @@ def pick_mode_and_preset(
|
|
|
1752
1782
|
for name, data in presets.items()
|
|
1753
1783
|
if preset_mode(data) == mode
|
|
1754
1784
|
]
|
|
1755
|
-
if mode
|
|
1785
|
+
if mode in (DEFAULT_PRESET_MODE, SWE_MODE):
|
|
1756
1786
|
options.append(
|
|
1757
1787
|
MenuOption(
|
|
1758
1788
|
CUSTOM_PRESET,
|
|
@@ -1761,11 +1791,16 @@ def pick_mode_and_preset(
|
|
|
1761
1791
|
)
|
|
1762
1792
|
)
|
|
1763
1793
|
default = mode_default_preset(presets, mode) or CUSTOM_PRESET
|
|
1794
|
+
# Custom always starts from the builder default (an applied baseline), so a
|
|
1795
|
+
# Custom entered from Software Engineer mode applies its settings instead of
|
|
1796
|
+
# inheriting Vanilla's bare short-circuit. (SE-specific review defaults are
|
|
1797
|
+
# deferred; today Custom shares the builder baseline.)
|
|
1798
|
+
custom_base = mode_default_preset(presets, DEFAULT_PRESET_MODE) or default
|
|
1764
1799
|
if ui is not None and default != CUSTOM_PRESET:
|
|
1765
1800
|
ui.set_plan(build_plan(config, host, default))
|
|
1766
1801
|
|
|
1767
1802
|
def preview_preset(value: str) -> dict[str, Any]:
|
|
1768
|
-
target =
|
|
1803
|
+
target = custom_base if value == CUSTOM_PRESET else value
|
|
1769
1804
|
return build_plan(config, host, target)
|
|
1770
1805
|
|
|
1771
1806
|
try:
|
|
@@ -1776,7 +1811,7 @@ def pick_mode_and_preset(
|
|
|
1776
1811
|
mode = None
|
|
1777
1812
|
continue
|
|
1778
1813
|
if selected == CUSTOM_PRESET:
|
|
1779
|
-
return
|
|
1814
|
+
return custom_base, True, mode
|
|
1780
1815
|
return selected, False, mode
|
|
1781
1816
|
|
|
1782
1817
|
|
|
@@ -1804,6 +1839,13 @@ def select_plan(
|
|
|
1804
1839
|
plan = build_plan(config, host, selected_name)
|
|
1805
1840
|
if selected_custom:
|
|
1806
1841
|
plan["label"] = f"Custom ({plan['label']})"
|
|
1842
|
+
# A customized plan is always an applied setup, never the bare Vanilla
|
|
1843
|
+
# short-circuit — even if custom_base fell back to an SE-mode preset
|
|
1844
|
+
# under a stripped config with no builder presets. project_args
|
|
1845
|
+
# projects the bare backend iff mode == SWE_MODE, so force a Custom
|
|
1846
|
+
# launch out of that mode.
|
|
1847
|
+
if plan.get("mode") == SWE_MODE:
|
|
1848
|
+
plan["mode"] = DEFAULT_PRESET_MODE
|
|
1807
1849
|
if ui is not None:
|
|
1808
1850
|
ui.set_plan(plan)
|
|
1809
1851
|
if selected_custom:
|
|
@@ -2030,11 +2072,13 @@ def claude_agents(plan: dict[str, Any]) -> str:
|
|
|
2030
2072
|
|
|
2031
2073
|
def project_args(plan: dict[str, Any], materialize_agents: bool = True) -> list[str]:
|
|
2032
2074
|
host = plan["host"]
|
|
2033
|
-
if plan.get("mode") ==
|
|
2034
|
-
#
|
|
2035
|
-
# launch contract, no tier pinning, no agents, no
|
|
2036
|
-
# flag
|
|
2037
|
-
#
|
|
2075
|
+
if plan.get("mode") == SWE_MODE:
|
|
2076
|
+
# Software Engineer's Vanilla preset is the plain backend with nothing
|
|
2077
|
+
# applied: no launch contract, no tier pinning, no agents, no
|
|
2078
|
+
# permission/sandbox flag — it defers entirely to the repo's own
|
|
2079
|
+
# AGENTS.md/CLAUDE.md. Short-circuit so that invariant holds
|
|
2080
|
+
# structurally. Custom entered from this mode re-bases to the builder
|
|
2081
|
+
# default, so its plan mode is "builder" and its settings DO apply.
|
|
2038
2082
|
return []
|
|
2039
2083
|
main = plan["tiers"][plan["main_tier"]]
|
|
2040
2084
|
main_effort = tier_effort(plan, plan["main_tier"])
|
package/scripts/check-parity.sh
CHANGED
|
@@ -677,6 +677,113 @@ if launcher.is_file() and launch_profile:
|
|
|
677
677
|
if not scripted_ui.checked or not scripted_plan.get("_launch_confirmed"):
|
|
678
678
|
mark_fail("agent-launch scripted Custom hub did not reach final confirmation")
|
|
679
679
|
|
|
680
|
+
# Software Engineer mode: its submenu is exactly Vanilla + Custom; Vanilla
|
|
681
|
+
# stays the bare backend (no launch contract), while Custom re-bases to the
|
|
682
|
+
# applied builder baseline rather than inheriting Vanilla's bare
|
|
683
|
+
# short-circuit. Driven directly through pick_mode_and_preset — the root
|
|
684
|
+
# mode picker is TTY-only, so a piped/non-TTY run defaults past it.
|
|
685
|
+
class SEModeUI:
|
|
686
|
+
def __init__(self, pick):
|
|
687
|
+
self._pick = pick
|
|
688
|
+
self.submenu_labels = None
|
|
689
|
+
|
|
690
|
+
def set_plan(self, plan):
|
|
691
|
+
pass
|
|
692
|
+
|
|
693
|
+
def choose(self, title, options, default, allow_back, preview=None, corpus_lines=None):
|
|
694
|
+
if title == "Mode":
|
|
695
|
+
if launcher_module.SWE_MODE not in {option.value for option in options}:
|
|
696
|
+
mark_fail("agent-launch root menu is missing the Software Engineer mode")
|
|
697
|
+
return launcher_module.SWE_MODE
|
|
698
|
+
if title == "Preset":
|
|
699
|
+
self.submenu_labels = [option.label for option in options]
|
|
700
|
+
return self._pick
|
|
701
|
+
raise AssertionError(f"unexpected scripted menu: {title}")
|
|
702
|
+
|
|
703
|
+
se_vanilla_ui = SEModeUI("vanilla")
|
|
704
|
+
se_v_name, se_v_custom, _ = launcher_module.pick_mode_and_preset(
|
|
705
|
+
launch_profile, "codex", se_vanilla_ui, None
|
|
706
|
+
)
|
|
707
|
+
if se_vanilla_ui.submenu_labels != ["Vanilla", "Custom"]:
|
|
708
|
+
mark_fail(
|
|
709
|
+
"agent-launch Software Engineer submenu must be exactly Vanilla + "
|
|
710
|
+
f"Custom, got {se_vanilla_ui.submenu_labels}"
|
|
711
|
+
)
|
|
712
|
+
if se_v_custom or se_v_name != "vanilla":
|
|
713
|
+
mark_fail("agent-launch SE -> Vanilla must select the vanilla preset directly")
|
|
714
|
+
elif launcher_module.project_args(
|
|
715
|
+
launcher_module.build_plan(launch_profile, "codex", se_v_name),
|
|
716
|
+
materialize_agents=False,
|
|
717
|
+
):
|
|
718
|
+
mark_fail("agent-launch SE -> Vanilla must project the bare backend (no launch contract)")
|
|
719
|
+
|
|
720
|
+
se_custom_ui = SEModeUI(launcher_module.CUSTOM_PRESET)
|
|
721
|
+
se_c_name, se_c_custom, _ = launcher_module.pick_mode_and_preset(
|
|
722
|
+
launch_profile, "codex", se_custom_ui, None
|
|
723
|
+
)
|
|
724
|
+
if not se_c_custom:
|
|
725
|
+
mark_fail("agent-launch SE -> Custom must request the settings hub")
|
|
726
|
+
elif not launcher_module.project_args(
|
|
727
|
+
launcher_module.build_plan(launch_profile, "codex", se_c_name),
|
|
728
|
+
materialize_agents=False,
|
|
729
|
+
):
|
|
730
|
+
mark_fail("agent-launch SE -> Custom must project an applied setup, not the bare backend")
|
|
731
|
+
|
|
732
|
+
# Under a config with no builder presets, custom_base falls back to the SE
|
|
733
|
+
# default (vanilla), so SE -> Custom builds a plan in SWE_MODE; select_plan
|
|
734
|
+
# must force a customized plan out of that bare short-circuit so the user's
|
|
735
|
+
# settings are not silently discarded. Driven through the real select_plan.
|
|
736
|
+
class SECustomHubUI:
|
|
737
|
+
def set_plan(self, plan):
|
|
738
|
+
pass
|
|
739
|
+
|
|
740
|
+
def choose(self, title, options, default, allow_back, preview=None, corpus_lines=None):
|
|
741
|
+
if title == "Mode":
|
|
742
|
+
return launcher_module.SWE_MODE
|
|
743
|
+
if title == "Preset":
|
|
744
|
+
return launcher_module.CUSTOM_PRESET
|
|
745
|
+
if title == "Custom settings":
|
|
746
|
+
return "start"
|
|
747
|
+
raise AssertionError(f"unexpected scripted menu: {title}")
|
|
748
|
+
|
|
749
|
+
def prompt_text(self, label, default):
|
|
750
|
+
return default
|
|
751
|
+
|
|
752
|
+
stripped_profile = dict(launch_profile)
|
|
753
|
+
stripped_profile["presets"] = {
|
|
754
|
+
name: data
|
|
755
|
+
for name, data in launch_profile["presets"].items()
|
|
756
|
+
if name in ("vanilla", "session-distill")
|
|
757
|
+
}
|
|
758
|
+
stripped_custom_plan = launcher_module.select_plan(
|
|
759
|
+
stripped_profile, "codex", None, False, SECustomHubUI()
|
|
760
|
+
)
|
|
761
|
+
if not launcher_module.project_args(stripped_custom_plan, materialize_agents=False):
|
|
762
|
+
mark_fail(
|
|
763
|
+
"agent-launch SE -> Custom under a no-builder-preset config must still "
|
|
764
|
+
"project an applied setup (customization must not be silently discarded)"
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
# Version line: version_label reads the deployed version marker
|
|
768
|
+
# ($STATE_DIR/version.json) that install writes from package.json. A
|
|
769
|
+
# present marker yields "agent-bios v<version> · <releaseDate>"; an absent
|
|
770
|
+
# one yields None (uninstalled / dev checkout, so the panel shows no line).
|
|
771
|
+
saved_version_path = launcher_module.VERSION_INFO_PATH
|
|
772
|
+
try:
|
|
773
|
+
version_marker = pathlib.Path(tempfile.mkdtemp()) / "version.json"
|
|
774
|
+
version_marker.write_text('{"version": "9.9.9", "releaseDate": "2026-01-02"}')
|
|
775
|
+
launcher_module.VERSION_INFO_PATH = version_marker
|
|
776
|
+
if launcher_module.version_label() != "agent-bios v9.9.9 · 2026-01-02":
|
|
777
|
+
mark_fail(
|
|
778
|
+
"agent-launch version_label wrong with a marker present: "
|
|
779
|
+
f"{launcher_module.version_label()!r}"
|
|
780
|
+
)
|
|
781
|
+
launcher_module.VERSION_INFO_PATH = version_marker.parent / "absent.json"
|
|
782
|
+
if launcher_module.version_label() is not None:
|
|
783
|
+
mark_fail("agent-launch version_label must be None when the marker is absent")
|
|
784
|
+
finally:
|
|
785
|
+
launcher_module.VERSION_INFO_PATH = saved_version_path
|
|
786
|
+
|
|
680
787
|
with tempfile.TemporaryDirectory() as raw_tmp:
|
|
681
788
|
tmp = pathlib.Path(raw_tmp)
|
|
682
789
|
backend = tmp / "backend"
|
|
@@ -719,10 +826,13 @@ if launcher.is_file() and launch_profile:
|
|
|
719
826
|
mark_fail(f"fake launch profile did not replace every backend: {fake_commands!r}")
|
|
720
827
|
fake_profile = pathlib.Path("/nonexistent/unsafe-fixture")
|
|
721
828
|
env = os.environ.copy()
|
|
829
|
+
version_fixture = tmp / "version.json"
|
|
830
|
+
version_fixture.write_text('{"version": "9.9.9", "releaseDate": "2026-01-02"}')
|
|
722
831
|
env.update(
|
|
723
832
|
FAKE_LAUNCH_ARGV=str(argv_log),
|
|
724
833
|
AGENT_LAUNCH_CONFIG=str(fake_profile),
|
|
725
834
|
XDG_CACHE_HOME=str(tmp / "cache"),
|
|
835
|
+
AGENT_LAUNCH_VERSION_FILE=str(version_fixture),
|
|
726
836
|
)
|
|
727
837
|
|
|
728
838
|
pty_env = env.copy()
|
|
@@ -841,7 +951,7 @@ if launcher.is_file() and launch_profile:
|
|
|
841
951
|
os.close(master)
|
|
842
952
|
return bytes(transcript), picker_status
|
|
843
953
|
|
|
844
|
-
# Root menu is now a Mode picker (
|
|
954
|
+
# Root menu is now a Mode picker (Software Engineer / Builder / Session
|
|
845
955
|
# distill); Builder is the default highlight and lists Custom. Reaching
|
|
846
956
|
# Custom means: Enter the root to open Builder's preset submenu, then
|
|
847
957
|
# step down through its fixed presets (Balanced default-highlighted).
|
|
@@ -887,6 +997,8 @@ if launcher.is_file() and launch_profile:
|
|
|
887
997
|
< positions[b"Options ("]
|
|
888
998
|
):
|
|
889
999
|
mark_fail("agent-launch layout is not setup then description then options")
|
|
1000
|
+
if b"agent-bios v9.9.9" not in transcript:
|
|
1001
|
+
mark_fail("agent-launch does not show the deployed version line in the setup panel")
|
|
890
1002
|
|
|
891
1003
|
_, picker_status = run_picker_scenario(
|
|
892
1004
|
"root escape cancellation",
|
|
@@ -1217,7 +1329,7 @@ if launcher.is_file() and launch_profile:
|
|
|
1217
1329
|
# TERM=dumb routes to numbered prompts (textual renders on any usable
|
|
1218
1330
|
# terminal, so the numbered fallback is gated on TERM/non-TTY/textual
|
|
1219
1331
|
# availability, not a terminfo probe). 'b' is the numbered back command.
|
|
1220
|
-
# The root Mode menu is fixed (
|
|
1332
|
+
# The root Mode menu is fixed (Software Engineer=1, Builder=2, Session distill=3)
|
|
1221
1333
|
# regardless of preset count; only Custom's position within Builder's
|
|
1222
1334
|
# own preset submenu depends on how many builder-mode presets exist.
|
|
1223
1335
|
builder_preset_count = sum(
|
|
@@ -1590,7 +1702,7 @@ if launcher.is_file() and launch_profile:
|
|
|
1590
1702
|
"agent-launch Claude expert overrides were not appended last"
|
|
1591
1703
|
)
|
|
1592
1704
|
|
|
1593
|
-
# Vanilla (mode=
|
|
1705
|
+
# Vanilla (mode=software-engineer): the raw backend with nothing applied — no
|
|
1594
1706
|
# launch contract, no agents, no permission-bypass flag.
|
|
1595
1707
|
vanilla_configured = invoke([
|
|
1596
1708
|
sys.executable, str(launcher), "--preset", "vanilla", "--yes", "--dry-run",
|
package/scripts/install.sh
CHANGED
|
@@ -476,6 +476,24 @@ cmd_install() {
|
|
|
476
476
|
else
|
|
477
477
|
log "note: corpus-status projection unavailable (versions.json/ledger missing?)"
|
|
478
478
|
fi
|
|
479
|
+
# Deploy/system version marker for the launcher's TUI version line, read from
|
|
480
|
+
# package.json (version + releaseDate) — distinct from the corpus content
|
|
481
|
+
# version. Best-effort: a failure here never fails the install.
|
|
482
|
+
if [ "$DRY_RUN" != 1 ]; then
|
|
483
|
+
if python3 - "$REPO/package.json" "$STATE_DIR/version.json" <<'PY' 2>/dev/null
|
|
484
|
+
import json, sys
|
|
485
|
+
pkg = json.load(open(sys.argv[1]))
|
|
486
|
+
with open(sys.argv[2], "w") as f:
|
|
487
|
+
json.dump({"version": pkg.get("version"), "releaseDate": pkg.get("releaseDate")}, f)
|
|
488
|
+
f.write("\n")
|
|
489
|
+
PY
|
|
490
|
+
then
|
|
491
|
+
printf '%s\n' "$STATE_DIR/version.json" >> "$MANIFEST"
|
|
492
|
+
info "version marker written ($STATE_DIR/version.json)"
|
|
493
|
+
else
|
|
494
|
+
log "note: version marker not written (package.json unreadable)"
|
|
495
|
+
fi
|
|
496
|
+
fi
|
|
479
497
|
log ""
|
|
480
498
|
log "Verifying deployment..."
|
|
481
499
|
if cmd_verify; then
|