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.
Files changed (106) hide show
  1. package/DEPENDENCIES.md +58 -30
  2. package/INSTALL.md +4 -4
  3. package/README.md +105 -28
  4. package/claude/CLAUDE.md +1 -1
  5. package/claude/guides/claude-prompting.md +1 -1
  6. package/claude/guides/cli-multi-model-workflow.md +4 -4
  7. package/claude/guides/coding-staged-workflow.md +17 -0
  8. package/claude/guides/documentation-hygiene.md +3 -0
  9. package/claude/guides/gpt-prompting.md +1 -1
  10. package/claude/guides/korean-writing.md +153 -0
  11. package/claude/guides/learning-flow.md +4 -4
  12. package/claude/guides/llm-capability-boundary.md +7 -1
  13. package/claude/guides/session-distill-workflow.md +8 -8
  14. package/claude/guides/slide-writing/RUNBOOK.md +5 -5
  15. package/claude/guides/tooling-gotchas.md +20 -1
  16. package/claude/guides/ui-design/visual-direction.md +88 -0
  17. package/claude/guides/ui-design.md +90 -0
  18. package/claude/guides/verification-discipline.md +10 -1
  19. package/claude/hooks/tooling-gotchas-hook.py +41 -0
  20. package/claude/skills/repo-charter/SKILL.md +3 -3
  21. package/claude/skills/understand/SKILL.md +5 -5
  22. package/codex/AGENTS.md +1 -1
  23. package/codex/guides/claude-prompting.md +1 -1
  24. package/codex/guides/cli-multi-model-workflow.md +4 -4
  25. package/codex/guides/coding-staged-workflow.md +17 -0
  26. package/codex/guides/documentation-hygiene.md +3 -0
  27. package/codex/guides/gpt-prompting.md +1 -1
  28. package/codex/guides/korean-writing.md +153 -0
  29. package/codex/guides/learning-flow.md +4 -4
  30. package/codex/guides/llm-capability-boundary.md +7 -1
  31. package/codex/guides/session-distill-workflow.md +8 -8
  32. package/codex/guides/slide-writing/RUNBOOK.md +5 -5
  33. package/codex/guides/tooling-gotchas.md +20 -1
  34. package/codex/guides/ui-design/visual-direction.md +88 -0
  35. package/codex/guides/ui-design.md +90 -0
  36. package/codex/guides/verification-discipline.md +10 -1
  37. package/compose/app_bridge/SKILL.md +12 -12
  38. package/compose/app_bridge/scripts/bridge.py +35 -10
  39. package/compose/app_desktop/server.py +250 -0
  40. package/compose/assemble.py +5 -5
  41. package/compose/bootstrap/SKILL.md +18 -18
  42. package/compose/canary.sh +4 -4
  43. package/compose/check-domains.py +6 -6
  44. package/compose/corpus-state.py +16 -1168
  45. package/compose/corpus.py +13 -402
  46. package/compose/corpus_app.py +14 -450
  47. package/compose/corpus_catalog.py +15 -926
  48. package/compose/corpus_import.py +14 -523
  49. package/compose/corpus_install.py +14 -1847
  50. package/compose/corpus_session.py +16 -848
  51. package/compose/corpus_setup.py +16 -672
  52. package/compose/corpus_setup_cli.py +15 -580
  53. package/compose/corpus_setup_i18n.py +20 -324
  54. package/compose/corpus_setup_ui.py +18 -645
  55. package/compose/corpus_store.py +16 -1664
  56. package/compose/corpus_transaction.py +15 -284
  57. package/compose/corpus_ui.py +17 -972
  58. package/compose/corpus_ui_runtime.py +16 -274
  59. package/compose/corpus_understand.py +13 -671
  60. package/compose/domains.json +3 -1
  61. package/compose/host_platform.py +121 -0
  62. package/compose/instructions-state.py +1178 -0
  63. package/compose/instructions.py +409 -0
  64. package/compose/instructions_app.py +697 -0
  65. package/compose/instructions_catalog.py +931 -0
  66. package/compose/instructions_import.py +537 -0
  67. package/compose/instructions_install.py +1932 -0
  68. package/compose/instructions_session.py +852 -0
  69. package/compose/instructions_setup.py +713 -0
  70. package/compose/instructions_setup_cli.py +607 -0
  71. package/compose/instructions_setup_i18n.py +327 -0
  72. package/compose/instructions_setup_ui.py +647 -0
  73. package/compose/instructions_store.py +1668 -0
  74. package/compose/instructions_transaction.py +308 -0
  75. package/compose/instructions_ui.py +975 -0
  76. package/compose/instructions_ui_runtime.py +279 -0
  77. package/compose/instructions_understand.py +678 -0
  78. package/compose/native_cli.py +52 -0
  79. package/compose/register-hooks.py +1 -1
  80. package/compose/runtime_entry.py +58 -0
  81. package/compose/setup/START.md +11 -11
  82. package/compose/windows_deploy.py +719 -0
  83. package/docs/advanced-launch.md +11 -11
  84. package/docs/instructions-compatibility.md +86 -0
  85. package/docs/{corpus.md → instructions.md} +36 -8
  86. package/docs/recovery.md +10 -10
  87. package/docs/releases/0.19.2.md +38 -0
  88. package/docs/releases/0.19.3.md +107 -0
  89. package/docs/session-model.md +31 -20
  90. package/docs/setup.md +63 -26
  91. package/docs/understand.md +6 -6
  92. package/docs/windows.md +99 -0
  93. package/install.sh +71 -69
  94. package/launch/agent-launch.py +309 -293
  95. package/launch/agent-launch.toml +2 -2
  96. package/launch/agent-launch.zsh +11 -1
  97. package/launch/i18n/en.toml +55 -55
  98. package/launch/i18n/ja.toml +56 -56
  99. package/launch/i18n/ko.toml +56 -56
  100. package/launch/shell_integration.py +4 -4
  101. package/learn/collect-learning.py +10 -10
  102. package/learn/learning.schema.json +1 -1
  103. package/learn/migrate-learnings.py +51 -51
  104. package/package.json +33 -12
  105. package/provenance.json +1 -1
  106. /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
