agent-bios 0.18.0 → 0.19.1

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