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 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 — **General user** (Vanilla: the bare CLI with no launch contract, no tier bindings, standard permissions; domain presets arrive with the non-technical rollout), **Builder** (the tier presets: Balanced, Deep review, Fast batch, Solo with delegation off), **Session distill** — then a preset within it. In Builder, select **Custom** 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.
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
 
@@ -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 = "general"
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.0",
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"
@@ -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, includes Custom), "general"
48
- # (plain CLI presets like Vanilla), "distill" (opens the Session Distill hub).
49
- # A preset with a missing/unknown mode defaults to "builder" so presets written
50
- # before this field existed (including saved/local user presets) keep working.
51
- GENERAL_MODE = "general"
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 = (GENERAL_MODE, DEFAULT_PRESET_MODE, DISTILL_MODE)
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 (General user / Builder / Session
1687
- distill), then that mode's preset submenu (Builder also lists Custom).
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
- GENERAL_MODE,
1702
- "General user",
1703
- "Plain presets with nothing launch-specific applied, like "
1704
- "Vanilla: no tier bindings or launch contract.",
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 == DEFAULT_PRESET_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 = default if value == CUSTOM_PRESET else value
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 default, True, mode
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") == GENERAL_MODE:
2034
- # General mode is defined as the plain backend with nothing applied: no
2035
- # launch contract, no tier pinning, no agents, no permission/sandbox
2036
- # flag. Short-circuit here so that invariant holds structurally instead
2037
- # of depending on every general-mode preset being configured just so.
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"])
@@ -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 (General user / Builder / Session
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 (General=1, Builder=2, Session distill=3)
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=general): the raw backend with nothing applied — no
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",
@@ -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