@@ -0,0 +1,607 @@
1
+ """JSON setup adapter with read-only reviews and durable, non-replaying receipts."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import contextlib
6
+ import hashlib
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import platform
11
+ import re
12
+ import shutil
13
+ import socket
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ from typing import Any, Callable
19
+
20
+ if __name__ == "__main__":
21
+ sys.dont_write_bytecode = True
22
+
23
+ try:
24
+ from host_platform import sync_directory, cli_argv, python_argv, runtime_environment
25
+ except ImportError:
26
+ from .host_platform import sync_directory, cli_argv, python_argv, runtime_environment
27
+
28
+ try:
29
+ from instructions_install import InstructionsInstaller
30
+ from instructions_setup import SetupController, SetupError, format_setup_result, review_summary
31
+ from instructions_setup_i18n import LANGUAGES, choice_label, dependency_display, detect_language, translate
32
+ from instructions_transaction import environment_value, confirmed_release, reject_symlink_ancestors, transaction_lock, try_transaction_lock
33
+ except ImportError:
34
+ from .instructions_install import InstructionsInstaller
35
+ from .instructions_setup import SetupController, SetupError, format_setup_result, review_summary
36
+ from .instructions_setup_i18n import LANGUAGES, choice_label, dependency_display, detect_language, translate
37
+ from .instructions_transaction import environment_value, confirmed_release, reject_symlink_ancestors, transaction_lock, try_transaction_lock
38
+
39
+
40
+ SCHEMA_VERSION = 1
41
+ IDENTIFIER = re.compile(r"[0-9a-f]{64}\Z")
42
+ LANGUAGE_IDS = {identifier for _label, identifier in LANGUAGES}
43
+ INCIDENTAL_ENV = {
44
+ **dict.fromkeys(("_", "SHLVL"), "shell command bookkeeping does not select setup effects"),
45
+ **dict.fromkeys(("PWD", "OLDPWD"), "the actual working directory is bound separately in the review context"),
46
+ **dict.fromkeys(("TERM", "COLORTERM", "TERM_PROGRAM", "TERM_PROGRAM_VERSION", "TERM_SESSION_ID", "SHELL_SESSION_ID",
47
+ "COLUMNS", "LINES", "NO_COLOR", "FORCE_COLOR"),
48
+ "terminal presentation and terminal-session identity are not installation authority"),
49
+ **dict.fromkeys(("LC_ALL", "LC_MESSAGES", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LANG", "LANGUAGE"),
50
+ "display locale may change between calls; the chosen review language is bound explicitly"),
51
+ **dict.fromkeys(("TMPDIR", "TMP", "TEMP"),
52
+ "scratch allocation may change between calls; package and private destinations are bound separately"),
53
+ **dict.fromkeys(("PYTHONDONTWRITEBYTECODE", "PYTHONUNBUFFERED"),
54
+ "Python cache and output policy do not select installation targets or recipes"),
55
+ **dict.fromkeys(("CODEX_THREAD_ID", "CODEX_TASK_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID"),
56
+ "setup does not activate a task and may continue from another task on the same machine and private roots"),
57
+ }
58
+
59
+
60
+ def _canonical(value: Any) -> bytes:
61
+ return json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
62
+
63
+
64
+ def _digest(value: Any) -> str:
65
+ return hashlib.sha256(_canonical(value)).hexdigest()
66
+
67
+
68
+ def _identity() -> dict[str, Any]:
69
+ hardware = ""
70
+ method = "host-filesystem"
71
+ if sys.platform.startswith("linux"):
72
+ for path in (Path("/etc/machine-id"), Path("/var/lib/dbus/machine-id")):
73
+ try:
74
+ candidate = path.read_text(encoding="ascii").strip()
75
+ except (OSError, UnicodeError):
76
+ continue
77
+ if re.fullmatch(r"[a-fA-F0-9]{32}", candidate):
78
+ hardware, method = candidate, "machine-id"
79
+ break
80
+ elif sys.platform == "darwin":
81
+ try:
82
+ result = subprocess.run(["/usr/sbin/ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
83
+ stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=5)
84
+ match = re.search(r'"IOPlatformUUID"\s*=\s*"([A-Fa-f0-9-]+)"', result.stdout)
85
+ if result.returncode == 0 and match:
86
+ hardware, method = match[1], "platform-uuid"
87
+ except (OSError, subprocess.SubprocessError):
88
+ pass
89
+ host = socket.gethostname()
90
+ if not hardware:
91
+ root = Path("/").stat()
92
+ hardware = f"{host}:{root.st_dev}:{root.st_ino}"
93
+ return {"machine_id": hashlib.sha256(hardware.encode()).hexdigest(), "identity_method": method,
94
+ "hostname": host, "platform": platform.system(), "architecture": platform.machine(),
95
+ "uid": os.getuid() if hasattr(os, "getuid") else None}
96
+
97
+
98
+ def _process_identity(pid: int) -> str | None:
99
+ if os.name == "nt":
100
+ import ctypes
101
+ from ctypes import wintypes
102
+ kernel = ctypes.WinDLL("kernel32", use_last_error=True)
103
+ kernel.OpenProcess.restype = wintypes.HANDLE
104
+ kernel.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
105
+ kernel.GetProcessTimes.argtypes = [wintypes.HANDLE] + [ctypes.POINTER(wintypes.FILETIME)] * 4
106
+ kernel.CloseHandle.argtypes = [wintypes.HANDLE]
107
+ handle = kernel.OpenProcess(0x1000, False, pid)
108
+ if not handle:
109
+ return None
110
+ try:
111
+ times = [wintypes.FILETIME() for _ in range(4)]
112
+ if not kernel.GetProcessTimes(handle, *(ctypes.byref(t) for t in times)):
113
+ return None
114
+ return str((times[0].dwHighDateTime << 32) | times[0].dwLowDateTime)
115
+ finally:
116
+ kernel.CloseHandle(handle)
117
+ if sys.platform.startswith("linux"):
118
+ try:
119
+ fields = (Path("/proc") / str(pid) / "stat").read_text().rsplit(")", 1)[1].split()
120
+ return fields[19]
121
+ except (OSError, IndexError):
122
+ return None
123
+ try:
124
+ result = subprocess.run(["/bin/ps", "-p", str(pid), "-o", "lstart="], stdin=subprocess.DEVNULL,
125
+ capture_output=True, text=True, timeout=3,
126
+ env={**os.environ, "LC_ALL": "C", "TZ": "UTC"})
127
+ return result.stdout.strip() if result.returncode == 0 and result.stdout.strip() else None
128
+ except (OSError, subprocess.SubprocessError):
129
+ return None
130
+
131
+
132
+ def _read_json(path: Path) -> dict[str, Any]:
133
+ reject_symlink_ancestors(path)
134
+ if not path.is_file() or path.stat().st_size > 8 * 1024 * 1024:
135
+ raise SetupError(f"missing, unsafe, or oversized setup JSON: {path}")
136
+ try:
137
+ value = json.loads(path.read_text(encoding="utf-8"))
138
+ except (OSError, ValueError) as exc:
139
+ raise SetupError(f"cannot read setup JSON: {path}: {exc}") from exc
140
+ if not isinstance(value, dict):
141
+ raise SetupError("setup JSON must be an object")
142
+ return value
143
+
144
+
145
+ def _write_json(path: Path, value: dict[str, Any]) -> None:
146
+ reject_symlink_ancestors(path)
147
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
148
+ descriptor, raw = tempfile.mkstemp(prefix=".setup-receipt-", dir=path.parent)
149
+ temporary = Path(raw)
150
+ try:
151
+ with os.fdopen(descriptor, "wb") as output:
152
+ output.write(_canonical(value) + b"\n")
153
+ output.flush()
154
+ os.fsync(output.fileno())
155
+ temporary.chmod(0o600)
156
+ os.replace(temporary, path)
157
+ sync_directory(path.parent)
158
+ finally:
159
+ temporary.unlink(missing_ok=True)
160
+
161
+
162
+ def _review_identity(review: dict[str, Any]) -> str:
163
+ return _digest({key: value for key, value in review.items() if key != "review_id"})
164
+
165
+
166
+ def _file_identity(path: Path) -> dict[str, Any]:
167
+ resolved = path.resolve()
168
+ if not resolved.is_file():
169
+ raise SetupError(f"reviewed dependency executable is unavailable: {path}")
170
+ digest = hashlib.sha256()
171
+ with resolved.open("rb") as source:
172
+ for chunk in iter(lambda: source.read(1024 * 1024), b""):
173
+ digest.update(chunk)
174
+ return {"path": str(resolved), "sha256": digest.hexdigest(), "size_bytes": resolved.stat().st_size}
175
+
176
+
177
+ class SetupService:
178
+ """Bind shared-engine reviews to a machine, then record every effect boundary."""
179
+
180
+ def __init__(self, installer: Any, *, controller_factory: Callable | None = None,
181
+ identity: Callable[[], dict[str, Any]] = _identity,
182
+ progress_hook: Callable[[dict[str, Any]], None] | None = None):
183
+ self.installer = installer
184
+ self.controller_factory = controller_factory or (lambda: SetupController(installer))
185
+ self.identity = identity
186
+ self.progress_hook = progress_hook
187
+
188
+ def context(self) -> dict[str, Any]:
189
+ env = self.installer.env
190
+ home = Path(env.get("HOME", str(Path.home()))).expanduser().resolve()
191
+ state = Path(getattr(self.installer, "state_root", env.get("AGENT_BIOS_STATE_DIR", home / ".local/share/agent-bios"))).expanduser().resolve()
192
+ user = Path(getattr(self.installer, "user_root", environment_value(env, "AGENT_BIOS_INSTRUCTIONS_DIR", "AGENT_BIOS_CORPUS_DIR", home / ".config/agent-bios/corpus"))).expanduser().resolve()
193
+ return {**self.identity(), "home": str(home), "state_dir": str(state), "user_dir": str(user),
194
+ "codex_dir": str(Path(env.get("CODEX_HOME", home / ".codex")).expanduser().resolve()),
195
+ "claude_dir": str(Path(env.get("CLAUDE_CONFIG_DIR", home / ".claude")).expanduser().resolve()),
196
+ "package_root": str(Path(self.installer.repo).resolve()), "cwd": str(Path.cwd().resolve()),
197
+ "python": {"executable": str(Path(sys.executable).resolve()), "version": platform.python_version()},
198
+ "environment_digest": _digest({key: value for key, value in env.items() if key not in INCIDENTAL_ENV})}
199
+
200
+ def _language(self, language: str | None) -> str:
201
+ value = language or detect_language(self.installer.env)
202
+ if value not in LANGUAGE_IDS:
203
+ raise SetupError("language must be en, ko, or ja")
204
+ return value
205
+
206
+ def _assert_owner(self, review: dict[str, Any]) -> None:
207
+ current = self.context()
208
+ if any(review["context"].get(key) != current.get(key) for key in ("machine_id", "uid", "home", "state_dir", "user_dir")):
209
+ raise SetupError("review receipt belongs to another machine or private root context")
210
+
211
+ def start(self, language: str | None = None) -> dict[str, Any]:
212
+ package = Path(self.installer.repo).resolve()
213
+ cli = cli_argv(package)
214
+ return {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-start",
215
+ "languages": [{"label": label, "id": identifier} for label, identifier in LANGUAGES],
216
+ "suggested_language": detect_language(self.installer.env), "language": self._language(language),
217
+ "context": self.context(), "guide_path": str(package / "compose/setup/START.md"),
218
+ "cli_argv": cli, "setup_argv": [*cli, "setup"]}
219
+
220
+ def inspect(self, language: str | None = None) -> dict[str, Any]:
221
+ language = self._language(language)
222
+ controller = self.controller_factory()
223
+ retained = getattr(controller, "retained_instructions", [])
224
+ return {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-inspection", "context": self.context(),
225
+ "language": language, "dependencies": controller.dependencies, "choices": controller.choices,
226
+ "retained_corpus": retained,
227
+ "default_plan": controller.default_plan(),
228
+ "display": {"dependencies": [dependency_display(language, row) for row in controller.dependencies],
229
+ "retained_corpus": [{**row, "label": translate(language, row["label"])} for row in retained],
230
+ "choices": [{**row, "label": choice_label(language, row)} for row in controller.choices]}}
231
+
232
+ def discover(self, roots: list[str]) -> dict[str, Any]:
233
+ if not isinstance(roots, list) or not all(isinstance(root, str) and Path(root).is_absolute() and Path(root).is_dir() for root in roots):
234
+ raise SetupError("project roots must be existing absolute directories")
235
+ found = self.installer.setup_discover(roots)
236
+ return {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-discovery", "context": self.context(),
237
+ "sources": found["sources"], "omitted": found.get("omitted", [])}
238
+
239
+ def _actions(self, preview: dict[str, Any]) -> list[dict[str, Any]]:
240
+ identities = []
241
+ for action in preview["plan"]["dependency_actions"]:
242
+ command = action["argv"][0]
243
+ path = shutil.which(command, path=self.installer.env.get("PATH", os.defpath))
244
+ if not path:
245
+ raise SetupError(f"reviewed dependency executable is unavailable: {command}")
246
+ identities.append({"id": action["id"], **_file_identity(Path(path))})
247
+ return identities
248
+
249
+ def plan(self, value: dict[str, Any], language: str | None = None, *, continuation: dict[str, Any] | None = None) -> dict[str, Any]:
250
+ language = self._language(language)
251
+ controller = self.controller_factory()
252
+ if not isinstance(value, dict) or set(value) != controller.FIELDS:
253
+ raise SetupError("plan input must contain exactly the six setup choice fields; derived commands are not input")
254
+ preview = controller.preview(value)
255
+ review = {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-review", "context": self.context(),
256
+ "language": language, "preview": preview, "action_inputs": self._actions(preview),
257
+ "summary": review_summary(preview["plan"], controller.dependencies, controller.choices, language)}
258
+ if continuation is not None:
259
+ review["continuation"] = continuation
260
+ review["review_id"] = _review_identity(review)
261
+ return review
262
+
263
+ def _receipt_path(self, identifier: str) -> Path:
264
+ if not isinstance(identifier, str) or not IDENTIFIER.fullmatch(identifier):
265
+ raise SetupError("review id must be an engine-issued 64-character identifier")
266
+ return Path(self.context()["state_dir"]) / "setup/receipts" / f"{identifier}.json"
267
+
268
+ def _receipt(self, identifier: str) -> dict[str, Any] | None:
269
+ path = self._receipt_path(identifier)
270
+ reject_symlink_ancestors(path)
271
+ if not path.exists():
272
+ return None
273
+ receipt = _read_json(path)
274
+ fingerprint = _digest({key: value for key, value in receipt.items() if key != "receipt_revision"})
275
+ if receipt.get("schema_version") != SCHEMA_VERSION or receipt.get("review_id") != identifier \
276
+ or receipt.get("receipt_revision") != fingerprint or not isinstance(receipt.get("operations"), dict) \
277
+ or receipt.get("state") not in {"running", "complete", "partial", "unknown"}:
278
+ raise SetupError("setup receipt integrity check failed")
279
+ self._validate_envelope(receipt.get("review"), identifier)
280
+ return receipt
281
+
282
+ def _save(self, receipt: dict[str, Any]) -> None:
283
+ receipt["updated_at"] = time.time()
284
+ receipt["receipt_revision"] = _digest({key: value for key, value in receipt.items() if key != "receipt_revision"})
285
+ _write_json(self._receipt_path(receipt["review_id"]), receipt)
286
+
287
+ @staticmethod
288
+ def _validate_envelope(review: Any, expected: str) -> None:
289
+ fields = {"schema_version", "kind", "context", "language", "preview", "action_inputs", "summary", "review_id"}
290
+ if not isinstance(review, dict) or set(review) not in (fields, fields | {"continuation"}) \
291
+ or review.get("schema_version") != SCHEMA_VERSION or review.get("kind") != "agent-bios-setup-review":
292
+ raise SetupError("apply requires the entire engine-issued review envelope")
293
+ if not isinstance(review["context"], dict) or not isinstance(review["preview"], dict) \
294
+ or not isinstance(review["language"], str) or review["language"] not in LANGUAGE_IDS \
295
+ or not isinstance(review["summary"], str) or not isinstance(review["action_inputs"], list):
296
+ raise SetupError("review envelope fields have invalid types")
297
+ if not isinstance(expected, str) or not IDENTIFIER.fullmatch(expected) or review.get("review_id") != expected \
298
+ or _review_identity(review) != expected:
299
+ raise SetupError("review identity does not match the exact reviewed envelope")
300
+
301
+ def _current_review(self, review: dict[str, Any]) -> dict[str, Any]:
302
+ if "continuation" in review:
303
+ continuation = review["continuation"]
304
+ if not isinstance(continuation, dict) or set(continuation) != {"review_id", "receipt_revision", "reuse_installation"}:
305
+ raise SetupError("invalid setup continuation")
306
+ resumed = self.resume(continuation["review_id"], review["language"])
307
+ if resumed.get("review") is None:
308
+ raise SetupError("setup continuation requires inspection of prior effects")
309
+ return resumed["review"]
310
+ value = review.get("preview", {}).get("plan", {})
311
+ plan = {key: value[key] for key in SetupController.FIELDS if key in value}
312
+ return self.plan(plan, review["language"])
313
+
314
+ def handoff(self) -> dict[str, Any]:
315
+ context = self.context()
316
+ result = {"verification": "deferred", "runtime_verified": None, "package_verified": None, "package_root": None, "release_digest": None, "cli_argv": None, "setup_argv": None,
317
+ "guide_path": None, "helper_registered": None, "helper_verified": None, "helper_argv": None,
318
+ "helper_usable": None,
319
+ "native_discovery": "unverified", "task_activation": {"performed": False, "existing_tasks": "unchanged", "default": "not_requested"},
320
+ "environment": {"HOME": context["home"], "AGENT_BIOS_STATE_DIR": context["state_dir"],
321
+ "AGENT_BIOS_INSTRUCTIONS_DIR": context["user_dir"], "CODEX_HOME": context["codex_dir"],
322
+ "AGENT_BIOS_CORPUS_DIR": context["user_dir"],
323
+ "CLAUDE_CONFIG_DIR": context["claude_dir"]}, "needs_action": []}
324
+ result["environment"].update(runtime_environment())
325
+ try:
326
+ with try_transaction_lock(Path(context["state_dir"])) as acquired:
327
+ if not acquired:
328
+ result["needs_action"].append("private state verification is deferred: another operation holds the lock or synchronization is unavailable; retry setup status")
329
+ return result
330
+ result.update(verification="checked", runtime_verified=False, package_verified=False,
331
+ helper_registered=False, helper_verified=False, helper_usable=False)
332
+ return self._verified_handoff(context, result)
333
+ except (OSError, RuntimeError, ValueError) as exc:
334
+ result["needs_action"].append(str(exc))
335
+ result["environment"].update(runtime_environment())
336
+ return result
337
+
338
+ def _verified_handoff(self, context: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
339
+ record = Path(context["state_dir"]) / "runtime/private-install.json"
340
+ pending = False
341
+ if record.is_file():
342
+ try:
343
+ reject_symlink_ancestors(record)
344
+ release = confirmed_release(Path(context["state_dir"]))
345
+ result.update(package_verified=True, package_root=str(release), release_digest=release.name,
346
+ cli_argv=cli_argv(release),
347
+ setup_argv=cli_argv(release, "setup"), guide_path=str(release / "compose/setup/START.md"))
348
+ result["environment"].update(AGENT_BIOS_PACKAGE_ROOT=str(release), AGENT_BIOS_PRIVATE_INSTRUCTIONS="1", AGENT_BIOS_PRIVATE_CORPUS="1", AGENT_BIOS_LEGACY_INSTALL="0")
349
+ except (OSError, RuntimeError, ValueError) as exc:
350
+ result["needs_action"].append(str(exc))
351
+ try:
352
+ verification = self.installer.verify()
353
+ result["runtime_verified"] = verification.get("stored") is True
354
+ if not result["runtime_verified"]:
355
+ result["needs_action"].append("private runtime verification did not confirm installation")
356
+ except (OSError, RuntimeError, ValueError) as exc:
357
+ result["needs_action"].append(str(exc))
358
+ try:
359
+ pending = bool(self.installer.status().get("transactions"))
360
+ if pending:
361
+ result["needs_action"].append("private installation has a pending transaction")
362
+ except (OSError, RuntimeError, ValueError) as exc:
363
+ pending = True
364
+ result["needs_action"].append(str(exc))
365
+ try:
366
+ manager = self.installer._app_manager()
367
+ bridge = manager.managed_status()
368
+ result["helper_registered"] = bool(bridge.get("registered"))
369
+ result["needs_action"].extend(bridge.get("needs_action", []))
370
+ if bridge.get("registered") and not bridge.get("needs_action"):
371
+ helper = manager.target / "scripts/bridge.py"
372
+ result.update(helper_verified=True, helper_argv=python_argv(helper))
373
+ if "AGENT_LAUNCH_VENV" in self.installer.env:
374
+ result["environment"]["AGENT_LAUNCH_VENV"] = self.installer.env["AGENT_LAUNCH_VENV"]
375
+ except (OSError, RuntimeError, ValueError) as exc:
376
+ result["needs_action"].append(str(exc))
377
+ result["helper_usable"] = result["helper_verified"] and result["package_verified"] and not pending
378
+ return result
379
+
380
+ def status(self, identifier: str, language: str | None = None, *, replayed: bool = False) -> dict[str, Any]:
381
+ receipt = self._receipt(identifier)
382
+ result = receipt.get("result") or {} if receipt else {}
383
+ language = self._language(language or (receipt or {}).get("review", {}).get("language"))
384
+ state = receipt["state"] if receipt else "missing"
385
+ if state == "running":
386
+ owner = receipt.get("owner", {})
387
+ token = _process_identity(owner.get("pid", -1)) if isinstance(owner.get("pid"), int) else None
388
+ same_machine = receipt["review"]["context"].get("machine_id") == self.context()["machine_id"]
389
+ if not same_machine or token is None or token != owner.get("process_identity"):
390
+ state = "unknown"
391
+ return {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-status", "review_id": identifier,
392
+ "state": state, "receipt_path": str(self._receipt_path(identifier)), "receipt": receipt,
393
+ "result": result, "summary": format_setup_result(result, language, interface="conversation"), "handoff": self.handoff(),
394
+ "replayed": replayed, "context": self.context(), "language": language}
395
+
396
+ def resume(self, identifier: str, language: str | None = None) -> dict[str, Any]:
397
+ response = self.status(identifier, language)
398
+ response.update(remaining_plan=None, review=None, needs_action=[])
399
+ receipt = response["receipt"]
400
+ if receipt is None:
401
+ response["needs_action"] = ["setup receipt was not found"]
402
+ return response
403
+ self._assert_owner(receipt["review"])
404
+ chain = []
405
+ while receipt.get("continued_by"):
406
+ if receipt["review_id"] in chain or len(chain) >= 32:
407
+ raise SetupError("setup continuation chain is invalid")
408
+ chain.append(receipt["review_id"])
409
+ child = self._receipt(receipt["continued_by"])
410
+ if child is None or child["review"].get("continuation", {}).get("review_id") != receipt["review_id"]:
411
+ raise SetupError("setup continuation receipt is missing or inconsistent")
412
+ self._assert_owner(child["review"])
413
+ receipt = child
414
+ response = self.status(receipt["review_id"], language)
415
+ response.update(remaining_plan=None, review=None, needs_action=[], resumed_from=identifier)
416
+ if receipt["state"] == "complete":
417
+ return response
418
+ if receipt["state"] == "running":
419
+ response["needs_action"] = ["setup is running or its interruption is unconfirmed; inspect its recorded progress before retrying"]
420
+ return response
421
+ unsafe = [name for name, operation in receipt.get("operations", {}).items()
422
+ if operation.get("state") in {"running", "failed", "unknown"}]
423
+ if unsafe or receipt["state"] == "unknown":
424
+ response["needs_action"] = ["inspect effects before retrying: " + ", ".join(unsafe or ["unknown operation"])]
425
+ response["inspection"] = self.inspect(response["language"])
426
+ return response
427
+ original = receipt["review"]["preview"]["plan"]
428
+ plan = {key: original[key] for key in SetupController.FIELDS}
429
+ completed = {name for name, operation in receipt.get("operations", {}).items() if operation.get("state") == "completed"}
430
+ controller = self.controller_factory()
431
+ inventory = {row["id"]: row for row in controller.dependencies}
432
+ remaining = []
433
+ for name in plan["dependencies"]:
434
+ row = inventory.get(name)
435
+ if row is None:
436
+ response["needs_action"].append(f"requested dependency is absent from the current inventory: {name}")
437
+ elif "dependency:" + name in completed:
438
+ if row.get("status") != "available":
439
+ response["needs_action"].append(f"completed dependency is not currently available: {name}")
440
+ elif row.get("status") == "available":
441
+ continue
442
+ elif not row.get("install_argv"):
443
+ response["needs_action"].append(f"requested dependency is still missing and has no current installation recipe: {name}")
444
+ else:
445
+ remaining.append(name)
446
+ response["inspection"] = {"dependencies": controller.dependencies, "choices": controller.choices}
447
+ if response["needs_action"]:
448
+ return response
449
+ plan["dependencies"] = remaining
450
+ reuse = "install" in completed
451
+ if reuse:
452
+ if not response["handoff"]["runtime_verified"] or response["handoff"]["needs_action"]:
453
+ response["needs_action"] = ["verify the completed private installation before continuing"]
454
+ return response
455
+ plan["selection_mode"], plan["targets"] = None, None
456
+ if "extras" in completed:
457
+ plan["app_bridge"], plan["import_paths"], plan["project_roots"] = False, [], []
458
+ continuation = {"review_id": receipt["review_id"], "receipt_revision": receipt["receipt_revision"], "reuse_installation": reuse}
459
+ response["remaining_plan"] = plan
460
+ candidate = self.plan(plan, response["language"], continuation=continuation)
461
+ if reuse:
462
+ completed_install = receipt["operations"]["install"]["event"].get("installation", {})
463
+ completed_digest = completed_install.get("record", {}).get("release_digest") or completed_install.get("release_digest")
464
+ source_digest = candidate["preview"]["installation"].get("release_digest")
465
+ if source_digest != response["handoff"].get("release_digest") or completed_digest != source_digest:
466
+ response["needs_action"] = ["source or installed package changed after partial setup; create a fresh installation plan"]
467
+ return response
468
+ response["review"] = candidate
469
+ return response
470
+
471
+ def apply(self, review: dict[str, Any], expected_review_id: str, *, yes: bool = False) -> dict[str, Any]:
472
+ if yes is not True:
473
+ raise SetupError("apply requires explicit --yes acknowledgment of the exact reviewed plan")
474
+ review = json.loads(json.dumps(review, ensure_ascii=False))
475
+ self._validate_envelope(review, expected_review_id)
476
+ existing = self._receipt(expected_review_id)
477
+ if existing is not None:
478
+ if existing["review"] != review:
479
+ raise SetupError("review differs from the owned receipt")
480
+ self._assert_owner(review)
481
+ return self.status(expected_review_id, replayed=True)
482
+ if self._current_review(review) != review:
483
+ raise SetupError("review is stale or belongs to another execution context; create a fresh plan")
484
+ state_root = Path(self.context()["state_dir"])
485
+ with transaction_lock(state_root):
486
+ if self._receipt(expected_review_id) is not None:
487
+ return self.status(expected_review_id, replayed=True)
488
+ if self._current_review(review) != review:
489
+ raise SetupError("review changed while waiting for the setup lock; create a fresh plan")
490
+ receipt = {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-receipt", "review_id": expected_review_id,
491
+ "review": review, "state": "running", "created_at": time.time(), "operations": {}, "progress": [], "result": None,
492
+ "owner": {"pid": os.getpid(), "process_identity": _process_identity(os.getpid())}}
493
+ self._save(receipt)
494
+ if review.get("continuation"):
495
+ parent = self._receipt(review["continuation"]["review_id"])
496
+ if parent is None or parent["receipt_revision"] != review["continuation"]["receipt_revision"] or parent.get("continued_by"):
497
+ raise SetupError("setup continuation was consumed or changed before execution")
498
+ parent["continued_by"] = expected_review_id
499
+ self._save(parent)
500
+ def progress(event: dict[str, Any]) -> None:
501
+ stage, phase = event.get("stage"), event.get("phase")
502
+ key = "dependency:" + str(event.get("dependency", event.get("id", ""))) if stage == "dependency" else stage
503
+ if phase == "started" and stage == "dependency":
504
+ actual = self._actions({"plan": {"dependency_actions": [action for action in review["preview"]["plan"]["dependency_actions"] if action["id"] == event["dependency"]]}})
505
+ expected = [row for row in review["action_inputs"] if row["id"] == event["dependency"]]
506
+ if actual != expected:
507
+ raise SetupError("dependency executable changed after review")
508
+ if stage in {"dependency", "install", "extras"}:
509
+ receipt["operations"][key] = {"state": {"started": "running", "completed": "completed", "failed": "failed", "not_started": "not_started"}.get(phase, "not_started"),
510
+ "event": event, "at": time.time()}
511
+ receipt["progress"].append(event)
512
+ self._save(receipt)
513
+ if self.progress_hook:
514
+ self.progress_hook(event)
515
+ try:
516
+ controller = self.controller_factory()
517
+ plan = {key: review["preview"]["plan"][key] for key in SetupController.FIELDS}
518
+ result = controller.apply(plan, preview=review["preview"], progress=progress,
519
+ reuse_installation=bool(review.get("continuation", {}).get("reuse_installation")))
520
+ receipt["result"] = result
521
+ active = any(operation["state"] == "running" for operation in receipt["operations"].values())
522
+ receipt["state"] = "complete" if result.get("applied") else "unknown" if active else "partial"
523
+ except BaseException as exc:
524
+ active = any(operation["state"] == "running" for operation in receipt["operations"].values())
525
+ receipt["state"] = "unknown" if active else "partial"
526
+ receipt["error"] = {"type": type(exc).__name__, "message": str(exc)}
527
+ receipt["result"] = {"applied": False, "installation_error": str(exc) or type(exc).__name__,
528
+ "dependency_results": [operation["event"] for key, operation in receipt["operations"].items() if key.startswith("dependency:") and "returncode" in operation["event"]]}
529
+ self._save(receipt)
530
+ return self.status(expected_review_id)
531
+
532
+
533
+ class _Parser(argparse.ArgumentParser):
534
+ def error(self, message):
535
+ raise SetupError(message)
536
+
537
+
538
+ def _input(source: str) -> dict[str, Any]:
539
+ if source != "-":
540
+ return _read_json(Path(source).expanduser())
541
+ if sys.stdin.isatty():
542
+ raise SetupError("--input - requires piped JSON; use a review file in a terminal")
543
+ body = sys.stdin.read(8 * 1024 * 1024 + 1)
544
+ if len(body) > 8 * 1024 * 1024:
545
+ raise SetupError("setup input is too large")
546
+ value = json.loads(body)
547
+ if not isinstance(value, dict):
548
+ raise SetupError("setup input must be a JSON object")
549
+ return value
550
+
551
+
552
+ def main(argv: list[str] | None = None) -> int:
553
+ args = None
554
+ presentation_language = None
555
+ try:
556
+ parser = _Parser(prog="agent-bios setup", add_help=False)
557
+ parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[1])
558
+ parser.add_argument("--help", action="store_true")
559
+ sub = parser.add_subparsers(dest="command")
560
+ for name in ("start", "inspect"):
561
+ command = sub.add_parser(name, add_help=False)
562
+ command.add_argument("--language", choices=sorted(LANGUAGE_IDS))
563
+ discover = sub.add_parser("discover", add_help=False)
564
+ discover.add_argument("--project-root", action="append", default=[])
565
+ plan = sub.add_parser("plan", add_help=False)
566
+ plan.add_argument("--input", required=True)
567
+ plan.add_argument("--language", choices=sorted(LANGUAGE_IDS))
568
+ apply = sub.add_parser("apply", add_help=False)
569
+ apply.add_argument("--input", required=True)
570
+ apply.add_argument("--review-id", required=True)
571
+ apply.add_argument("--yes", action="store_true")
572
+ for name in ("status", "resume"):
573
+ command = sub.add_parser(name, add_help=False)
574
+ command.add_argument("--review-id", required=True)
575
+ command.add_argument("--language", choices=sorted(LANGUAGE_IDS))
576
+ raw = list(sys.argv[1:] if argv is None else argv)
577
+ args = parser.parse_args([value for value in raw if value != "--json"])
578
+ if args.help:
579
+ result = {"schema_version": SCHEMA_VERSION, "commands": ["start", "inspect", "discover", "plan", "apply", "status", "resume"], "format": "JSON"}
580
+ else:
581
+ service = SetupService(InstructionsInstaller(args.repo))
582
+ command = args.command or "start"
583
+ with contextlib.redirect_stdout(sys.stderr):
584
+ if command == "start": result = service.start(getattr(args, "language", None))
585
+ elif command == "inspect": result = service.inspect(args.language)
586
+ elif command == "discover": result = service.discover(args.project_root)
587
+ elif command == "plan": result = service.plan(_input(args.input), args.language)
588
+ elif command == "apply":
589
+ envelope = _input(args.input)
590
+ selected_language = envelope.get("language")
591
+ if isinstance(selected_language, str) and selected_language in LANGUAGE_IDS:
592
+ presentation_language = selected_language
593
+ result = service.apply(envelope, args.review_id, yes=args.yes)
594
+ elif command == "status": result = service.status(args.review_id, args.language)
595
+ else: result = service.resume(args.review_id, args.language)
596
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
597
+ return 1 if (args.command == "apply" and result.get("state") != "complete") else 0
598
+ except (OSError, RuntimeError, ValueError, TypeError, KeyError) as exc:
599
+ language = presentation_language or getattr(args, "language", None) or detect_language(os.environ)
600
+ print(json.dumps({"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-error", "ok": False,
601
+ "error": {"type": type(exc).__name__, "message": str(exc)},
602
+ "summary": translate(language, "Setup could not finish. Details: {detail}", detail=str(exc))}, ensure_ascii=False))
603
+ return 2
604
+
605
+
606
+ if __name__ == "__main__":
607
+ raise SystemExit(main())