agent-bios 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPENDENCIES.md +58 -30
- package/INSTALL.md +4 -4
- package/README.md +105 -28
- package/claude/CLAUDE.md +1 -1
- package/claude/guides/claude-prompting.md +1 -1
- package/claude/guides/cli-multi-model-workflow.md +4 -4
- package/claude/guides/coding-staged-workflow.md +17 -0
- package/claude/guides/documentation-hygiene.md +3 -0
- package/claude/guides/gpt-prompting.md +1 -1
- package/claude/guides/korean-writing.md +153 -0
- package/claude/guides/learning-flow.md +4 -4
- package/claude/guides/llm-capability-boundary.md +7 -1
- package/claude/guides/session-distill-workflow.md +8 -8
- package/claude/guides/slide-writing/RUNBOOK.md +5 -5
- package/claude/guides/tooling-gotchas.md +20 -1
- package/claude/guides/ui-design/visual-direction.md +88 -0
- package/claude/guides/ui-design.md +90 -0
- package/claude/guides/verification-discipline.md +10 -1
- package/claude/hooks/tooling-gotchas-hook.py +41 -0
- package/claude/skills/repo-charter/SKILL.md +3 -3
- package/claude/skills/understand/SKILL.md +5 -5
- package/codex/AGENTS.md +1 -1
- package/codex/guides/claude-prompting.md +1 -1
- package/codex/guides/cli-multi-model-workflow.md +4 -4
- package/codex/guides/coding-staged-workflow.md +17 -0
- package/codex/guides/documentation-hygiene.md +3 -0
- package/codex/guides/gpt-prompting.md +1 -1
- package/codex/guides/korean-writing.md +153 -0
- package/codex/guides/learning-flow.md +4 -4
- package/codex/guides/llm-capability-boundary.md +7 -1
- package/codex/guides/session-distill-workflow.md +8 -8
- package/codex/guides/slide-writing/RUNBOOK.md +5 -5
- package/codex/guides/tooling-gotchas.md +20 -1
- package/codex/guides/ui-design/visual-direction.md +88 -0
- package/codex/guides/ui-design.md +90 -0
- package/codex/guides/verification-discipline.md +10 -1
- package/compose/app_bridge/SKILL.md +12 -12
- package/compose/app_bridge/scripts/bridge.py +35 -10
- package/compose/app_desktop/server.py +250 -0
- package/compose/assemble.py +5 -5
- package/compose/bootstrap/SKILL.md +18 -18
- package/compose/canary.sh +4 -4
- package/compose/check-domains.py +6 -6
- package/compose/corpus-state.py +16 -1168
- package/compose/corpus.py +13 -402
- package/compose/corpus_app.py +14 -450
- package/compose/corpus_catalog.py +15 -926
- package/compose/corpus_import.py +14 -523
- package/compose/corpus_install.py +14 -1847
- package/compose/corpus_session.py +16 -848
- package/compose/corpus_setup.py +16 -672
- package/compose/corpus_setup_cli.py +15 -580
- package/compose/corpus_setup_i18n.py +20 -324
- package/compose/corpus_setup_ui.py +18 -645
- package/compose/corpus_store.py +16 -1664
- package/compose/corpus_transaction.py +15 -284
- package/compose/corpus_ui.py +17 -972
- package/compose/corpus_ui_runtime.py +16 -274
- package/compose/corpus_understand.py +13 -671
- package/compose/domains.json +3 -1
- package/compose/host_platform.py +121 -0
- package/compose/instructions-state.py +1178 -0
- package/compose/instructions.py +409 -0
- package/compose/instructions_app.py +697 -0
- package/compose/instructions_catalog.py +931 -0
- package/compose/instructions_import.py +537 -0
- package/compose/instructions_install.py +1932 -0
- package/compose/instructions_session.py +852 -0
- package/compose/instructions_setup.py +713 -0
- package/compose/instructions_setup_cli.py +607 -0
- package/compose/instructions_setup_i18n.py +327 -0
- package/compose/instructions_setup_ui.py +647 -0
- package/compose/instructions_store.py +1668 -0
- package/compose/instructions_transaction.py +308 -0
- package/compose/instructions_ui.py +975 -0
- package/compose/instructions_ui_runtime.py +279 -0
- package/compose/instructions_understand.py +678 -0
- package/compose/native_cli.py +52 -0
- package/compose/register-hooks.py +1 -1
- package/compose/runtime_entry.py +58 -0
- package/compose/setup/START.md +11 -11
- package/compose/windows_deploy.py +719 -0
- package/docs/advanced-launch.md +11 -11
- package/docs/instructions-compatibility.md +86 -0
- package/docs/{corpus.md → instructions.md} +36 -8
- package/docs/recovery.md +10 -10
- package/docs/releases/0.19.2.md +38 -0
- package/docs/releases/0.19.3.md +107 -0
- package/docs/session-model.md +31 -20
- package/docs/setup.md +63 -26
- package/docs/understand.md +6 -6
- package/docs/windows.md +99 -0
- package/install.sh +71 -69
- package/launch/agent-launch.py +309 -293
- package/launch/agent-launch.toml +2 -2
- package/launch/agent-launch.zsh +11 -1
- package/launch/i18n/en.toml +55 -55
- package/launch/i18n/ja.toml +56 -56
- package/launch/i18n/ko.toml +56 -56
- package/launch/shell_integration.py +4 -4
- package/learn/collect-learning.py +10 -10
- package/learn/learning.schema.json +1 -1
- package/learn/migrate-learnings.py +51 -51
- package/package.json +33 -12
- package/provenance.json +1 -1
- /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
package/launch/agent-launch.py
CHANGED
|
@@ -6,7 +6,6 @@ from __future__ import annotations
|
|
|
6
6
|
import argparse
|
|
7
7
|
import copy
|
|
8
8
|
import datetime
|
|
9
|
-
import fcntl
|
|
10
9
|
import hashlib
|
|
11
10
|
import json
|
|
12
11
|
import os
|
|
@@ -24,6 +23,12 @@ import uuid
|
|
|
24
23
|
from dataclasses import dataclass, field
|
|
25
24
|
from typing import Any, Callable, NoReturn
|
|
26
25
|
|
|
26
|
+
if os.name == "nt":
|
|
27
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "compose"))
|
|
28
|
+
from host_platform import file_locks as fcntl
|
|
29
|
+
else:
|
|
30
|
+
import fcntl
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
TIER_ORDER = ("frontier", "helm", "workhorse", "sweep")
|
|
29
34
|
# Review is not work any tier can do. A reviewer must be at least this capable; helm or
|
|
@@ -972,10 +977,10 @@ CRITERION_CLAUSE = (
|
|
|
972
977
|
f"refuses unclassified findings, and fold returned rows against the declared enum "
|
|
973
978
|
f"without guessing a class"
|
|
974
979
|
)
|
|
975
|
-
# The panel that carries
|
|
980
|
+
# The panel that carries instructions/registration text, and the scrolling body that holds it.
|
|
976
981
|
# Named once so the screen, its CSS, its key bindings and the check that drives them cannot
|
|
977
982
|
# drift apart.
|
|
978
|
-
|
|
983
|
+
INSTRUCTIONS_PANEL_ID = "al-instructions"
|
|
979
984
|
BODY_PANEL_ID = "al-body"
|
|
980
985
|
TROPHY_PANEL_ID = "al-understand-trophy"
|
|
981
986
|
|
|
@@ -1423,8 +1428,8 @@ def parse_capability_offers(name: str, raw: Any) -> tuple:
|
|
|
1423
1428
|
|
|
1424
1429
|
def review_wrapper_command(host: str, name: str) -> str | None:
|
|
1425
1430
|
"""Resolve a review adapter from the active private package or native installation."""
|
|
1426
|
-
if
|
|
1427
|
-
path =
|
|
1431
|
+
if private_instructions_enabled():
|
|
1432
|
+
path = instructions_package_root() / "wrappers" / f"{name}.sh"
|
|
1428
1433
|
elif host == "codex":
|
|
1429
1434
|
path = expand_config_path(f"${{CODEX_HOME}}/bin/{name}")
|
|
1430
1435
|
else:
|
|
@@ -3635,7 +3640,7 @@ def emit_receipt_command(
|
|
|
3635
3640
|
fields[key] = value
|
|
3636
3641
|
# Present-but-empty is a configuration error, never a silent prose downgrade: an
|
|
3637
3642
|
# exported empty value made the whole validation vacuous while the caller believed
|
|
3638
|
-
# it armed (criterion round, #3 — the empty-variable class the
|
|
3643
|
+
# it armed (criterion round, #3 — the empty-variable class the instructions already
|
|
3639
3644
|
# names for shell checks).
|
|
3640
3645
|
criterion_env = os.environ.get(RECEIPT_CRITERION_ENV)
|
|
3641
3646
|
if criterion_env is not None and not criterion_env.strip():
|
|
@@ -4123,44 +4128,54 @@ _UI_RUNTIME_RELEASE: Callable[[], None] | None = None
|
|
|
4123
4128
|
def exec_backend(command: str, args: list[str], env: dict[str, str] | None = None) -> NoReturn:
|
|
4124
4129
|
if _UI_RUNTIME_RELEASE is not None:
|
|
4125
4130
|
_UI_RUNTIME_RELEASE()
|
|
4131
|
+
if os.name == "nt":
|
|
4132
|
+
raise SystemExit(subprocess.call([command, *args], env=os.environ.copy() if env is None else env))
|
|
4126
4133
|
os.execve(command, [command, *args], os.environ.copy() if env is None else env)
|
|
4127
4134
|
|
|
4128
4135
|
|
|
4129
|
-
def
|
|
4130
|
-
|
|
4136
|
+
def _instructions_environment(canonical: str, legacy: str, default=None):
|
|
4137
|
+
# The launcher also runs standalone, before any private package is located.
|
|
4138
|
+
current, previous = os.environ.get(canonical), os.environ.get(legacy)
|
|
4139
|
+
if current is not None and previous is not None and current != previous:
|
|
4140
|
+
raise LaunchError(f"conflicting {canonical} and {legacy}; set one value")
|
|
4141
|
+
return current if current is not None else previous if previous is not None else default
|
|
4142
|
+
|
|
4143
|
+
|
|
4144
|
+
def private_instructions_enabled() -> bool:
|
|
4145
|
+
explicit = _instructions_environment("AGENT_BIOS_PRIVATE_INSTRUCTIONS", "AGENT_BIOS_PRIVATE_CORPUS")
|
|
4131
4146
|
if explicit is not None:
|
|
4132
4147
|
return explicit == "1"
|
|
4133
4148
|
state = pathlib.Path(os.environ.get("AGENT_BIOS_STATE_DIR", str(pathlib.Path.home() / ".local/share/agent-bios")))
|
|
4134
4149
|
return (state / "runtime/private-install.json").is_file()
|
|
4135
4150
|
|
|
4136
4151
|
|
|
4137
|
-
def
|
|
4152
|
+
def instructions_package_root() -> pathlib.Path:
|
|
4138
4153
|
explicit = os.environ.get("AGENT_BIOS_PACKAGE_ROOT")
|
|
4139
4154
|
if explicit:
|
|
4140
4155
|
return pathlib.Path(explicit).resolve()
|
|
4141
4156
|
source = pathlib.Path(__file__).resolve().parents[1]
|
|
4142
|
-
if (source / "compose/corpus_store.py")
|
|
4157
|
+
if any((source / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
|
|
4143
4158
|
return source
|
|
4144
4159
|
state = pathlib.Path(os.environ.get("AGENT_BIOS_STATE_DIR", str(pathlib.Path.home() / ".local/share/agent-bios")))
|
|
4145
4160
|
try:
|
|
4146
4161
|
root = pathlib.Path(json.loads((state / "runtime/private-install.json").read_text())["package_root"])
|
|
4147
4162
|
except (OSError, ValueError, KeyError, TypeError) as exc:
|
|
4148
|
-
raise LaunchError("private
|
|
4149
|
-
if not (root / "compose/corpus_store.py")
|
|
4150
|
-
raise LaunchError("private
|
|
4163
|
+
raise LaunchError("private instructions runtime missing; run agent-bios install") from exc
|
|
4164
|
+
if not any((root / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
|
|
4165
|
+
raise LaunchError("private instructions runtime missing; run agent-bios install")
|
|
4151
4166
|
return root
|
|
4152
4167
|
|
|
4153
4168
|
|
|
4154
|
-
def
|
|
4155
|
-
root =
|
|
4169
|
+
def instructions_store():
|
|
4170
|
+
root = instructions_package_root()
|
|
4156
4171
|
module_root = str(root / "compose")
|
|
4157
4172
|
if module_root not in sys.path:
|
|
4158
4173
|
sys.path.insert(0, module_root)
|
|
4159
|
-
from
|
|
4160
|
-
return
|
|
4174
|
+
from instructions_store import InstructionsStore
|
|
4175
|
+
return InstructionsStore(root)
|
|
4161
4176
|
|
|
4162
4177
|
|
|
4163
|
-
def
|
|
4178
|
+
def _instructions_generation(state_root: pathlib.Path, config_path: pathlib.Path) -> str:
|
|
4164
4179
|
paths = {state_root / "runtime/private-install.json", state_root / "runtime/state.json", config_path}
|
|
4165
4180
|
digest = hashlib.sha256()
|
|
4166
4181
|
for path in sorted(paths):
|
|
@@ -4172,12 +4187,12 @@ def _corpus_generation(state_root: pathlib.Path, config_path: pathlib.Path) -> s
|
|
|
4172
4187
|
|
|
4173
4188
|
def _load_private_config(config_path: pathlib.Path, *, replay_only: bool = False):
|
|
4174
4189
|
"""Read one coherent configuration; verify its generation again at activation."""
|
|
4175
|
-
root =
|
|
4190
|
+
root = instructions_package_root()
|
|
4176
4191
|
module_root = str(root / "compose")
|
|
4177
4192
|
if module_root not in sys.path:
|
|
4178
4193
|
sys.path.insert(0, module_root)
|
|
4179
|
-
from
|
|
4180
|
-
state_root =
|
|
4194
|
+
from instructions_transaction import transaction_lock, guard_pending, confirmed_release, TransactionPendingError
|
|
4195
|
+
state_root = instructions_store().state_root
|
|
4181
4196
|
with transaction_lock(state_root):
|
|
4182
4197
|
try:
|
|
4183
4198
|
guard_pending(state_root)
|
|
@@ -4186,36 +4201,37 @@ def _load_private_config(config_path: pathlib.Path, *, replay_only: bool = False
|
|
|
4186
4201
|
raise
|
|
4187
4202
|
config_path = confirmed_release(state_root) / "launch/agent-launch.toml"
|
|
4188
4203
|
config = load_config(config_path)
|
|
4189
|
-
return config,
|
|
4204
|
+
return config, _instructions_generation(state_root, config_path)
|
|
4190
4205
|
|
|
4191
4206
|
|
|
4192
4207
|
def _snapshot_from_config(store, config_path, generation, host, selected, *, dry_run, native):
|
|
4193
|
-
from
|
|
4208
|
+
from instructions_transaction import transaction_lock, guard_pending
|
|
4194
4209
|
with transaction_lock(store.state_root):
|
|
4195
4210
|
guard_pending(store.state_root)
|
|
4196
|
-
if generation !=
|
|
4197
|
-
raise LaunchError("
|
|
4211
|
+
if generation != _instructions_generation(store.state_root, config_path):
|
|
4212
|
+
raise LaunchError("instructions installation or launch settings changed during setup; reopen the launcher")
|
|
4198
4213
|
return store.snapshot(host, selected, dry_run=dry_run, native=native)
|
|
4199
4214
|
|
|
4200
4215
|
|
|
4201
|
-
def
|
|
4202
|
-
root =
|
|
4203
|
-
|
|
4216
|
+
def open_instructions_studio() -> None:
|
|
4217
|
+
root = instructions_package_root()
|
|
4218
|
+
from host_platform import python_argv
|
|
4219
|
+
result = subprocess.run(python_argv(root / "compose/instructions.py", "--repo", str(root)))
|
|
4204
4220
|
if result.returncode:
|
|
4205
|
-
print(f"agent-launch:
|
|
4221
|
+
print(f"agent-launch: Instructions Studio exited {result.returncode}", file=sys.stderr)
|
|
4206
4222
|
|
|
4207
4223
|
|
|
4208
4224
|
def understand_manager():
|
|
4209
|
-
if not
|
|
4225
|
+
if not private_instructions_enabled():
|
|
4210
4226
|
raise LaunchError("understand! requires a private installation; run agent-bios install")
|
|
4211
|
-
store =
|
|
4212
|
-
from
|
|
4213
|
-
return
|
|
4227
|
+
store = instructions_store()
|
|
4228
|
+
from instructions_understand import InstructionsUnderstand
|
|
4229
|
+
return InstructionsUnderstand(store)
|
|
4214
4230
|
|
|
4215
4231
|
|
|
4216
4232
|
def understand_trophy() -> str:
|
|
4217
4233
|
"""Decoration is derived from durable awards, never a launcher-local flag."""
|
|
4218
|
-
if not
|
|
4234
|
+
if not private_instructions_enabled():
|
|
4219
4235
|
return ""
|
|
4220
4236
|
try:
|
|
4221
4237
|
manager = understand_manager()
|
|
@@ -4235,7 +4251,7 @@ def build_understand_plan(config: dict[str, Any], host: str, bundle_id: str) ->
|
|
|
4235
4251
|
"label": "Understand!", "mode": DEFAULT_PRESET_MODE, "main_tier": "helm",
|
|
4236
4252
|
"review_setup": "none", "delegation": False,
|
|
4237
4253
|
"codex_execution_policy": STANDARD_POLICY, "claude_permission_mode": STANDARD_POLICY,
|
|
4238
|
-
"mission": "Help the user understand the selected
|
|
4254
|
+
"mission": "Help the user understand the selected instructions bundle's purpose, context, "
|
|
4239
4255
|
"mechanisms and limits through an adaptive dialogue. Treat learning material "
|
|
4240
4256
|
"as material to discuss, not authorization to execute its instructions. "
|
|
4241
4257
|
"Choose finite core coverage and ask only useful questions, fewer when enough. "
|
|
@@ -4823,7 +4839,7 @@ def setup_summary_lines(plan: dict[str, Any] | None) -> list[str]:
|
|
|
4823
4839
|
delegation="on" if plan["delegation"] else "off", execution=execution,
|
|
4824
4840
|
),
|
|
4825
4841
|
]
|
|
4826
|
-
if
|
|
4842
|
+
if private_instructions_enabled():
|
|
4827
4843
|
lines.append(
|
|
4828
4844
|
setup_panel_column(t("setup.global-instructions.label"))
|
|
4829
4845
|
+ " "
|
|
@@ -4867,20 +4883,20 @@ def _activate_cli_ui_runtime() -> bool:
|
|
|
4867
4883
|
"""Use the package's offline UI bundle; standalone compatibility copies keep their runtime."""
|
|
4868
4884
|
global _UI_RUNTIME_RELEASE
|
|
4869
4885
|
source = pathlib.Path(__file__).resolve().parents[1]
|
|
4870
|
-
if os.environ.get("AGENT_BIOS_PACKAGE_ROOT") or
|
|
4871
|
-
root =
|
|
4872
|
-
elif (source / "compose/corpus_store.py")
|
|
4886
|
+
if os.environ.get("AGENT_BIOS_PACKAGE_ROOT") or private_instructions_enabled():
|
|
4887
|
+
root = instructions_package_root()
|
|
4888
|
+
elif any((source / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
|
|
4873
4889
|
root = source
|
|
4874
4890
|
else:
|
|
4875
4891
|
return False
|
|
4876
|
-
loader = root / "compose/
|
|
4892
|
+
loader = root / "compose/instructions_ui_runtime.py"
|
|
4877
4893
|
if loader.is_symlink() or not loader.is_file():
|
|
4878
4894
|
raise LaunchError("bundled UI runtime loader is missing or unsafe; reinstall the agent-bios package")
|
|
4879
4895
|
module_root = str(root / "compose")
|
|
4880
4896
|
if module_root not in sys.path:
|
|
4881
4897
|
sys.path.insert(0, module_root)
|
|
4882
4898
|
try:
|
|
4883
|
-
from
|
|
4899
|
+
from instructions_ui_runtime import activate_ui_runtime, release_ui_runtime
|
|
4884
4900
|
activate_ui_runtime(root)
|
|
4885
4901
|
except (ImportError, OSError, RuntimeError) as exc:
|
|
4886
4902
|
raise LaunchError(f"bundled terminal UI could not start: {exc}") from exc
|
|
@@ -5033,10 +5049,10 @@ def _build_app_class():
|
|
|
5033
5049
|
height: auto; min-height: 5; max-height: 45vh;
|
|
5034
5050
|
}
|
|
5035
5051
|
#al-detail-scroll > #al-detail { border: none; padding: 0; height: auto; }
|
|
5036
|
-
#al-
|
|
5052
|
+
#al-instructions-title { background: $warning; color: black; text-style: bold; padding: 0 1; }
|
|
5037
5053
|
/* No cap and no scroller of its own: one nested scroll region inside another is a
|
|
5038
5054
|
worse answer than a body that simply scrolls. */
|
|
5039
|
-
#al-
|
|
5055
|
+
#al-instructions {
|
|
5040
5056
|
border: round $warning; padding: 0 1; height: auto;
|
|
5041
5057
|
}
|
|
5042
5058
|
#al-hdr { color: $text-muted; text-style: bold; padding: 0 1; }
|
|
@@ -5121,7 +5137,7 @@ def _build_app_class():
|
|
|
5121
5137
|
self.call_after_refresh(self._size_detail)
|
|
5122
5138
|
|
|
5123
5139
|
def __init__(
|
|
5124
|
-
self, title, options, default, allow_back, plan, preview=None,
|
|
5140
|
+
self, title, options, default, allow_back, plan, preview=None, instructions=None,
|
|
5125
5141
|
confirm=None,
|
|
5126
5142
|
):
|
|
5127
5143
|
super().__init__()
|
|
@@ -5131,7 +5147,7 @@ def _build_app_class():
|
|
|
5131
5147
|
self._allow_back = allow_back
|
|
5132
5148
|
self._plan = plan
|
|
5133
5149
|
self._preview = preview
|
|
5134
|
-
self.
|
|
5150
|
+
self._instructions = instructions
|
|
5135
5151
|
self._trophy = understand_trophy()
|
|
5136
5152
|
# The value Enter decides on, for a screen whose rows are changes rather than
|
|
5137
5153
|
# choices. Without it Enter and Space would both mean "act on the highlighted
|
|
@@ -5148,9 +5164,9 @@ def _build_app_class():
|
|
|
5148
5164
|
with Horizontal(id="al-reference-row"):
|
|
5149
5165
|
with Vertical(id="al-reference-content"):
|
|
5150
5166
|
yield setup_panel(self._plan)
|
|
5151
|
-
if self.
|
|
5152
|
-
yield Static(t("tui.
|
|
5153
|
-
yield Static("\n".join(self.
|
|
5167
|
+
if self._instructions:
|
|
5168
|
+
yield Static(t("tui.instructions.title"), id="al-instructions-title")
|
|
5169
|
+
yield Static("\n".join(self._instructions), id=INSTRUCTIONS_PANEL_ID)
|
|
5154
5170
|
yield Static(self._trophy, id=TROPHY_PANEL_ID, markup=False)
|
|
5155
5171
|
detail = VerticalScroll(Static("", id="al-detail", markup=False), id="al-detail-scroll")
|
|
5156
5172
|
detail.border_title = t("tui.detail.title")
|
|
@@ -5357,13 +5373,13 @@ class TextualUI:
|
|
|
5357
5373
|
default: str,
|
|
5358
5374
|
allow_back: bool,
|
|
5359
5375
|
preview=None,
|
|
5360
|
-
|
|
5376
|
+
instructions_lines: list[str] | None = None,
|
|
5361
5377
|
confirm: str | None = None,
|
|
5362
5378
|
) -> str:
|
|
5363
5379
|
result = self.app.call_from_thread(
|
|
5364
5380
|
self.app.push_screen_wait,
|
|
5365
5381
|
self._menu_screen(
|
|
5366
|
-
title, options, default, allow_back, self.plan, preview,
|
|
5382
|
+
title, options, default, allow_back, self.plan, preview, instructions_lines,
|
|
5367
5383
|
confirm,
|
|
5368
5384
|
),
|
|
5369
5385
|
)
|
|
@@ -5429,12 +5445,12 @@ def choose_lines(
|
|
|
5429
5445
|
options: list[MenuOption],
|
|
5430
5446
|
default: str,
|
|
5431
5447
|
allow_back: bool,
|
|
5432
|
-
|
|
5448
|
+
instructions_lines: list[str] | None = None,
|
|
5433
5449
|
) -> str:
|
|
5434
5450
|
print(f"\n{title}")
|
|
5435
|
-
if
|
|
5436
|
-
print(" " + t("prompt.
|
|
5437
|
-
for line in
|
|
5451
|
+
if instructions_lines:
|
|
5452
|
+
print(" " + t("prompt.instructions.header"))
|
|
5453
|
+
for line in instructions_lines:
|
|
5438
5454
|
print(f" {line}")
|
|
5439
5455
|
print(" --")
|
|
5440
5456
|
for index, option in enumerate(options, 1):
|
|
@@ -5480,16 +5496,16 @@ def choose(
|
|
|
5480
5496
|
ui: TextualUI | None = None,
|
|
5481
5497
|
allow_back: bool = False,
|
|
5482
5498
|
preview=None,
|
|
5483
|
-
|
|
5499
|
+
instructions_lines: list[str] | None = None,
|
|
5484
5500
|
confirm: str | None = None,
|
|
5485
5501
|
) -> str:
|
|
5486
5502
|
if not any(option.enabled for option in options):
|
|
5487
5503
|
raise LaunchError(f"no available options for {title}")
|
|
5488
5504
|
if ui is not None:
|
|
5489
5505
|
return ui.choose(
|
|
5490
|
-
title, options, default, allow_back, preview,
|
|
5506
|
+
title, options, default, allow_back, preview, instructions_lines, confirm
|
|
5491
5507
|
)
|
|
5492
|
-
return choose_lines(title, options, default, allow_back,
|
|
5508
|
+
return choose_lines(title, options, default, allow_back, instructions_lines)
|
|
5493
5509
|
|
|
5494
5510
|
|
|
5495
5511
|
def read_input(prompt: str) -> str:
|
|
@@ -6530,7 +6546,7 @@ def register_reviewer_wizard(
|
|
|
6530
6546
|
if perspectives:
|
|
6531
6547
|
answers["perspectives"] = perspectives
|
|
6532
6548
|
break
|
|
6533
|
-
|
|
6549
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.perspectives.refused")])
|
|
6534
6550
|
while True:
|
|
6535
6551
|
trials_raw = prompt_text(
|
|
6536
6552
|
t("wizard.trials.label"), str(answers.get("trials", 2)), ui
|
|
@@ -6538,10 +6554,10 @@ def register_reviewer_wizard(
|
|
|
6538
6554
|
try:
|
|
6539
6555
|
trials = int(trials_raw)
|
|
6540
6556
|
except ValueError:
|
|
6541
|
-
|
|
6557
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
|
|
6542
6558
|
continue
|
|
6543
6559
|
if trials < 1:
|
|
6544
|
-
|
|
6560
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
|
|
6545
6561
|
continue
|
|
6546
6562
|
answers["trials"] = trials
|
|
6547
6563
|
break
|
|
@@ -6596,7 +6612,7 @@ def register_reviewer_wizard(
|
|
|
6596
6612
|
MenuOption("edit", t("wizard.confirm.edit.label"), ""),
|
|
6597
6613
|
],
|
|
6598
6614
|
"write", ui, allow_back=True,
|
|
6599
|
-
|
|
6615
|
+
instructions_lines=[t("wizard.confirm.target").format(target=target),
|
|
6600
6616
|
*block.splitlines()],
|
|
6601
6617
|
)
|
|
6602
6618
|
if confirm == "edit":
|
|
@@ -6610,7 +6626,7 @@ def register_reviewer_wizard(
|
|
|
6610
6626
|
# a planted symlink and truncates whatever it points at, merely by
|
|
6611
6627
|
# opening the wizard.
|
|
6612
6628
|
lock_fd = os.open(
|
|
6613
|
-
lock_path, os.O_CREAT | os.O_WRONLY | os
|
|
6629
|
+
lock_path, os.O_CREAT | os.O_WRONLY | getattr(os, "O_NOFOLLOW", 0), 0o600
|
|
6614
6630
|
)
|
|
6615
6631
|
with os.fdopen(lock_fd, "w") as lock_handle:
|
|
6616
6632
|
fcntl.flock(lock_handle, fcntl.LOCK_EX)
|
|
@@ -6618,16 +6634,16 @@ def register_reviewer_wizard(
|
|
|
6618
6634
|
# since the trial must read as a race even when its bytes happen
|
|
6619
6635
|
# to equal the header the wizard would have invented.
|
|
6620
6636
|
if write_target.is_file() != existed:
|
|
6621
|
-
|
|
6622
|
-
back_hint=t("
|
|
6637
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.raced.line")],
|
|
6638
|
+
back_hint=t("instructions.back.hint"))
|
|
6623
6639
|
return False
|
|
6624
6640
|
current = (
|
|
6625
6641
|
write_target.read_bytes() if write_target.is_file()
|
|
6626
6642
|
else USER_METHODS_HEADER.encode()
|
|
6627
6643
|
)
|
|
6628
6644
|
if current != before:
|
|
6629
|
-
|
|
6630
|
-
back_hint=t("
|
|
6645
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.raced.line")],
|
|
6646
|
+
back_hint=t("instructions.back.hint"))
|
|
6631
6647
|
return False
|
|
6632
6648
|
mode = (write_target.stat().st_mode & 0o777) if existed else 0o600
|
|
6633
6649
|
try:
|
|
@@ -6663,9 +6679,9 @@ def register_reviewer_wizard(
|
|
|
6663
6679
|
# merge. Sharing the restore branch's message told the user the
|
|
6664
6680
|
# file had been restored in the one case where it was left
|
|
6665
6681
|
# exactly as the other writer wrote it.
|
|
6666
|
-
|
|
6682
|
+
_instructions_info(
|
|
6667
6683
|
ui, t("wizard.title"), [t("wizard.postwrite.raced.line")],
|
|
6668
|
-
back_hint=t("
|
|
6684
|
+
back_hint=t("instructions.back.hint"),
|
|
6669
6685
|
)
|
|
6670
6686
|
return False
|
|
6671
6687
|
if existed:
|
|
@@ -6677,18 +6693,18 @@ def register_reviewer_wizard(
|
|
|
6677
6693
|
os.replace(restore, write_target)
|
|
6678
6694
|
else:
|
|
6679
6695
|
write_target.unlink(missing_ok=True)
|
|
6680
|
-
|
|
6681
|
-
back_hint=t("
|
|
6696
|
+
_instructions_info(ui, t("wizard.title"), [t("wizard.postwrite.line")],
|
|
6697
|
+
back_hint=t("instructions.back.hint"))
|
|
6682
6698
|
return False # pre-write state restored above
|
|
6683
6699
|
if verdict is None:
|
|
6684
6700
|
config.clear()
|
|
6685
6701
|
config.update(fresh)
|
|
6686
6702
|
registry.clear()
|
|
6687
6703
|
registry.update(load_review_methods(config))
|
|
6688
|
-
|
|
6704
|
+
_instructions_info(
|
|
6689
6705
|
ui, t("wizard.title"),
|
|
6690
6706
|
[t("wizard.done.line").format(method_id=answers["id"])],
|
|
6691
|
-
back_hint=t("
|
|
6707
|
+
back_hint=t("instructions.back.hint"),
|
|
6692
6708
|
)
|
|
6693
6709
|
return True
|
|
6694
6710
|
action = choose(
|
|
@@ -6699,7 +6715,7 @@ def register_reviewer_wizard(
|
|
|
6699
6715
|
MenuOption("contract", t("wizard.refused.contract.label"), ""),
|
|
6700
6716
|
],
|
|
6701
6717
|
"edit", ui, allow_back=True,
|
|
6702
|
-
|
|
6718
|
+
instructions_lines=[t("wizard.refused.header"), "", *verdict.splitlines()],
|
|
6703
6719
|
)
|
|
6704
6720
|
if action == "contract":
|
|
6705
6721
|
register_reviewer_info(ui, config_path)
|
|
@@ -6712,7 +6728,7 @@ def register_reviewer_info(ui: TextualUI | None, config_path: pathlib.Path) -> N
|
|
|
6712
6728
|
user owns and what a descriptor must contain, rather than offering a form that
|
|
6713
6729
|
would have to duplicate the reader's validation and then drift from it."""
|
|
6714
6730
|
path = user_methods_path(config_path)
|
|
6715
|
-
|
|
6731
|
+
_instructions_info(
|
|
6716
6732
|
ui,
|
|
6717
6733
|
"Register another reviewer",
|
|
6718
6734
|
[
|
|
@@ -6800,7 +6816,7 @@ def review_editor(
|
|
|
6800
6816
|
except BackRequested:
|
|
6801
6817
|
return None
|
|
6802
6818
|
except LaunchError as exc:
|
|
6803
|
-
|
|
6819
|
+
_instructions_info(
|
|
6804
6820
|
ui, f"{title} — cannot be seated", [str(exc)],
|
|
6805
6821
|
back_hint="Return to the review editor.",
|
|
6806
6822
|
)
|
|
@@ -7659,7 +7675,7 @@ def save_preset(
|
|
|
7659
7675
|
try:
|
|
7660
7676
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
7661
7677
|
lock_path = target.with_name(target.name + ".lock")
|
|
7662
|
-
lock_fd = os.open(lock_path, os.O_CREAT | os.O_WRONLY | os
|
|
7678
|
+
lock_fd = os.open(lock_path, os.O_CREAT | os.O_WRONLY | getattr(os, "O_NOFOLLOW", 0), 0o600)
|
|
7663
7679
|
with os.fdopen(lock_fd, "w") as lock_handle:
|
|
7664
7680
|
fcntl.flock(lock_handle, fcntl.LOCK_EX)
|
|
7665
7681
|
try:
|
|
@@ -8038,7 +8054,7 @@ def customize(
|
|
|
8038
8054
|
preview=lambda value: {**plan, policy_field: value},
|
|
8039
8055
|
)
|
|
8040
8056
|
elif action == "global-instructions":
|
|
8041
|
-
exclude_available =
|
|
8057
|
+
exclude_available = private_instructions_enabled() and plan["host"] == "claude"
|
|
8042
8058
|
exclude_reason = (
|
|
8043
8059
|
t("global-instructions.exclude.codex-unavailable")
|
|
8044
8060
|
if plan["host"] == "codex"
|
|
@@ -8203,7 +8219,7 @@ def customize(
|
|
|
8203
8219
|
)
|
|
8204
8220
|
save_preset(plan, config, config_path, name)
|
|
8205
8221
|
except LaunchError as exc:
|
|
8206
|
-
|
|
8222
|
+
_instructions_info(ui, t("custom.save.prompt"), [str(exc)],
|
|
8207
8223
|
back_hint=t("custom.title"))
|
|
8208
8224
|
continue
|
|
8209
8225
|
plan["_launch_confirmed"] = True
|
|
@@ -8213,9 +8229,9 @@ def customize(
|
|
|
8213
8229
|
|
|
8214
8230
|
|
|
8215
8231
|
DISTILL_PRESET = "session-distill"
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
"AGENT_BIOS_CORPUS_STATUS",
|
|
8232
|
+
INSTRUCTIONS_STATUS_PATH = pathlib.Path(
|
|
8233
|
+
_instructions_environment(
|
|
8234
|
+
"AGENT_BIOS_INSTRUCTIONS_STATUS", "AGENT_BIOS_CORPUS_STATUS",
|
|
8219
8235
|
str(pathlib.Path.home() / ".local/share/agent-bios/corpus-status.json"),
|
|
8220
8236
|
)
|
|
8221
8237
|
)
|
|
@@ -8238,7 +8254,7 @@ UPDATE_CHECK_PATH = pathlib.Path(
|
|
|
8238
8254
|
# at most SPAWNS the installer, detached, to refresh it. Two reasons, both load-bearing:
|
|
8239
8255
|
# `npm view` routinely takes seconds and a launcher that blocks on the network is worse
|
|
8240
8256
|
# than one that shows a stale badge, and the network operation belongs to the component
|
|
8241
|
-
# that already owns fetch-
|
|
8257
|
+
# that already owns fetch-instructions (ENDPOINTS.md) rather than to the UI.
|
|
8242
8258
|
UPDATE_CHECK_INTERVAL_S = 24 * 60 * 60
|
|
8243
8259
|
|
|
8244
8260
|
|
|
@@ -8316,24 +8332,24 @@ def spawn_update_check() -> None:
|
|
|
8316
8332
|
pass
|
|
8317
8333
|
|
|
8318
8334
|
|
|
8319
|
-
def
|
|
8320
|
-
"""The
|
|
8335
|
+
def load_instructions_status() -> dict[str, Any] | None:
|
|
8336
|
+
"""The instructions status projection, or None when the file is unreadable or is not
|
|
8321
8337
|
an object at all. Nothing deeper is validated here on purpose.
|
|
8322
8338
|
|
|
8323
8339
|
Validating the shape field by field was tried and is a queue that refills:
|
|
8324
8340
|
guarding `summary` and `versions` still left `summary.placed_by_layer: null`,
|
|
8325
8341
|
`domains.applied: [1,2]` and `last_apply.requested: 5` killing the ROOT MENU
|
|
8326
8342
|
before it drew, and each new consumer would add another field to remember. The
|
|
8327
|
-
depth is unbounded, so the readers DEGRADE instead — see
|
|
8343
|
+
depth is unbounded, so the readers DEGRADE instead — see instructions_summary_lines.
|
|
8328
8344
|
"""
|
|
8329
|
-
if
|
|
8345
|
+
if private_instructions_enabled():
|
|
8330
8346
|
try:
|
|
8331
|
-
return {"private":
|
|
8347
|
+
return {"private": instructions_store().status()}
|
|
8332
8348
|
except (OSError, ValueError, RuntimeError) as exc:
|
|
8333
|
-
print(f"agent-launch: cannot read private
|
|
8349
|
+
print(f"agent-launch: cannot read private instructions: {exc}", file=sys.stderr)
|
|
8334
8350
|
return None
|
|
8335
8351
|
try:
|
|
8336
|
-
data = json.loads(
|
|
8352
|
+
data = json.loads(INSTRUCTIONS_STATUS_PATH.read_text(encoding="utf-8"))
|
|
8337
8353
|
except (OSError, ValueError):
|
|
8338
8354
|
return None
|
|
8339
8355
|
return data if isinstance(data, dict) else None
|
|
@@ -8343,7 +8359,7 @@ def version_label() -> str | None:
|
|
|
8343
8359
|
"""The deployed agent-bios version + release date for the TUI, or None when
|
|
8344
8360
|
the marker is absent (uninstalled / dev checkout). `agent-bios install`
|
|
8345
8361
|
writes it from package.json (version + releaseDate). This is the deploy /
|
|
8346
|
-
system version — distinct from the
|
|
8362
|
+
system version — distinct from the instructions content version in the distill hub."""
|
|
8347
8363
|
try:
|
|
8348
8364
|
info = json.loads(VERSION_INFO_PATH.read_text(encoding="utf-8"))
|
|
8349
8365
|
except (OSError, ValueError):
|
|
@@ -8377,7 +8393,7 @@ def display_width(text: str) -> int:
|
|
|
8377
8393
|
)
|
|
8378
8394
|
|
|
8379
8395
|
|
|
8380
|
-
def
|
|
8396
|
+
def instructions_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
8381
8397
|
"""Panel body for the Session Distill area, or the not-projected line when the
|
|
8382
8398
|
status cannot be rendered.
|
|
8383
8399
|
|
|
@@ -8389,10 +8405,10 @@ def corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
|
8389
8405
|
The exception is printed, never swallowed, so a defect in the rendering code
|
|
8390
8406
|
below stays visible instead of hiding behind a degraded panel."""
|
|
8391
8407
|
try:
|
|
8392
|
-
return
|
|
8408
|
+
return _instructions_summary_lines(status)
|
|
8393
8409
|
except Exception as exc: # noqa: BLE001 — the root menu must survive any shape
|
|
8394
8410
|
print(
|
|
8395
|
-
f"agent-launch: cannot render the
|
|
8411
|
+
f"agent-launch: cannot render the instructions status from {INSTRUCTIONS_STATUS_PATH} "
|
|
8396
8412
|
f"({type(exc).__name__}: {exc}); showing it as not projected. "
|
|
8397
8413
|
"Run: agent-bios install", file=sys.stderr,
|
|
8398
8414
|
)
|
|
@@ -8414,7 +8430,7 @@ def status_list(value: Any) -> list:
|
|
|
8414
8430
|
return value if isinstance(value, list) else []
|
|
8415
8431
|
|
|
8416
8432
|
|
|
8417
|
-
|
|
8433
|
+
_INSTRUCTIONS_FACTS: dict[tuple, dict[str, Any] | None] = {}
|
|
8418
8434
|
|
|
8419
8435
|
|
|
8420
8436
|
def size_label(chars: int) -> str:
|
|
@@ -8424,7 +8440,7 @@ def size_label(chars: int) -> str:
|
|
|
8424
8440
|
return f"{chars / 1024:.1f} KB"
|
|
8425
8441
|
|
|
8426
8442
|
|
|
8427
|
-
def
|
|
8443
|
+
def instructions_facts(status: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
8428
8444
|
"""What each domain package holds and what it costs, or None when the package the
|
|
8429
8445
|
projection points at cannot be read.
|
|
8430
8446
|
|
|
@@ -8453,20 +8469,20 @@ def corpus_facts(status: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
8453
8469
|
stamp = (repo, manifest_path.stat().st_mtime, applied)
|
|
8454
8470
|
except OSError:
|
|
8455
8471
|
return None
|
|
8456
|
-
if stamp in
|
|
8457
|
-
return
|
|
8458
|
-
|
|
8472
|
+
if stamp in _INSTRUCTIONS_FACTS:
|
|
8473
|
+
return _INSTRUCTIONS_FACTS[stamp]
|
|
8474
|
+
_INSTRUCTIONS_FACTS[stamp] = None # a repeat of a failing read must not repeat the cost
|
|
8459
8475
|
try:
|
|
8460
|
-
facts =
|
|
8476
|
+
facts = _instructions_facts(pathlib.Path(repo), manifest_path, set(applied))
|
|
8461
8477
|
except SystemExit:
|
|
8462
8478
|
# assemble.die() on a manifest/monolith disagreement. Not an Exception, so it
|
|
8463
8479
|
# would otherwise leave the launcher through every absorber in this file.
|
|
8464
8480
|
return None
|
|
8465
8481
|
except Exception as exc: # noqa: BLE001 — sizing is decoration; picking is not
|
|
8466
|
-
print(f"agent-launch: cannot size the
|
|
8482
|
+
print(f"agent-launch: cannot size the instructions packages in {repo} "
|
|
8467
8483
|
f"({type(exc).__name__}: {exc})", file=sys.stderr)
|
|
8468
8484
|
return None
|
|
8469
|
-
|
|
8485
|
+
_INSTRUCTIONS_FACTS[stamp] = facts
|
|
8470
8486
|
return facts
|
|
8471
8487
|
|
|
8472
8488
|
|
|
@@ -8490,7 +8506,7 @@ def _load_assembler(repo: pathlib.Path):
|
|
|
8490
8506
|
return module
|
|
8491
8507
|
|
|
8492
8508
|
|
|
8493
|
-
def
|
|
8509
|
+
def _instructions_facts(repo: pathlib.Path, manifest_path: pathlib.Path, applied: set) -> dict[str, Any]:
|
|
8494
8510
|
"""The derivation proper, with every failure left to the caller to absorb."""
|
|
8495
8511
|
assemble = _load_assembler(repo)
|
|
8496
8512
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
@@ -8539,14 +8555,14 @@ def _corpus_facts(repo: pathlib.Path, manifest_path: pathlib.Path, applied: set)
|
|
|
8539
8555
|
"guides": len(selected_guides),
|
|
8540
8556
|
"guide_chars": guide_chars(selected_guides),
|
|
8541
8557
|
# A projection can name a domain the installed package does not carry;
|
|
8542
|
-
# the figures above then describe a smaller
|
|
8558
|
+
# the figures above then describe a smaller instructions than the row above them
|
|
8543
8559
|
# claims, so the gap is reported rather than folded in.
|
|
8544
8560
|
"unknown": sorted(applied - set(domains)),
|
|
8545
8561
|
},
|
|
8546
8562
|
}
|
|
8547
8563
|
|
|
8548
8564
|
|
|
8549
|
-
def
|
|
8565
|
+
def _instructions_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
8550
8566
|
"""The panel body proper. The label column is derived at render time from the
|
|
8551
8567
|
widest label in the ACTIVE language and measured in display cells: a pad written
|
|
8552
8568
|
for English cannot align a translated panel."""
|
|
@@ -8555,20 +8571,20 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
|
8555
8571
|
if isinstance(status.get("private"), dict):
|
|
8556
8572
|
current = status["private"]
|
|
8557
8573
|
selection = current.get("selection")
|
|
8558
|
-
selected = ", ".join(selection) if selection else t("
|
|
8559
|
-
return [t("
|
|
8574
|
+
selected = ", ".join(selection) if selection else t("instructions.launch.base")
|
|
8575
|
+
return [t("instructions.private.relationship"),
|
|
8560
8576
|
f"{t('panel.domains.label')} {selected}",
|
|
8561
8577
|
f"{t('panel.version.label')} {current.get('selected_baseline_ref') or '—'}"]
|
|
8562
8578
|
# `versions is None` is the projection saying "this install cannot know" — a packaged
|
|
8563
|
-
# install has no author-side registry (compose/
|
|
8564
|
-
# "unavailable", which is true of the ledger and says nothing about the
|
|
8579
|
+
# install has no author-side registry (compose/instructions-state.py). Three rows then read
|
|
8580
|
+
# "unavailable", which is true of the ledger and says nothing about the instructions the
|
|
8565
8581
|
# user actually has. The package carries the manifest and the monolith the installer
|
|
8566
8582
|
# assembled from, so those rows are answerable from it; the absence is still stated,
|
|
8567
8583
|
# once, on the row it belongs to.
|
|
8568
8584
|
unavailable = status.get("versions") is None
|
|
8569
8585
|
domains = status.get("domains")
|
|
8570
8586
|
applied_raw = domains.get("applied") if isinstance(domains, dict) else None
|
|
8571
|
-
facts =
|
|
8587
|
+
facts = instructions_facts(status)
|
|
8572
8588
|
# An unapplied install is NOT sized: what is deployed is whatever the install put
|
|
8573
8589
|
# there, and the manifest cannot say which. A guess here would read as a reading.
|
|
8574
8590
|
packaged = unavailable and facts is not None and applied_raw is not None
|
|
@@ -8595,10 +8611,10 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
|
8595
8611
|
# rows above describe less than the domain row below them claims.
|
|
8596
8612
|
lines.append(t("panel.domains.unknown").format(
|
|
8597
8613
|
names=", ".join(selected["unknown"])))
|
|
8598
|
-
return lines +
|
|
8614
|
+
return lines + _instructions_domain_lines(status, applied_raw, row, labels[3])
|
|
8599
8615
|
|
|
8600
8616
|
# `versions is None` is the projection saying "this install cannot know" — a packaged
|
|
8601
|
-
# install has no author version/ledger registry (compose/
|
|
8617
|
+
# install has no author version/ledger registry (compose/instructions-state.py). It is not
|
|
8602
8618
|
# `[]`, which would mean a checkout whose registry is genuinely empty, and it must not
|
|
8603
8619
|
# render as `0 placed · 0 versions`: a fabricated zero is worse than a blank, because
|
|
8604
8620
|
# a reader cannot tell it from a real count. The domain rows below stay real, which is
|
|
@@ -8628,10 +8644,10 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
|
8628
8644
|
versions=len(status_list(status.get("versions"))),
|
|
8629
8645
|
)),
|
|
8630
8646
|
]
|
|
8631
|
-
return lines +
|
|
8647
|
+
return lines + _instructions_domain_lines(status, applied_raw, row, labels[3])
|
|
8632
8648
|
|
|
8633
8649
|
|
|
8634
|
-
def
|
|
8650
|
+
def _instructions_domain_lines(status, applied_raw, row, label: str) -> list[str]:
|
|
8635
8651
|
"""The rows both panels end on: what is applied, and a failed apply if there is one.
|
|
8636
8652
|
|
|
8637
8653
|
Shared rather than written twice — a packaged panel that quietly dropped the failed
|
|
@@ -8640,23 +8656,23 @@ def _corpus_domain_lines(status, applied_raw, row, label: str) -> list[str]:
|
|
|
8640
8656
|
if isinstance(status.get("domains"), dict):
|
|
8641
8657
|
applied = None if applied_raw is None else status_list(applied_raw)
|
|
8642
8658
|
lines.append(row(label, t("panel.domains.unset") if applied is None
|
|
8643
|
-
else ", ".join(applied) or t("
|
|
8659
|
+
else ", ".join(applied) or t("instructions.core-only")))
|
|
8644
8660
|
last_apply = status.get("last_apply")
|
|
8645
8661
|
if isinstance(last_apply, dict) and last_apply.get("outcome") not in (None, "applied"):
|
|
8646
8662
|
# A failed apply must be loud on the panel, not a fact buried in a log:
|
|
8647
|
-
# the deployed
|
|
8663
|
+
# the deployed instructions and the requested selection disagree right now.
|
|
8648
8664
|
lines.append(t("panel.last-apply").format(
|
|
8649
8665
|
outcome=last_apply.get("outcome"),
|
|
8650
8666
|
at=last_apply.get("at"),
|
|
8651
|
-
requested=", ".join(status_list(last_apply.get("requested"))) or t("
|
|
8667
|
+
requested=", ".join(status_list(last_apply.get("requested"))) or t("instructions.core-only"),
|
|
8652
8668
|
))
|
|
8653
8669
|
return lines
|
|
8654
8670
|
|
|
8655
8671
|
|
|
8656
|
-
def
|
|
8657
|
-
"""Run
|
|
8672
|
+
def _instructions_screen(ui: TextualUI | None, screen: "Callable[[], None]") -> None:
|
|
8673
|
+
"""Run an instruction screen, absorbing a status shape it cannot render.
|
|
8658
8674
|
|
|
8659
|
-
`
|
|
8675
|
+
`instructions_summary_lines` argued this for the root panel: the projection's depth is
|
|
8660
8676
|
unbounded, its writer is not the only thing that can produce the file, so a status
|
|
8661
8677
|
that cannot be rendered is treated as one that is not projected. That argument was
|
|
8662
8678
|
implemented for the panel alone, and the three screens one keypress deeper read the
|
|
@@ -8666,25 +8682,25 @@ def _corpus_screen(ui: TextualUI | None, screen: "Callable[[], None]") -> None:
|
|
|
8666
8682
|
the top and fatal underneath.
|
|
8667
8683
|
|
|
8668
8684
|
Control flow passes through untouched: LaunchError is the launcher's own refusal,
|
|
8669
|
-
BackRequested and
|
|
8685
|
+
BackRequested and InstructionsApplyRequested are how these screens exit normally, and
|
|
8670
8686
|
KeyboardInterrupt/SystemExit are not Exception. The rest is printed and shown rather
|
|
8671
8687
|
than swallowed, so a defect in the rendering code stays visible.
|
|
8672
8688
|
"""
|
|
8673
8689
|
try:
|
|
8674
8690
|
screen()
|
|
8675
|
-
except (LaunchError, BackRequested,
|
|
8691
|
+
except (LaunchError, BackRequested, InstructionsApplyRequested):
|
|
8676
8692
|
raise
|
|
8677
8693
|
except Exception as exc: # noqa: BLE001 — a status shape must not end the session
|
|
8678
8694
|
print(
|
|
8679
|
-
f"agent-launch: cannot render the
|
|
8695
|
+
f"agent-launch: cannot render the instructions status from {INSTRUCTIONS_STATUS_PATH} "
|
|
8680
8696
|
f"({type(exc).__name__}: {exc}); showing it as not projected. "
|
|
8681
8697
|
"Run: agent-bios install",
|
|
8682
8698
|
file=sys.stderr,
|
|
8683
8699
|
)
|
|
8684
|
-
|
|
8700
|
+
_instructions_info(ui, t("distill.title"), [t("panel.unprojected")])
|
|
8685
8701
|
|
|
8686
8702
|
|
|
8687
|
-
def
|
|
8703
|
+
def _instructions_info(
|
|
8688
8704
|
ui: TextualUI | None, title: str, lines: list[str],
|
|
8689
8705
|
back_hint: str | None = None,
|
|
8690
8706
|
) -> None:
|
|
@@ -8702,12 +8718,12 @@ def _corpus_info(
|
|
|
8702
8718
|
)
|
|
8703
8719
|
]
|
|
8704
8720
|
try:
|
|
8705
|
-
choose(title, options, "back", ui, allow_back=True,
|
|
8721
|
+
choose(title, options, "back", ui, allow_back=True, instructions_lines=lines)
|
|
8706
8722
|
except BackRequested:
|
|
8707
8723
|
pass
|
|
8708
8724
|
|
|
8709
8725
|
|
|
8710
|
-
def
|
|
8726
|
+
def _instructions_rollback_choice(version: str, ui: "TextualUI | None") -> str:
|
|
8711
8727
|
"""The rollback confirmation prompt, so the Esc handling wraps exactly it."""
|
|
8712
8728
|
return choose(
|
|
8713
8729
|
t("distill.rollback.title").format(version=version),
|
|
@@ -8729,13 +8745,13 @@ def _corpus_rollback_choice(version: str, ui: "TextualUI | None") -> str:
|
|
|
8729
8745
|
)
|
|
8730
8746
|
|
|
8731
8747
|
|
|
8732
|
-
def
|
|
8748
|
+
def _instructions_rollback(status: dict[str, Any], version: str, ui: TextualUI | None) -> None:
|
|
8733
8749
|
# Esc means the same thing the Cancel entry means, and this screen offered both while
|
|
8734
8750
|
# honouring only one: BackRequested propagated out of a destructive confirmation as an
|
|
8735
8751
|
# exception, past every caller that had nothing to do with it. A screen that shows a
|
|
8736
8752
|
# Cancel option and a back hint has already told the user those are the same answer.
|
|
8737
8753
|
try:
|
|
8738
|
-
confirm =
|
|
8754
|
+
confirm = _instructions_rollback_choice(version, ui)
|
|
8739
8755
|
except BackRequested:
|
|
8740
8756
|
return
|
|
8741
8757
|
if confirm != "rollback":
|
|
@@ -8746,10 +8762,10 @@ def _corpus_rollback(status: dict[str, Any], version: str, ui: TextualUI | None)
|
|
|
8746
8762
|
# an outcome this screen already knows how to report.
|
|
8747
8763
|
repo = status.get("repo")
|
|
8748
8764
|
if not isinstance(repo, str) or not repo:
|
|
8749
|
-
|
|
8765
|
+
_instructions_info(ui, t("distill.rollback.failed"),
|
|
8750
8766
|
[t("panel.unprojected")], back_hint=t("distill.back.hint"))
|
|
8751
8767
|
return
|
|
8752
|
-
script = pathlib.Path(repo) / "compose/
|
|
8768
|
+
script = pathlib.Path(repo) / "compose/instructions-state.py"
|
|
8753
8769
|
result = subprocess.run(
|
|
8754
8770
|
[sys.executable, str(script), "rollback", "--version", version],
|
|
8755
8771
|
capture_output=True,
|
|
@@ -8761,14 +8777,14 @@ def _corpus_rollback(status: dict[str, Any], version: str, ui: TextualUI | None)
|
|
|
8761
8777
|
t("distill.rollback.done") if result.returncode == 0
|
|
8762
8778
|
else t("distill.rollback.failed")
|
|
8763
8779
|
)
|
|
8764
|
-
|
|
8780
|
+
_instructions_info(ui, verdict, [tail] if tail else [])
|
|
8765
8781
|
|
|
8766
8782
|
|
|
8767
|
-
def
|
|
8783
|
+
def _instructions_versions(ui: TextualUI | None) -> None:
|
|
8768
8784
|
while True:
|
|
8769
|
-
status =
|
|
8785
|
+
status = load_instructions_status()
|
|
8770
8786
|
if status is None:
|
|
8771
|
-
|
|
8787
|
+
_instructions_info(ui, t("distill.versions.label"), instructions_summary_lines(None))
|
|
8772
8788
|
return
|
|
8773
8789
|
current = status.get("current_version")
|
|
8774
8790
|
options = []
|
|
@@ -8809,28 +8825,28 @@ def _corpus_versions(ui: TextualUI | None) -> None:
|
|
|
8809
8825
|
options[0].value,
|
|
8810
8826
|
ui,
|
|
8811
8827
|
allow_back=True,
|
|
8812
|
-
|
|
8828
|
+
instructions_lines=instructions_summary_lines(status),
|
|
8813
8829
|
)
|
|
8814
8830
|
except BackRequested:
|
|
8815
8831
|
return
|
|
8816
8832
|
if selected == "back":
|
|
8817
8833
|
return
|
|
8818
8834
|
if selected == current:
|
|
8819
|
-
|
|
8835
|
+
_instructions_info(
|
|
8820
8836
|
ui,
|
|
8821
8837
|
t("distill.version.applied.title").format(version=selected),
|
|
8822
8838
|
[t("distill.version.applied.line")],
|
|
8823
8839
|
)
|
|
8824
8840
|
continue
|
|
8825
|
-
|
|
8841
|
+
_instructions_rollback(status, selected, ui)
|
|
8826
8842
|
|
|
8827
8843
|
|
|
8828
|
-
def
|
|
8829
|
-
"""v1: the
|
|
8844
|
+
def _instructions_packages(ui: TextualUI | None) -> None:
|
|
8845
|
+
"""v1: the instructions ships as a single core package; the list shape is ready
|
|
8830
8846
|
for the domain-packaging backlog to populate with real packages."""
|
|
8831
|
-
status =
|
|
8847
|
+
status = load_instructions_status()
|
|
8832
8848
|
if status is None:
|
|
8833
|
-
|
|
8849
|
+
_instructions_info(ui, t("distill.packages.label"), instructions_summary_lines(None))
|
|
8834
8850
|
return
|
|
8835
8851
|
layers = status.get("summary", {}).get("placed_by_layer", {})
|
|
8836
8852
|
options = [
|
|
@@ -8852,7 +8868,7 @@ def _corpus_packages(ui: TextualUI | None) -> None:
|
|
|
8852
8868
|
"back",
|
|
8853
8869
|
ui,
|
|
8854
8870
|
allow_back=True,
|
|
8855
|
-
|
|
8871
|
+
instructions_lines=instructions_summary_lines(status),
|
|
8856
8872
|
)
|
|
8857
8873
|
except BackRequested:
|
|
8858
8874
|
pass
|
|
@@ -8864,7 +8880,7 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
|
|
|
8864
8880
|
Returns "start" to launch the session-distill preset, "back" otherwise.
|
|
8865
8881
|
"""
|
|
8866
8882
|
while True:
|
|
8867
|
-
status =
|
|
8883
|
+
status = load_instructions_status()
|
|
8868
8884
|
options = [
|
|
8869
8885
|
MenuOption(
|
|
8870
8886
|
"start",
|
|
@@ -8894,7 +8910,7 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
|
|
|
8894
8910
|
"start",
|
|
8895
8911
|
ui,
|
|
8896
8912
|
allow_back=True,
|
|
8897
|
-
|
|
8913
|
+
instructions_lines=instructions_summary_lines(status),
|
|
8898
8914
|
)
|
|
8899
8915
|
except BackRequested:
|
|
8900
8916
|
return "back"
|
|
@@ -8903,9 +8919,9 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
|
|
|
8903
8919
|
if selected == "start":
|
|
8904
8920
|
return "start"
|
|
8905
8921
|
if selected == "packages":
|
|
8906
|
-
|
|
8922
|
+
_instructions_screen(ui, lambda: _instructions_packages(ui))
|
|
8907
8923
|
elif selected == "versions":
|
|
8908
|
-
|
|
8924
|
+
_instructions_screen(ui, lambda: _instructions_versions(ui))
|
|
8909
8925
|
|
|
8910
8926
|
|
|
8911
8927
|
def preset_mode(data: dict[str, Any]) -> str:
|
|
@@ -8927,8 +8943,8 @@ def mode_default_preset(presets: dict[str, Any], mode: str) -> str | None:
|
|
|
8927
8943
|
return names[0] if names else None
|
|
8928
8944
|
|
|
8929
8945
|
|
|
8930
|
-
class
|
|
8931
|
-
"""Raised out of the interactive flow when the user confirms
|
|
8946
|
+
class InstructionsApplyRequested(Exception):
|
|
8947
|
+
"""Raised out of the interactive flow when the user confirms an instruction
|
|
8932
8948
|
selection: the Textual app must be torn down before the installer owns the
|
|
8933
8949
|
terminal, so the request travels as control flow and main() runs the apply
|
|
8934
8950
|
in the plain terminal, then re-enters the picker."""
|
|
@@ -8938,22 +8954,22 @@ class CorpusApplyRequested(Exception):
|
|
|
8938
8954
|
self.selection = selection
|
|
8939
8955
|
|
|
8940
8956
|
|
|
8941
|
-
class
|
|
8957
|
+
class InstructionsStudioRequested(Exception):
|
|
8942
8958
|
"""Release Textual's terminal before starting the standalone manager."""
|
|
8943
8959
|
|
|
8944
8960
|
|
|
8945
|
-
|
|
8961
|
+
INSTRUCTIONS_OPTION = "__instructions__"
|
|
8946
8962
|
# A plain value, because the numbered prompt prints it as the default label.
|
|
8947
8963
|
# Collision with a domain id is structurally impossible: domain slugs come from
|
|
8948
8964
|
# compose/domains.json, which this repo owns and which carries no "apply".
|
|
8949
|
-
|
|
8965
|
+
INSTRUCTIONS_APPLY = "apply"
|
|
8950
8966
|
|
|
8951
8967
|
|
|
8952
|
-
def
|
|
8968
|
+
def run_instructions_apply(selection: list[str]) -> None:
|
|
8953
8969
|
"""Run the installer's onboard loop for the selection, in the caller's
|
|
8954
8970
|
terminal. The installer alone deploys, canaries, and records the outcome;
|
|
8955
8971
|
this function only streams it and reports the exit."""
|
|
8956
|
-
status =
|
|
8972
|
+
status = load_instructions_status() or {}
|
|
8957
8973
|
domains = ",".join(selection) if selection else "none"
|
|
8958
8974
|
interaction = [] if os.environ.get("AGENT_BIOS_LEGACY_INSTALL") == "1" else ["--non-interactive"]
|
|
8959
8975
|
front = shutil.which("agent-bios")
|
|
@@ -8976,15 +8992,15 @@ def run_corpus_apply(selection: list[str]) -> None:
|
|
|
8976
8992
|
)
|
|
8977
8993
|
return
|
|
8978
8994
|
argv = ["bash", str(installer), "onboard", *interaction, "--domains", domains]
|
|
8979
|
-
print(f"\nagent-launch: applying
|
|
8995
|
+
print(f"\nagent-launch: applying instructions selection: {' '.join(argv)}\n", flush=True)
|
|
8980
8996
|
proc = subprocess.run(argv)
|
|
8981
8997
|
if proc.returncode != 0:
|
|
8982
8998
|
print(
|
|
8983
|
-
f"\nagent-launch:
|
|
8984
|
-
"
|
|
8999
|
+
f"\nagent-launch: instructions apply FAILED (exit {proc.returncode}); the "
|
|
9000
|
+
"instructions panel shows the recorded outcome.", file=sys.stderr,
|
|
8985
9001
|
)
|
|
8986
9002
|
else:
|
|
8987
|
-
print("\nagent-launch:
|
|
9003
|
+
print("\nagent-launch: instructions selection applied.", flush=True)
|
|
8988
9004
|
|
|
8989
9005
|
|
|
8990
9006
|
def checkbox_label(checked: bool, name: str, trailing: str = ""):
|
|
@@ -9002,25 +9018,25 @@ def checkbox_label(checked: bool, name: str, trailing: str = ""):
|
|
|
9002
9018
|
return Text.assemble(*box, name, (trailing, "dim"))
|
|
9003
9019
|
|
|
9004
9020
|
|
|
9005
|
-
def
|
|
9021
|
+
def _instructions_domain_content(name: str, fallback: str = "") -> str:
|
|
9006
9022
|
"""Localized content help; unfamiliar packages retain their manifest description."""
|
|
9007
9023
|
descriptions = {
|
|
9008
|
-
"builder-base": t("
|
|
9009
|
-
"llm-pipeline-dev": t("
|
|
9010
|
-
"multi-agent-orchestration": t("
|
|
9011
|
-
"visualization-docs": t("
|
|
9012
|
-
"office-work": t("
|
|
9024
|
+
"builder-base": t("instructions.domain.builder-base"),
|
|
9025
|
+
"llm-pipeline-dev": t("instructions.domain.llm-pipeline-dev"),
|
|
9026
|
+
"multi-agent-orchestration": t("instructions.domain.multi-agent-orchestration"),
|
|
9027
|
+
"visualization-docs": t("instructions.domain.visualization-docs"),
|
|
9028
|
+
"office-work": t("instructions.domain.office-work"),
|
|
9013
9029
|
}
|
|
9014
9030
|
return descriptions.get(name, fallback).strip()
|
|
9015
9031
|
|
|
9016
9032
|
|
|
9017
|
-
def
|
|
9033
|
+
def instructions_domain_labels() -> dict[str, str]:
|
|
9018
9034
|
return {
|
|
9019
|
-
"builder-base": t("
|
|
9020
|
-
"llm-pipeline-dev": t("
|
|
9021
|
-
"multi-agent-orchestration": t("
|
|
9022
|
-
"visualization-docs": t("
|
|
9023
|
-
"office-work": t("
|
|
9035
|
+
"builder-base": t("instructions.domain.builder-base.label"),
|
|
9036
|
+
"llm-pipeline-dev": t("instructions.domain.llm-pipeline-dev.label"),
|
|
9037
|
+
"multi-agent-orchestration": t("instructions.domain.multi-agent-orchestration.label"),
|
|
9038
|
+
"visualization-docs": t("instructions.domain.visualization-docs.label"),
|
|
9039
|
+
"office-work": t("instructions.domain.office-work.label"),
|
|
9024
9040
|
}
|
|
9025
9041
|
|
|
9026
9042
|
|
|
@@ -9031,16 +9047,16 @@ def preset_descriptions() -> dict[str, str]:
|
|
|
9031
9047
|
"deep-review": t("preset.deep-review.description"),
|
|
9032
9048
|
"fast-batch": t("preset.fast-batch.description"),
|
|
9033
9049
|
"solo": t("preset.solo.description"),
|
|
9034
|
-
"vanilla": (t("
|
|
9050
|
+
"vanilla": (t("instructions.private.vanilla") if private_instructions_enabled()
|
|
9035
9051
|
else t("preset.vanilla.description")),
|
|
9036
9052
|
}
|
|
9037
9053
|
|
|
9038
9054
|
|
|
9039
|
-
def
|
|
9055
|
+
def _instructions_launch_description(status: dict[str, Any] | None) -> str:
|
|
9040
9056
|
"""Explain the installed selection shared by modes without claiming a launch applies it."""
|
|
9041
|
-
if
|
|
9042
|
-
return t("
|
|
9043
|
-
lines = [t("
|
|
9057
|
+
if private_instructions_enabled():
|
|
9058
|
+
return t("instructions.private.relationship")
|
|
9059
|
+
lines = [t("instructions.launch.relationship")]
|
|
9044
9060
|
domains = (status or {}).get("domains")
|
|
9045
9061
|
applied = domains.get("applied") if isinstance(domains, dict) else None
|
|
9046
9062
|
if not isinstance(applied, list) or not all(isinstance(name, str) for name in applied):
|
|
@@ -9048,12 +9064,12 @@ def _corpus_launch_description(status: dict[str, Any] | None) -> str:
|
|
|
9048
9064
|
if isinstance(domains, dict) and "applied" in domains and applied is None
|
|
9049
9065
|
else t("panel.unprojected"))
|
|
9050
9066
|
return "\n".join([*lines, message])
|
|
9051
|
-
lines += [t("
|
|
9052
|
-
facts = (
|
|
9053
|
-
labels =
|
|
9067
|
+
lines += [t("instructions.launch.applied"), t("instructions.launch.base")]
|
|
9068
|
+
facts = (instructions_facts(status) or {}).get("domains", {})
|
|
9069
|
+
labels = instructions_domain_labels()
|
|
9054
9070
|
for name in applied:
|
|
9055
|
-
content =
|
|
9056
|
-
name, facts.get(name, {}).get("description") or t("
|
|
9071
|
+
content = _instructions_domain_content(
|
|
9072
|
+
name, facts.get(name, {}).get("description") or t("instructions.launch.unknown"),
|
|
9057
9073
|
)
|
|
9058
9074
|
lines.append(f"• {labels.get(name, name)}: {content.splitlines()[0]}")
|
|
9059
9075
|
return "\n".join(lines)
|
|
@@ -9068,53 +9084,53 @@ def _domain_description(info: dict[str, Any] | None, name: str = "") -> str:
|
|
|
9068
9084
|
and they are kept apart because they are spent differently: rules are in the global
|
|
9069
9085
|
that every session and every subagent loads, guides are on disk and read only when a
|
|
9070
9086
|
rule points at one."""
|
|
9071
|
-
content =
|
|
9087
|
+
content = _instructions_domain_content(name, info.get("description", "") if info else "")
|
|
9072
9088
|
if info is None:
|
|
9073
|
-
return "\n\n".join(filter(None, [content, t("
|
|
9089
|
+
return "\n\n".join(filter(None, [content, t("instructions.toggle.description")]))
|
|
9074
9090
|
# Both keys are named at a literal call site rather than chosen into a variable:
|
|
9075
9091
|
# the catalog gate finds keys by reading the quoted argument of each t() call, so a
|
|
9076
9092
|
# computed key is a string no language is ever checked for.
|
|
9077
9093
|
if info["guides"]:
|
|
9078
|
-
sizes = t("
|
|
9094
|
+
sizes = t("instructions.size.detail").format(
|
|
9079
9095
|
rules=info["rules"],
|
|
9080
9096
|
size=size_label(info["chars"]),
|
|
9081
9097
|
guides=info["guides"],
|
|
9082
9098
|
guide_size=size_label(info["guide_chars"]),
|
|
9083
9099
|
)
|
|
9084
9100
|
else:
|
|
9085
|
-
sizes = t("
|
|
9101
|
+
sizes = t("instructions.size.detail.noguides").format(
|
|
9086
9102
|
rules=info["rules"], size=size_label(info["chars"]),
|
|
9087
9103
|
)
|
|
9088
|
-
return "\n\n".join(filter(None, [content, sizes, t("
|
|
9104
|
+
return "\n\n".join(filter(None, [content, sizes, t("instructions.toggle.description")]))
|
|
9089
9105
|
|
|
9090
9106
|
|
|
9091
|
-
def
|
|
9107
|
+
def instructions_checklist(ui: "TextualUI | None") -> None:
|
|
9092
9108
|
"""Toggle-and-apply loop over the optional domain packages.
|
|
9093
9109
|
|
|
9094
9110
|
The list and the applied set come from corpus-status.json — the installer's
|
|
9095
9111
|
projection — never from repo paths this launcher cannot know. Selection state
|
|
9096
9112
|
lives only on this screen; Apply hands the exact set to the installer
|
|
9097
9113
|
(raising through the Textual app so the terminal is free), and Esc leaves the
|
|
9098
|
-
deployed
|
|
9099
|
-
status =
|
|
9114
|
+
deployed instructions untouched."""
|
|
9115
|
+
status = load_instructions_status()
|
|
9100
9116
|
domains = (status or {}).get("domains")
|
|
9101
9117
|
if not isinstance(domains, dict) or not isinstance(domains.get("available"), list):
|
|
9102
|
-
|
|
9103
|
-
ui, t("
|
|
9104
|
-
[t("
|
|
9105
|
-
back_hint=t("
|
|
9118
|
+
_instructions_info(
|
|
9119
|
+
ui, t("instructions.title"),
|
|
9120
|
+
[t("instructions.unavailable.line1"), t("instructions.unavailable.line2")],
|
|
9121
|
+
back_hint=t("instructions.back.hint"),
|
|
9106
9122
|
)
|
|
9107
9123
|
return
|
|
9108
9124
|
available = domains["available"]
|
|
9109
9125
|
if not available:
|
|
9110
9126
|
# A list that is empty passes the shape check above and then builds a menu whose
|
|
9111
9127
|
# only row is a disabled Apply, which `choose` refuses as "no available options"
|
|
9112
|
-
# — a LaunchError, which `
|
|
9128
|
+
# — a LaunchError, which `_instructions_screen` rethrows by design, so the session ends
|
|
9113
9129
|
# on a projection that is not even malformed. Told, not raised. Its own message
|
|
9114
9130
|
# rather than the one above: "run the installer" is wrong advice for a projection
|
|
9115
9131
|
# that read fine and simply lists nothing optional.
|
|
9116
|
-
|
|
9117
|
-
back_hint=t("
|
|
9132
|
+
_instructions_info(ui, t("instructions.title"), [t("instructions.none.line")],
|
|
9133
|
+
back_hint=t("instructions.back.hint"))
|
|
9118
9134
|
return
|
|
9119
9135
|
applied_raw = domains.get("applied")
|
|
9120
9136
|
# None is "never selected", [] is a verified core+infra-only selection —
|
|
@@ -9127,9 +9143,9 @@ def corpus_checklist(ui: "TextualUI | None") -> None:
|
|
|
9127
9143
|
# read, and every use below falls back to the bare name — a checklist that cannot
|
|
9128
9144
|
# size its packages is still a checklist, and this screen is how a user reaches the
|
|
9129
9145
|
# installer that would repair the projection.
|
|
9130
|
-
facts =
|
|
9146
|
+
facts = instructions_facts(status)
|
|
9131
9147
|
sized = (facts or {}).get("domains", {})
|
|
9132
|
-
labels =
|
|
9148
|
+
labels = instructions_domain_labels()
|
|
9133
9149
|
display_names = {name: f"{labels[name]} ({name})" if name in labels else name for name in available}
|
|
9134
9150
|
column = max(display_width(label) for label in display_names.values()) + 2
|
|
9135
9151
|
while True:
|
|
@@ -9147,35 +9163,35 @@ def corpus_checklist(ui: "TextualUI | None") -> None:
|
|
|
9147
9163
|
]
|
|
9148
9164
|
changed = toggles != applied or never_applied
|
|
9149
9165
|
pending = "" if not changed else (
|
|
9150
|
-
" → " + (", ".join(sorted(toggles)) or t("
|
|
9166
|
+
" → " + (", ".join(sorted(toggles)) or t("instructions.core-only"))
|
|
9151
9167
|
)
|
|
9152
9168
|
options.append(
|
|
9153
9169
|
MenuOption(
|
|
9154
|
-
|
|
9155
|
-
t("
|
|
9156
|
-
t("
|
|
9170
|
+
INSTRUCTIONS_APPLY,
|
|
9171
|
+
t("instructions.apply.label").format(pending=pending),
|
|
9172
|
+
t("instructions.apply.description"),
|
|
9157
9173
|
enabled=changed,
|
|
9158
|
-
unavailable_reason=t("
|
|
9174
|
+
unavailable_reason=t("instructions.apply.unchanged"),
|
|
9159
9175
|
)
|
|
9160
9176
|
)
|
|
9161
9177
|
try:
|
|
9162
9178
|
choice = choose(
|
|
9163
|
-
t("
|
|
9164
|
-
|
|
9165
|
-
t("
|
|
9166
|
-
*([] if not sized else [t("
|
|
9167
|
-
*
|
|
9179
|
+
t("instructions.title"), options, INSTRUCTIONS_APPLY, ui, allow_back=True,
|
|
9180
|
+
instructions_lines=[
|
|
9181
|
+
t("instructions.core.line"),
|
|
9182
|
+
*([] if not sized else [t("instructions.size.legend")]),
|
|
9183
|
+
*instructions_summary_lines(status),
|
|
9168
9184
|
],
|
|
9169
|
-
confirm=
|
|
9185
|
+
confirm=INSTRUCTIONS_APPLY,
|
|
9170
9186
|
)
|
|
9171
9187
|
except BackRequested:
|
|
9172
9188
|
return
|
|
9173
|
-
if choice ==
|
|
9189
|
+
if choice == INSTRUCTIONS_APPLY:
|
|
9174
9190
|
selection = sorted(toggles)
|
|
9175
9191
|
if ui is None:
|
|
9176
|
-
|
|
9192
|
+
run_instructions_apply(selection)
|
|
9177
9193
|
return
|
|
9178
|
-
raise
|
|
9194
|
+
raise InstructionsApplyRequested(selection)
|
|
9179
9195
|
if choice in toggles:
|
|
9180
9196
|
toggles.discard(choice)
|
|
9181
9197
|
else:
|
|
@@ -9197,7 +9213,7 @@ def understand_menu(ui: TextualUI | None) -> str | None:
|
|
|
9197
9213
|
try:
|
|
9198
9214
|
bundles = understand_manager().list_bundles()
|
|
9199
9215
|
if not bundles:
|
|
9200
|
-
|
|
9216
|
+
_instructions_info(ui, t("understand.title"), [t("understand.empty")])
|
|
9201
9217
|
return None
|
|
9202
9218
|
return choose(t("understand.title"), [
|
|
9203
9219
|
MenuOption(row["id"], row["title"],
|
|
@@ -9205,11 +9221,11 @@ def understand_menu(ui: TextualUI | None) -> str | None:
|
|
|
9205
9221
|
count=row["item_count"]))
|
|
9206
9222
|
for row in bundles
|
|
9207
9223
|
], bundles[0]["id"], ui, allow_back=True,
|
|
9208
|
-
|
|
9224
|
+
instructions_lines=[t("understand.description"), t("understand.session.scope")])
|
|
9209
9225
|
except BackRequested:
|
|
9210
9226
|
return None
|
|
9211
9227
|
except (OSError, RuntimeError, ValueError) as exc:
|
|
9212
|
-
|
|
9228
|
+
_instructions_info(ui, t("understand.title"), [str(exc)])
|
|
9213
9229
|
return None
|
|
9214
9230
|
|
|
9215
9231
|
|
|
@@ -9228,24 +9244,24 @@ def shell_connection_menu(ui: TextualUI | None, dry_run: bool = False) -> None:
|
|
|
9228
9244
|
MenuOption("restore", t("shell.restore"), t("shell.restore.description")),
|
|
9229
9245
|
MenuOption("remove", t("shell.remove"), t("shell.remove.description")),
|
|
9230
9246
|
MenuOption("back", t("distill.back.label"), t("shell.back")),
|
|
9231
|
-
], "back", ui, allow_back=True,
|
|
9247
|
+
], "back", ui, allow_back=True, instructions_lines=lines)
|
|
9232
9248
|
if selected == "back":
|
|
9233
9249
|
return
|
|
9234
9250
|
decision = choose(t("shell.confirm"), [
|
|
9235
9251
|
MenuOption("cancel", t("shell.cancel"), t("shell.back")),
|
|
9236
9252
|
MenuOption("apply", t("shell.apply"), t("shell.scope")),
|
|
9237
|
-
], "cancel", ui, allow_back=True,
|
|
9253
|
+
], "cancel", ui, allow_back=True, instructions_lines=lines)
|
|
9238
9254
|
if decision != "apply":
|
|
9239
9255
|
continue
|
|
9240
9256
|
result = manager.apply(selected, dry_run=dry_run)
|
|
9241
9257
|
message = (t("shell.preview") if dry_run else
|
|
9242
9258
|
t("shell.restored") if selected == "restore" else t("shell.removed"))
|
|
9243
|
-
|
|
9259
|
+
_instructions_info(ui, t("shell.title"), [message, *result["changed_paths"],
|
|
9244
9260
|
*result.get("needs_action", [])])
|
|
9245
9261
|
except BackRequested:
|
|
9246
9262
|
return
|
|
9247
9263
|
except (ShellIntegrationError, OSError, RuntimeError) as exc:
|
|
9248
|
-
|
|
9264
|
+
_instructions_info(ui, t("shell.title"), [str(exc)])
|
|
9249
9265
|
# Language names render in their own language BY DESIGN — a reader hunting for
|
|
9250
9266
|
# their language must be able to recognise it whatever UI language is active — so
|
|
9251
9267
|
# these labels are deliberately catalog-independent.
|
|
@@ -9285,7 +9301,7 @@ def choose_language(
|
|
|
9285
9301
|
# refusal is reported and returns to the hub; this is that, for the same
|
|
9286
9302
|
# reason. The catalogs are deliberately not reloaded: the file still holds the
|
|
9287
9303
|
# previous language, so the session keeps rendering what was actually saved.
|
|
9288
|
-
|
|
9304
|
+
_instructions_info(ui, t("language.title"), [str(exc)])
|
|
9289
9305
|
return
|
|
9290
9306
|
load_catalogs(config_path)
|
|
9291
9307
|
|
|
@@ -9314,31 +9330,31 @@ def pick_mode_and_preset(
|
|
|
9314
9330
|
user_names = set(load_user_presets(user_presets_path(config_path))) if config_path else set()
|
|
9315
9331
|
mode = resume_mode
|
|
9316
9332
|
while True:
|
|
9317
|
-
status =
|
|
9318
|
-
|
|
9333
|
+
status = load_instructions_status()
|
|
9334
|
+
instructions_description = _instructions_launch_description(status)
|
|
9319
9335
|
if mode is None:
|
|
9320
9336
|
mode_options = [
|
|
9321
9337
|
MenuOption(SWE_MODE, t("mode.swe.label"),
|
|
9322
|
-
t("mode.swe.description") + "\n\n" +
|
|
9338
|
+
t("mode.swe.description") + "\n\n" + instructions_description),
|
|
9323
9339
|
MenuOption(
|
|
9324
9340
|
DEFAULT_PRESET_MODE,
|
|
9325
9341
|
t("mode.builder.label"),
|
|
9326
|
-
t("mode.builder.description") + "\n\n" +
|
|
9342
|
+
t("mode.builder.description") + "\n\n" + instructions_description,
|
|
9327
9343
|
),
|
|
9328
9344
|
MenuOption(
|
|
9329
9345
|
DISTILL_MODE,
|
|
9330
9346
|
t("mode.distill.label"),
|
|
9331
|
-
t("mode.distill.description") + "\n\n" +
|
|
9347
|
+
t("mode.distill.description") + "\n\n" + instructions_description,
|
|
9332
9348
|
),
|
|
9333
9349
|
]
|
|
9334
9350
|
mode_options.append(
|
|
9335
9351
|
MenuOption(
|
|
9336
|
-
|
|
9337
|
-
t("
|
|
9338
|
-
t("
|
|
9352
|
+
INSTRUCTIONS_OPTION,
|
|
9353
|
+
t("instructions.private.title") if private_instructions_enabled() else t("instructions.label"),
|
|
9354
|
+
t("instructions.private.description") if private_instructions_enabled() else t("instructions.description"),
|
|
9339
9355
|
)
|
|
9340
9356
|
)
|
|
9341
|
-
if
|
|
9357
|
+
if private_instructions_enabled():
|
|
9342
9358
|
mode_options.append(MenuOption(UNDERSTAND_OPTION, t("understand.title"), t("understand.description")))
|
|
9343
9359
|
mode_options.append(MenuOption(SHELL_CONNECTION_OPTION, t("shell.title"), t("shell.description")))
|
|
9344
9360
|
if config_path is not None:
|
|
@@ -9386,16 +9402,16 @@ def pick_mode_and_preset(
|
|
|
9386
9402
|
DEFAULT_PRESET_MODE,
|
|
9387
9403
|
ui,
|
|
9388
9404
|
preview=preview_mode,
|
|
9389
|
-
|
|
9405
|
+
instructions_lines=instructions_summary_lines(status),
|
|
9390
9406
|
)
|
|
9391
|
-
if mode ==
|
|
9392
|
-
if
|
|
9407
|
+
if mode == INSTRUCTIONS_OPTION:
|
|
9408
|
+
if private_instructions_enabled():
|
|
9393
9409
|
if ui is not None:
|
|
9394
|
-
raise
|
|
9395
|
-
|
|
9410
|
+
raise InstructionsStudioRequested()
|
|
9411
|
+
open_instructions_studio()
|
|
9396
9412
|
mode = None
|
|
9397
9413
|
continue
|
|
9398
|
-
|
|
9414
|
+
_instructions_screen(ui, lambda: instructions_checklist(ui))
|
|
9399
9415
|
mode = None
|
|
9400
9416
|
continue
|
|
9401
9417
|
if mode == SHELL_CONNECTION_OPTION:
|
|
@@ -9427,7 +9443,7 @@ def pick_mode_and_preset(
|
|
|
9427
9443
|
data["label"],
|
|
9428
9444
|
(preset_descriptions().get(name, data.get("description", ""))
|
|
9429
9445
|
if name not in user_names else data.get("description", ""))
|
|
9430
|
-
+ "\n\n" +
|
|
9446
|
+
+ "\n\n" + instructions_description,
|
|
9431
9447
|
)
|
|
9432
9448
|
for name, data in presets.items()
|
|
9433
9449
|
if preset_mode(data) == mode
|
|
@@ -9438,7 +9454,7 @@ def pick_mode_and_preset(
|
|
|
9438
9454
|
CUSTOM_PRESET,
|
|
9439
9455
|
t("preset.custom.label"),
|
|
9440
9456
|
t("preset.custom.description")
|
|
9441
|
-
+ "\n\n" +
|
|
9457
|
+
+ "\n\n" + instructions_description,
|
|
9442
9458
|
)
|
|
9443
9459
|
)
|
|
9444
9460
|
default = mode_default_preset(presets, mode) or CUSTOM_PRESET
|
|
@@ -9609,11 +9625,11 @@ def child_agent_registrations(
|
|
|
9609
9625
|
raise LaunchError(f"Codex agent template requires description: {source}")
|
|
9610
9626
|
data["model"] = plan["tiers"][tier]["model"]
|
|
9611
9627
|
data["model_reasoning_effort"] = tier_effort(plan, tier)
|
|
9612
|
-
if plan.get("
|
|
9613
|
-
# The child's own instruction body and the selected parent
|
|
9628
|
+
if plan.get("instructions_instruction_text"):
|
|
9629
|
+
# The child's own instruction body and the selected parent instructions are
|
|
9614
9630
|
# composed before the content-addressed config path is derived.
|
|
9615
9631
|
existing = data.get("developer_instructions", "")
|
|
9616
|
-
data["developer_instructions"] = existing + "\n\n" + plan["
|
|
9632
|
+
data["developer_instructions"] = existing + "\n\n" + plan["instructions_instruction_text"]
|
|
9617
9633
|
lines = []
|
|
9618
9634
|
for key, value in data.items():
|
|
9619
9635
|
if not isinstance(value, (str, int, float, bool)):
|
|
@@ -10123,7 +10139,7 @@ def delegation_clause(plan: dict[str, Any]) -> str:
|
|
|
10123
10139
|
|
|
10124
10140
|
The CLI's line carries its own exception, so the fix is to satisfy it rather than argue
|
|
10125
10141
|
with it. Delegation IS the user's standing request — they chose the preset, and the
|
|
10126
|
-
|
|
10142
|
+
instructions's standing spawn policy is their instruction — so the contract says so in the
|
|
10127
10143
|
words that clause is looking for. Nothing is overridden; a fact that was already true
|
|
10128
10144
|
is simply stated where the reader can see it."""
|
|
10129
10145
|
if sweep_main(plan):
|
|
@@ -10164,8 +10180,8 @@ def claude_agents(plan: dict[str, Any]) -> str:
|
|
|
10164
10180
|
# allowlist enforces its one-rule-per-item contract instead of merely
|
|
10165
10181
|
# restating it in the prompt.
|
|
10166
10182
|
prompt += " Apply one explicit read-only rule per item; do not make semantic judgments."
|
|
10167
|
-
if plan.get("
|
|
10168
|
-
prompt += "\n\n" + plan["
|
|
10183
|
+
if plan.get("instructions_instruction_text"):
|
|
10184
|
+
prompt += "\n\n" + plan["instructions_instruction_text"]
|
|
10169
10185
|
role = {
|
|
10170
10186
|
"description": f"{tier.upper()} tier: {format_model_effort(binding['model'], effort)}",
|
|
10171
10187
|
"prompt": prompt,
|
|
@@ -10647,7 +10663,7 @@ def print_summary(
|
|
|
10647
10663
|
"(permission mode, not OS sandbox)",
|
|
10648
10664
|
file=stream,
|
|
10649
10665
|
)
|
|
10650
|
-
if
|
|
10666
|
+
if private_instructions_enabled():
|
|
10651
10667
|
print(
|
|
10652
10668
|
" Global files "
|
|
10653
10669
|
+ t("global-instructions.summary").format(
|
|
@@ -10729,16 +10745,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
10729
10745
|
)
|
|
10730
10746
|
parser.add_argument("--yes", action="store_true", help="skip launch confirmation")
|
|
10731
10747
|
parser.add_argument("--dry-run", action="store_true", help="print projection without launching")
|
|
10732
|
-
parser.add_argument("--corpus", action="store_true", help="open private
|
|
10733
|
-
parser.add_argument("--understand", metavar="BUNDLE", help="start an interactive learning session for
|
|
10734
|
-
parser.add_argument("--corpus-domains", help="domain selection for this activated session only")
|
|
10735
|
-
parser.add_argument("--corpus-native", action="store_true", help="opt into selected
|
|
10748
|
+
parser.add_argument("--instructions", "--corpus", action="store_true", help="open private Instructions Studio")
|
|
10749
|
+
parser.add_argument("--understand", metavar="BUNDLE", help="start an interactive learning session for an instruction bundle; list with agent-bios understand list")
|
|
10750
|
+
parser.add_argument("--instructions-domains", "--corpus-domains", help="domain selection for this activated session only")
|
|
10751
|
+
parser.add_argument("--instructions-native", "--corpus-native", action="store_true", help="opt into selected instructions hooks on either host and Claude native agents for this session only")
|
|
10736
10752
|
parser.add_argument(
|
|
10737
10753
|
"--exclude-global-instructions",
|
|
10738
10754
|
action="store_true",
|
|
10739
10755
|
help="omit only personal global AGENTS.md/CLAUDE.md files and imports for this private Claude session",
|
|
10740
10756
|
)
|
|
10741
|
-
parser.add_argument("--resume-session", help="resume a host session with its pinned
|
|
10757
|
+
parser.add_argument("--resume-session", help="resume a host session with its pinned instructions")
|
|
10742
10758
|
parser.add_argument(
|
|
10743
10759
|
"--verify-receipts", nargs=2, metavar=("PLAN", "RECEIPTS"),
|
|
10744
10760
|
help="adjudicate a ReviewPlan/v1 record against a ReviewReceipts/v1 bundle and exit",
|
|
@@ -10810,14 +10826,14 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
10810
10826
|
hostless = (
|
|
10811
10827
|
args.verify_receipts or args.emit_receipt or args.fold_receipts
|
|
10812
10828
|
or args.check_adapter or args.compile_criterion or args.check_findings
|
|
10813
|
-
or args.check_schema_flag or args.
|
|
10829
|
+
or args.check_schema_flag or args.instructions
|
|
10814
10830
|
)
|
|
10815
10831
|
if args.host is None and not hostless:
|
|
10816
10832
|
parser.error("the following arguments are required: host")
|
|
10817
|
-
if args.understand and (args.preset or args.custom or args.
|
|
10818
|
-
or args.
|
|
10833
|
+
if args.understand and (args.preset or args.custom or args.instructions or args.resume_session
|
|
10834
|
+
or args.instructions_native or args.instructions_domains is not None
|
|
10819
10835
|
or args.exclude_global_instructions or args.forward):
|
|
10820
|
-
parser.error("--understand selects its own learning setup and cannot be combined with preset,
|
|
10836
|
+
parser.error("--understand selects its own learning setup and cannot be combined with preset, instructions, resume, custom, global-exclusion or forwarded options")
|
|
10821
10837
|
if args.evidence and not args.emit_receipt:
|
|
10822
10838
|
parser.error("--evidence only applies to --emit-receipt")
|
|
10823
10839
|
if args.packet and not args.verify_receipts:
|
|
@@ -11054,8 +11070,8 @@ def _tolerate_narrow_stdout() -> None:
|
|
|
11054
11070
|
def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
11055
11071
|
_tolerate_narrow_stdout()
|
|
11056
11072
|
args = parse_args(drop_check_adapter_separator(argv))
|
|
11057
|
-
if args.
|
|
11058
|
-
|
|
11073
|
+
if args.instructions:
|
|
11074
|
+
open_instructions_studio()
|
|
11059
11075
|
return 0
|
|
11060
11076
|
if args.verify_receipts:
|
|
11061
11077
|
return verify_receipts_command(
|
|
@@ -11080,7 +11096,7 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11080
11096
|
return check_adapter_command(args.check_adapter[0], args.check_adapter[1:])
|
|
11081
11097
|
config_path = args.config.expanduser()
|
|
11082
11098
|
generation = None
|
|
11083
|
-
if
|
|
11099
|
+
if private_instructions_enabled():
|
|
11084
11100
|
bare = not args.preset and not args.custom and not args.understand and not args.dry_run and (
|
|
11085
11101
|
args.no_tui or bool(args.forward) or os.environ.get("AGENT_LAUNCH_TUI") == "0"
|
|
11086
11102
|
or not (sys.stdin.isatty() and sys.stdout.isatty()))
|
|
@@ -11090,7 +11106,7 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11090
11106
|
config = load_config(config_path)
|
|
11091
11107
|
command, bare_args = resolve_backend(config, args.host)
|
|
11092
11108
|
if args.exclude_global_instructions and not args.resume_session:
|
|
11093
|
-
if not
|
|
11109
|
+
if not private_instructions_enabled():
|
|
11094
11110
|
raise LaunchError(
|
|
11095
11111
|
"excluding global instruction files requires an agent-bios activated session"
|
|
11096
11112
|
)
|
|
@@ -11107,30 +11123,30 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11107
11123
|
for preset in config.get("presets", {}).values():
|
|
11108
11124
|
if isinstance(preset, dict):
|
|
11109
11125
|
preset["include_global_instructions"] = False
|
|
11110
|
-
if args.
|
|
11111
|
-
raise LaunchError("--
|
|
11112
|
-
if
|
|
11126
|
+
if args.instructions_native and not private_instructions_enabled():
|
|
11127
|
+
raise LaunchError("--instructions-native requires a private installation; run agent-bios install first")
|
|
11128
|
+
if private_instructions_enabled():
|
|
11113
11129
|
# Private templates retain the native host's config home and registrations.
|
|
11114
|
-
private_root =
|
|
11130
|
+
private_root = instructions_package_root()
|
|
11115
11131
|
os.environ["AGENT_BIOS_PACKAGE_ROOT"] = str(private_root)
|
|
11116
11132
|
config["hosts"]["codex"]["agent_templates"] = {
|
|
11117
11133
|
tier: str(private_root / "codex/agents" / f"{tier}.toml") for tier in SPAWNABLE_TIERS
|
|
11118
11134
|
}
|
|
11119
11135
|
if args.resume_session:
|
|
11120
11136
|
if (
|
|
11121
|
-
args.
|
|
11122
|
-
or args.
|
|
11137
|
+
args.instructions_native
|
|
11138
|
+
or args.instructions_domains is not None
|
|
11123
11139
|
or args.exclude_global_instructions
|
|
11124
11140
|
):
|
|
11125
11141
|
raise LaunchError(
|
|
11126
|
-
"resume uses its pinned
|
|
11142
|
+
"resume uses its pinned instructions/native/global-instruction configuration; "
|
|
11127
11143
|
"start a new session to change it"
|
|
11128
11144
|
)
|
|
11129
|
-
if not
|
|
11130
|
-
raise LaunchError("
|
|
11131
|
-
store =
|
|
11132
|
-
import
|
|
11133
|
-
return
|
|
11145
|
+
if not private_instructions_enabled():
|
|
11146
|
+
raise LaunchError("an instruction-pinned resume requires a private installation")
|
|
11147
|
+
store = instructions_store()
|
|
11148
|
+
import instructions_session
|
|
11149
|
+
return instructions_session.launch(command, [], store.state_root, args.host, {},
|
|
11134
11150
|
resume_id=args.resume_session)
|
|
11135
11151
|
nudge = session_distill_nudge(config)
|
|
11136
11152
|
if nudge:
|
|
@@ -11148,11 +11164,11 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11148
11164
|
bypass = args.no_tui or bool(args.forward) or os.environ.get("AGENT_LAUNCH_TUI") == "0"
|
|
11149
11165
|
if (bypass or not tty) and not args.preset and not args.custom and not args.understand and not args.dry_run:
|
|
11150
11166
|
if (
|
|
11151
|
-
args.
|
|
11152
|
-
or args.
|
|
11167
|
+
args.instructions_native
|
|
11168
|
+
or args.instructions_domains is not None
|
|
11153
11169
|
or args.exclude_global_instructions
|
|
11154
11170
|
):
|
|
11155
|
-
raise LaunchError("
|
|
11171
|
+
raise LaunchError("instructions launch options require --preset NAME or an interactive configured launch")
|
|
11156
11172
|
# The bare launch: nothing decides policy but the backend's own bare-launch
|
|
11157
11173
|
# arguments. Every other path below projects the preset's policy instead.
|
|
11158
11174
|
exec_backend(command, [*bare_args, *args.forward])
|
|
@@ -11212,26 +11228,26 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11212
11228
|
shell_dry_run=args.dry_run,
|
|
11213
11229
|
)
|
|
11214
11230
|
break
|
|
11215
|
-
except
|
|
11231
|
+
except InstructionsApplyRequested as request:
|
|
11216
11232
|
# The Textual app is already torn down; the installer owns the
|
|
11217
11233
|
# terminal for the duration, and the picker re-opens on a fresh
|
|
11218
11234
|
# status projection afterwards.
|
|
11219
|
-
|
|
11235
|
+
run_instructions_apply(request.selection)
|
|
11220
11236
|
continue
|
|
11221
|
-
except
|
|
11222
|
-
|
|
11237
|
+
except InstructionsStudioRequested:
|
|
11238
|
+
open_instructions_studio()
|
|
11223
11239
|
continue
|
|
11224
11240
|
except UnderstandRequested as request:
|
|
11225
11241
|
plan = build_understand_plan(config, args.host, request.bundle_id)
|
|
11226
11242
|
break
|
|
11227
11243
|
learning = None
|
|
11228
11244
|
if plan.get("understand_bundle"):
|
|
11229
|
-
if args.forward or args.
|
|
11230
|
-
raise LaunchError("understand! cannot use forwarded arguments or native
|
|
11245
|
+
if args.forward or args.instructions_native or args.instructions_domains is not None:
|
|
11246
|
+
raise LaunchError("understand! cannot use forwarded arguments or native instructions activation")
|
|
11231
11247
|
learning = understand_manager().show(plan["understand_bundle"])
|
|
11232
11248
|
validate_review_setup(plan)
|
|
11233
11249
|
if not plan.get("include_global_instructions", True):
|
|
11234
|
-
if not
|
|
11250
|
+
if not private_instructions_enabled():
|
|
11235
11251
|
raise LaunchError(
|
|
11236
11252
|
"excluding global instruction files requires an agent-bios activated session"
|
|
11237
11253
|
)
|
|
@@ -11246,19 +11262,19 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11246
11262
|
)
|
|
11247
11263
|
snapshot = None
|
|
11248
11264
|
store = None
|
|
11249
|
-
if
|
|
11250
|
-
store =
|
|
11265
|
+
if private_instructions_enabled() and not plan_projects_nothing(plan):
|
|
11266
|
+
store = instructions_store()
|
|
11251
11267
|
selected = None
|
|
11252
|
-
if args.
|
|
11253
|
-
raw = [x.strip() for x in args.
|
|
11268
|
+
if args.instructions_domains is not None:
|
|
11269
|
+
raw = [x.strip() for x in args.instructions_domains.split(",") if x.strip()]
|
|
11254
11270
|
if "none" in raw and raw != ["none"]:
|
|
11255
|
-
raise LaunchError("--
|
|
11271
|
+
raise LaunchError("--instructions-domains none cannot be combined with other domains")
|
|
11256
11272
|
selected = [] if raw == ["none"] else [
|
|
11257
11273
|
x if x.startswith("@") else f"@agent-bios/core/{x}" for x in raw
|
|
11258
11274
|
]
|
|
11259
11275
|
snapshot = _snapshot_from_config(store, config_path, generation, args.host, selected,
|
|
11260
|
-
dry_run=args.dry_run, native=args.
|
|
11261
|
-
plan["
|
|
11276
|
+
dry_run=args.dry_run, native=args.instructions_native)
|
|
11277
|
+
plan["instructions_instruction_text"] = snapshot["instruction_text"]
|
|
11262
11278
|
projected_args = project_args(plan, materialize_agents=not args.dry_run)
|
|
11263
11279
|
collisions = forwarded_collisions(projected_args, args.forward)
|
|
11264
11280
|
if collisions:
|
|
@@ -11271,8 +11287,8 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11271
11287
|
if learning is not None and args.dry_run:
|
|
11272
11288
|
projected += [understand_initial_prompt("<pinned-understand-session-prompt>")]
|
|
11273
11289
|
if snapshot is not None and args.dry_run:
|
|
11274
|
-
import
|
|
11275
|
-
projected =
|
|
11290
|
+
import instructions_session
|
|
11291
|
+
projected = instructions_session.compose_argv(
|
|
11276
11292
|
command,
|
|
11277
11293
|
projected,
|
|
11278
11294
|
args.host,
|
|
@@ -11281,17 +11297,17 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11281
11297
|
)
|
|
11282
11298
|
summary_stream = sys.stdout if tty or args.dry_run else sys.stderr
|
|
11283
11299
|
if snapshot is not None:
|
|
11284
|
-
print(f"
|
|
11285
|
-
if args.
|
|
11300
|
+
print(f" Instructions snapshot {snapshot['content_ref']} · private · next session only", file=summary_stream)
|
|
11301
|
+
if args.instructions_native:
|
|
11286
11302
|
plugins = snapshot.get("assets", {}).get("claude_plugins", [])
|
|
11287
11303
|
hooks = snapshot.get("assets", {}).get("codex_hooks", {})
|
|
11288
11304
|
if args.host == "codex":
|
|
11289
11305
|
count = sum(len(group["hooks"]) for groups in hooks.values() for group in groups)
|
|
11290
|
-
print(f" Native
|
|
11306
|
+
print(f" Native instructions opt-in: {count} session-only hook(s); Codex enablement and /hooks trust review apply.", file=summary_stream)
|
|
11291
11307
|
else:
|
|
11292
|
-
print(f" Native
|
|
11308
|
+
print(f" Native instructions opt-in: {len(plugins)} session-only plugin(s); selected hook code can execute.", file=summary_stream)
|
|
11293
11309
|
for unavailable in snapshot.get("unavailable", []):
|
|
11294
|
-
print(f"
|
|
11310
|
+
print(f" Instructions unavailable: {unavailable}", file=summary_stream)
|
|
11295
11311
|
if learning is not None:
|
|
11296
11312
|
print(f" Understand! {learning['title']} · {learning['source_ref']}", file=summary_stream)
|
|
11297
11313
|
print(" Learning Pinned bundle as reference material; native global files remain unchanged.", file=summary_stream)
|
|
@@ -11327,8 +11343,8 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
|
|
|
11327
11343
|
env["AGENT_BIOS_UNDERSTAND_SESSION"] = session["session_id"]
|
|
11328
11344
|
summary_stream.flush()
|
|
11329
11345
|
if snapshot is not None:
|
|
11330
|
-
import
|
|
11331
|
-
return
|
|
11346
|
+
import instructions_session
|
|
11347
|
+
return instructions_session.launch(command, projected, store.state_root, args.host,
|
|
11332
11348
|
snapshot, env=env,
|
|
11333
11349
|
include_global_instructions=plan["include_global_instructions"])
|
|
11334
11350
|
exec_backend(command, projected, env)
|