agent-bios 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/DEPENDENCIES.md +27 -27
  2. package/INSTALL.md +4 -4
  3. package/README.md +58 -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 +3 -3
  7. package/claude/guides/documentation-hygiene.md +3 -0
  8. package/claude/guides/gpt-prompting.md +1 -1
  9. package/claude/guides/korean-writing.md +153 -0
  10. package/claude/guides/learning-flow.md +4 -4
  11. package/claude/guides/session-distill-workflow.md +8 -8
  12. package/claude/guides/slide-writing/RUNBOOK.md +5 -5
  13. package/claude/skills/repo-charter/SKILL.md +3 -3
  14. package/claude/skills/understand/SKILL.md +58 -28
  15. package/codex/AGENTS.md +1 -1
  16. package/codex/guides/claude-prompting.md +1 -1
  17. package/codex/guides/cli-multi-model-workflow.md +3 -3
  18. package/codex/guides/documentation-hygiene.md +3 -0
  19. package/codex/guides/gpt-prompting.md +1 -1
  20. package/codex/guides/korean-writing.md +153 -0
  21. package/codex/guides/learning-flow.md +4 -4
  22. package/codex/guides/session-distill-workflow.md +8 -8
  23. package/codex/guides/slide-writing/RUNBOOK.md +5 -5
  24. package/compose/app_bridge/SKILL.md +12 -12
  25. package/compose/app_bridge/scripts/bridge.py +15 -7
  26. package/compose/assemble.py +5 -5
  27. package/compose/bootstrap/SKILL.md +18 -18
  28. package/compose/canary.sh +4 -4
  29. package/compose/check-domains.py +6 -6
  30. package/compose/corpus-state.py +16 -1168
  31. package/compose/corpus.py +13 -402
  32. package/compose/corpus_app.py +14 -450
  33. package/compose/corpus_catalog.py +15 -926
  34. package/compose/corpus_import.py +14 -523
  35. package/compose/corpus_install.py +14 -1841
  36. package/compose/corpus_session.py +16 -848
  37. package/compose/corpus_setup.py +16 -670
  38. package/compose/corpus_setup_cli.py +15 -577
  39. package/compose/corpus_setup_i18n.py +20 -318
  40. package/compose/corpus_setup_ui.py +18 -631
  41. package/compose/corpus_store.py +16 -1621
  42. package/compose/corpus_transaction.py +15 -284
  43. package/compose/corpus_ui.py +17 -972
  44. package/compose/corpus_ui_runtime.py +16 -274
  45. package/compose/corpus_understand.py +13 -520
  46. package/compose/domains.json +2 -1
  47. package/compose/instructions-state.py +1175 -0
  48. package/compose/instructions.py +409 -0
  49. package/compose/instructions_app.py +464 -0
  50. package/compose/instructions_catalog.py +931 -0
  51. package/compose/instructions_import.py +529 -0
  52. package/compose/instructions_install.py +1866 -0
  53. package/compose/instructions_session.py +852 -0
  54. package/compose/instructions_setup.py +676 -0
  55. package/compose/instructions_setup_cli.py +586 -0
  56. package/compose/instructions_setup_i18n.py +324 -0
  57. package/compose/instructions_setup_ui.py +647 -0
  58. package/compose/instructions_store.py +1668 -0
  59. package/compose/instructions_transaction.py +306 -0
  60. package/compose/instructions_ui.py +975 -0
  61. package/compose/instructions_ui_runtime.py +278 -0
  62. package/compose/instructions_understand.py +678 -0
  63. package/compose/register-hooks.py +1 -1
  64. package/compose/setup/START.md +24 -13
  65. package/docs/advanced-launch.md +11 -11
  66. package/docs/instructions-compatibility.md +86 -0
  67. package/docs/{corpus.md → instructions.md} +35 -8
  68. package/docs/recovery.md +8 -8
  69. package/docs/releases/0.19.2.md +38 -0
  70. package/docs/session-model.md +23 -20
  71. package/docs/setup.md +42 -25
  72. package/docs/understand.md +57 -9
  73. package/install.sh +70 -69
  74. package/launch/agent-launch.py +306 -295
  75. package/launch/agent-launch.toml +2 -2
  76. package/launch/i18n/en.toml +55 -55
  77. package/launch/i18n/ja.toml +56 -56
  78. package/launch/i18n/ko.toml +56 -56
  79. package/launch/shell_integration.py +4 -4
  80. package/learn/collect-learning.py +10 -10
  81. package/learn/learning.schema.json +1 -1
  82. package/learn/migrate-learnings.py +51 -51
  83. package/package.json +27 -11
  84. package/provenance.json +1 -1
  85. /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
@@ -0,0 +1,586 @@
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 instructions_install import InstructionsInstaller
25
+ from instructions_setup import SetupController, SetupError, format_setup_result, review_summary
26
+ from instructions_setup_i18n import LANGUAGES, choice_label, dependency_display, detect_language, translate
27
+ from instructions_transaction import environment_value, confirmed_release, reject_symlink_ancestors, transaction_lock, try_transaction_lock
28
+ except ImportError:
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
+
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", environment_value(env, "AGENT_BIOS_INSTRUCTIONS_DIR", "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_instructions", [])
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_INSTRUCTIONS_DIR": context["user_dir"], "CODEX_HOME": context["codex_dir"],
303
+ "AGENT_BIOS_CORPUS_DIR": context["user_dir"],
304
+ "CLAUDE_CONFIG_DIR": context["claude_dir"]}, "needs_action": []}
305
+ try:
306
+ with try_transaction_lock(Path(context["state_dir"])) as acquired:
307
+ if not acquired:
308
+ result["needs_action"].append("private state verification is deferred: another operation holds the lock or synchronization is unavailable; retry setup status")
309
+ return result
310
+ result.update(verification="checked", runtime_verified=False, package_verified=False,
311
+ helper_registered=False, helper_verified=False, helper_usable=False)
312
+ return self._verified_handoff(context, result)
313
+ except (OSError, RuntimeError, ValueError) as exc:
314
+ result["needs_action"].append(str(exc))
315
+ return result
316
+
317
+ def _verified_handoff(self, context: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
318
+ record = Path(context["state_dir"]) / "runtime/private-install.json"
319
+ pending = False
320
+ if record.is_file():
321
+ try:
322
+ reject_symlink_ancestors(record)
323
+ release = confirmed_release(Path(context["state_dir"]))
324
+ result.update(package_verified=True, package_root=str(release), release_digest=release.name,
325
+ cli_argv=["/bin/bash", str(release / "install.sh")],
326
+ setup_argv=["/bin/bash", str(release / "install.sh"), "setup"], guide_path=str(release / "compose/setup/START.md"))
327
+ result["environment"].update(AGENT_BIOS_PACKAGE_ROOT=str(release), AGENT_BIOS_PRIVATE_INSTRUCTIONS="1", AGENT_BIOS_PRIVATE_CORPUS="1", AGENT_BIOS_LEGACY_INSTALL="0")
328
+ except (OSError, RuntimeError, ValueError) as exc:
329
+ result["needs_action"].append(str(exc))
330
+ try:
331
+ verification = self.installer.verify()
332
+ result["runtime_verified"] = verification.get("stored") is True
333
+ if not result["runtime_verified"]:
334
+ result["needs_action"].append("private runtime verification did not confirm installation")
335
+ except (OSError, RuntimeError, ValueError) as exc:
336
+ result["needs_action"].append(str(exc))
337
+ try:
338
+ pending = bool(self.installer.status().get("transactions"))
339
+ if pending:
340
+ result["needs_action"].append("private installation has a pending transaction")
341
+ except (OSError, RuntimeError, ValueError) as exc:
342
+ pending = True
343
+ result["needs_action"].append(str(exc))
344
+ try:
345
+ manager = self.installer._app_manager()
346
+ bridge = manager.managed_status()
347
+ result["helper_registered"] = bool(bridge.get("registered"))
348
+ result["needs_action"].extend(bridge.get("needs_action", []))
349
+ if bridge.get("registered") and not bridge.get("needs_action"):
350
+ helper = manager.target / "scripts/bridge.py"
351
+ result.update(helper_verified=True, helper_argv=[sys.executable, str(helper)])
352
+ if "AGENT_LAUNCH_VENV" in self.installer.env:
353
+ result["environment"]["AGENT_LAUNCH_VENV"] = self.installer.env["AGENT_LAUNCH_VENV"]
354
+ except (OSError, RuntimeError, ValueError) as exc:
355
+ result["needs_action"].append(str(exc))
356
+ result["helper_usable"] = result["helper_verified"] and result["package_verified"] and not pending
357
+ return result
358
+
359
+ def status(self, identifier: str, language: str | None = None, *, replayed: bool = False) -> dict[str, Any]:
360
+ receipt = self._receipt(identifier)
361
+ result = receipt.get("result") or {} if receipt else {}
362
+ language = self._language(language or (receipt or {}).get("review", {}).get("language"))
363
+ state = receipt["state"] if receipt else "missing"
364
+ if state == "running":
365
+ owner = receipt.get("owner", {})
366
+ token = _process_identity(owner.get("pid", -1)) if isinstance(owner.get("pid"), int) else None
367
+ same_machine = receipt["review"]["context"].get("machine_id") == self.context()["machine_id"]
368
+ if not same_machine or token is None or token != owner.get("process_identity"):
369
+ state = "unknown"
370
+ return {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-status", "review_id": identifier,
371
+ "state": state, "receipt_path": str(self._receipt_path(identifier)), "receipt": receipt,
372
+ "result": result, "summary": format_setup_result(result, language, interface="conversation"), "handoff": self.handoff(),
373
+ "replayed": replayed, "context": self.context(), "language": language}
374
+
375
+ def resume(self, identifier: str, language: str | None = None) -> dict[str, Any]:
376
+ response = self.status(identifier, language)
377
+ response.update(remaining_plan=None, review=None, needs_action=[])
378
+ receipt = response["receipt"]
379
+ if receipt is None:
380
+ response["needs_action"] = ["setup receipt was not found"]
381
+ return response
382
+ self._assert_owner(receipt["review"])
383
+ chain = []
384
+ while receipt.get("continued_by"):
385
+ if receipt["review_id"] in chain or len(chain) >= 32:
386
+ raise SetupError("setup continuation chain is invalid")
387
+ chain.append(receipt["review_id"])
388
+ child = self._receipt(receipt["continued_by"])
389
+ if child is None or child["review"].get("continuation", {}).get("review_id") != receipt["review_id"]:
390
+ raise SetupError("setup continuation receipt is missing or inconsistent")
391
+ self._assert_owner(child["review"])
392
+ receipt = child
393
+ response = self.status(receipt["review_id"], language)
394
+ response.update(remaining_plan=None, review=None, needs_action=[], resumed_from=identifier)
395
+ if receipt["state"] == "complete":
396
+ return response
397
+ if receipt["state"] == "running":
398
+ response["needs_action"] = ["setup is running or its interruption is unconfirmed; inspect its recorded progress before retrying"]
399
+ return response
400
+ unsafe = [name for name, operation in receipt.get("operations", {}).items()
401
+ if operation.get("state") in {"running", "failed", "unknown"}]
402
+ if unsafe or receipt["state"] == "unknown":
403
+ response["needs_action"] = ["inspect effects before retrying: " + ", ".join(unsafe or ["unknown operation"])]
404
+ response["inspection"] = self.inspect(response["language"])
405
+ return response
406
+ original = receipt["review"]["preview"]["plan"]
407
+ plan = {key: original[key] for key in SetupController.FIELDS}
408
+ completed = {name for name, operation in receipt.get("operations", {}).items() if operation.get("state") == "completed"}
409
+ controller = self.controller_factory()
410
+ inventory = {row["id"]: row for row in controller.dependencies}
411
+ remaining = []
412
+ for name in plan["dependencies"]:
413
+ row = inventory.get(name)
414
+ if row is None:
415
+ response["needs_action"].append(f"requested dependency is absent from the current inventory: {name}")
416
+ elif "dependency:" + name in completed:
417
+ if row.get("status") != "available":
418
+ response["needs_action"].append(f"completed dependency is not currently available: {name}")
419
+ elif row.get("status") == "available":
420
+ continue
421
+ elif not row.get("install_argv"):
422
+ response["needs_action"].append(f"requested dependency is still missing and has no current installation recipe: {name}")
423
+ else:
424
+ remaining.append(name)
425
+ response["inspection"] = {"dependencies": controller.dependencies, "choices": controller.choices}
426
+ if response["needs_action"]:
427
+ return response
428
+ plan["dependencies"] = remaining
429
+ reuse = "install" in completed
430
+ if reuse:
431
+ if not response["handoff"]["runtime_verified"] or response["handoff"]["needs_action"]:
432
+ response["needs_action"] = ["verify the completed private installation before continuing"]
433
+ return response
434
+ plan["selection_mode"], plan["targets"] = None, None
435
+ if "extras" in completed:
436
+ plan["app_bridge"], plan["import_paths"], plan["project_roots"] = False, [], []
437
+ continuation = {"review_id": receipt["review_id"], "receipt_revision": receipt["receipt_revision"], "reuse_installation": reuse}
438
+ response["remaining_plan"] = plan
439
+ candidate = self.plan(plan, response["language"], continuation=continuation)
440
+ if reuse:
441
+ completed_install = receipt["operations"]["install"]["event"].get("installation", {})
442
+ completed_digest = completed_install.get("record", {}).get("release_digest") or completed_install.get("release_digest")
443
+ source_digest = candidate["preview"]["installation"].get("release_digest")
444
+ if source_digest != response["handoff"].get("release_digest") or completed_digest != source_digest:
445
+ response["needs_action"] = ["source or installed package changed after partial setup; create a fresh installation plan"]
446
+ return response
447
+ response["review"] = candidate
448
+ return response
449
+
450
+ def apply(self, review: dict[str, Any], expected_review_id: str, *, yes: bool = False) -> dict[str, Any]:
451
+ if yes is not True:
452
+ raise SetupError("apply requires explicit --yes acknowledgment of the exact reviewed plan")
453
+ review = json.loads(json.dumps(review, ensure_ascii=False))
454
+ self._validate_envelope(review, expected_review_id)
455
+ existing = self._receipt(expected_review_id)
456
+ if existing is not None:
457
+ if existing["review"] != review:
458
+ raise SetupError("review differs from the owned receipt")
459
+ self._assert_owner(review)
460
+ return self.status(expected_review_id, replayed=True)
461
+ if self._current_review(review) != review:
462
+ raise SetupError("review is stale or belongs to another execution context; create a fresh plan")
463
+ state_root = Path(self.context()["state_dir"])
464
+ with transaction_lock(state_root):
465
+ if self._receipt(expected_review_id) is not None:
466
+ return self.status(expected_review_id, replayed=True)
467
+ if self._current_review(review) != review:
468
+ raise SetupError("review changed while waiting for the setup lock; create a fresh plan")
469
+ receipt = {"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-receipt", "review_id": expected_review_id,
470
+ "review": review, "state": "running", "created_at": time.time(), "operations": {}, "progress": [], "result": None,
471
+ "owner": {"pid": os.getpid(), "process_identity": _process_identity(os.getpid())}}
472
+ self._save(receipt)
473
+ if review.get("continuation"):
474
+ parent = self._receipt(review["continuation"]["review_id"])
475
+ if parent is None or parent["receipt_revision"] != review["continuation"]["receipt_revision"] or parent.get("continued_by"):
476
+ raise SetupError("setup continuation was consumed or changed before execution")
477
+ parent["continued_by"] = expected_review_id
478
+ self._save(parent)
479
+ def progress(event: dict[str, Any]) -> None:
480
+ stage, phase = event.get("stage"), event.get("phase")
481
+ key = "dependency:" + str(event.get("dependency", event.get("id", ""))) if stage == "dependency" else stage
482
+ if phase == "started" and stage == "dependency":
483
+ actual = self._actions({"plan": {"dependency_actions": [action for action in review["preview"]["plan"]["dependency_actions"] if action["id"] == event["dependency"]]}})
484
+ expected = [row for row in review["action_inputs"] if row["id"] == event["dependency"]]
485
+ if actual != expected:
486
+ raise SetupError("dependency executable changed after review")
487
+ if stage in {"dependency", "install", "extras"}:
488
+ receipt["operations"][key] = {"state": {"started": "running", "completed": "completed", "failed": "failed", "not_started": "not_started"}.get(phase, "not_started"),
489
+ "event": event, "at": time.time()}
490
+ receipt["progress"].append(event)
491
+ self._save(receipt)
492
+ if self.progress_hook:
493
+ self.progress_hook(event)
494
+ try:
495
+ controller = self.controller_factory()
496
+ plan = {key: review["preview"]["plan"][key] for key in SetupController.FIELDS}
497
+ result = controller.apply(plan, preview=review["preview"], progress=progress,
498
+ reuse_installation=bool(review.get("continuation", {}).get("reuse_installation")))
499
+ receipt["result"] = result
500
+ active = any(operation["state"] == "running" for operation in receipt["operations"].values())
501
+ receipt["state"] = "complete" if result.get("applied") else "unknown" if active else "partial"
502
+ except BaseException as exc:
503
+ active = any(operation["state"] == "running" for operation in receipt["operations"].values())
504
+ receipt["state"] = "unknown" if active else "partial"
505
+ receipt["error"] = {"type": type(exc).__name__, "message": str(exc)}
506
+ receipt["result"] = {"applied": False, "installation_error": str(exc) or type(exc).__name__,
507
+ "dependency_results": [operation["event"] for key, operation in receipt["operations"].items() if key.startswith("dependency:") and "returncode" in operation["event"]]}
508
+ self._save(receipt)
509
+ return self.status(expected_review_id)
510
+
511
+
512
+ class _Parser(argparse.ArgumentParser):
513
+ def error(self, message):
514
+ raise SetupError(message)
515
+
516
+
517
+ def _input(source: str) -> dict[str, Any]:
518
+ if source != "-":
519
+ return _read_json(Path(source).expanduser())
520
+ if sys.stdin.isatty():
521
+ raise SetupError("--input - requires piped JSON; use a review file in a terminal")
522
+ body = sys.stdin.read(8 * 1024 * 1024 + 1)
523
+ if len(body) > 8 * 1024 * 1024:
524
+ raise SetupError("setup input is too large")
525
+ value = json.loads(body)
526
+ if not isinstance(value, dict):
527
+ raise SetupError("setup input must be a JSON object")
528
+ return value
529
+
530
+
531
+ def main(argv: list[str] | None = None) -> int:
532
+ args = None
533
+ presentation_language = None
534
+ try:
535
+ parser = _Parser(prog="agent-bios setup", add_help=False)
536
+ parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[1])
537
+ parser.add_argument("--help", action="store_true")
538
+ sub = parser.add_subparsers(dest="command")
539
+ for name in ("start", "inspect"):
540
+ command = sub.add_parser(name, add_help=False)
541
+ command.add_argument("--language", choices=sorted(LANGUAGE_IDS))
542
+ discover = sub.add_parser("discover", add_help=False)
543
+ discover.add_argument("--project-root", action="append", default=[])
544
+ plan = sub.add_parser("plan", add_help=False)
545
+ plan.add_argument("--input", required=True)
546
+ plan.add_argument("--language", choices=sorted(LANGUAGE_IDS))
547
+ apply = sub.add_parser("apply", add_help=False)
548
+ apply.add_argument("--input", required=True)
549
+ apply.add_argument("--review-id", required=True)
550
+ apply.add_argument("--yes", action="store_true")
551
+ for name in ("status", "resume"):
552
+ command = sub.add_parser(name, add_help=False)
553
+ command.add_argument("--review-id", required=True)
554
+ command.add_argument("--language", choices=sorted(LANGUAGE_IDS))
555
+ raw = list(sys.argv[1:] if argv is None else argv)
556
+ args = parser.parse_args([value for value in raw if value != "--json"])
557
+ if args.help:
558
+ result = {"schema_version": SCHEMA_VERSION, "commands": ["start", "inspect", "discover", "plan", "apply", "status", "resume"], "format": "JSON"}
559
+ else:
560
+ service = SetupService(InstructionsInstaller(args.repo))
561
+ command = args.command or "start"
562
+ with contextlib.redirect_stdout(sys.stderr):
563
+ if command == "start": result = service.start(getattr(args, "language", None))
564
+ elif command == "inspect": result = service.inspect(args.language)
565
+ elif command == "discover": result = service.discover(args.project_root)
566
+ elif command == "plan": result = service.plan(_input(args.input), args.language)
567
+ elif command == "apply":
568
+ envelope = _input(args.input)
569
+ selected_language = envelope.get("language")
570
+ if isinstance(selected_language, str) and selected_language in LANGUAGE_IDS:
571
+ presentation_language = selected_language
572
+ result = service.apply(envelope, args.review_id, yes=args.yes)
573
+ elif command == "status": result = service.status(args.review_id, args.language)
574
+ else: result = service.resume(args.review_id, args.language)
575
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
576
+ return 1 if (args.command == "apply" and result.get("state") != "complete") else 0
577
+ except (OSError, RuntimeError, ValueError, TypeError, KeyError) as exc:
578
+ language = presentation_language or getattr(args, "language", None) or detect_language(os.environ)
579
+ print(json.dumps({"schema_version": SCHEMA_VERSION, "kind": "agent-bios-setup-error", "ok": False,
580
+ "error": {"type": type(exc).__name__, "message": str(exc)},
581
+ "summary": translate(language, "Setup could not finish. Details: {detail}", detail=str(exc))}, ensure_ascii=False))
582
+ return 2
583
+
584
+
585
+ if __name__ == "__main__":
586
+ raise SystemExit(main())