agent-bios 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPENDENCIES.md +236 -80
- package/INSTALL.md +112 -0
- package/README.md +184 -524
- package/claude/CLAUDE.md +1 -1
- package/claude/guides/cli-multi-model-workflow.md +1 -1
- package/claude/guides/learning-flow.md +23 -12
- package/claude/guides/session-distill-workflow.md +22 -12
- package/codex/AGENTS.md +1 -1
- package/codex/guides/cli-multi-model-workflow.md +1 -1
- package/codex/guides/learning-flow.md +23 -12
- package/codex/guides/session-distill-workflow.md +22 -12
- package/compose/app_bridge/SKILL.md +75 -0
- package/compose/app_bridge/agents/openai.yaml +2 -0
- package/compose/app_bridge/scripts/bridge.py +76 -0
- package/compose/bootstrap/SKILL.md +12 -1
- package/compose/corpus.py +31 -9
- package/compose/corpus_app.py +456 -0
- package/compose/corpus_import.py +529 -0
- package/compose/corpus_install.py +196 -18
- package/compose/corpus_session.py +27 -0
- package/compose/corpus_setup.py +674 -0
- package/compose/corpus_setup_cli.py +582 -0
- package/compose/corpus_setup_i18n.py +318 -0
- package/compose/corpus_setup_ui.py +633 -0
- package/compose/corpus_store.py +167 -29
- package/compose/corpus_transaction.py +43 -10
- package/compose/corpus_ui_runtime.py +278 -0
- package/compose/setup/START.md +147 -0
- package/compose/ui_runtime/linkify_it_py-2.2.0-py3-none-any.whl +0 -0
- package/compose/ui_runtime/manifest.json +238 -0
- package/compose/ui_runtime/markdown_it_py-4.2.0-py3-none-any.whl +0 -0
- package/compose/ui_runtime/mdit_py_plugins-0.6.1-py3-none-any.whl +0 -0
- package/compose/ui_runtime/mdurl-0.1.2-py3-none-any.whl +0 -0
- package/compose/ui_runtime/platformdirs-4.11.8-py3-none-any.whl +0 -0
- package/compose/ui_runtime/pygments-2.21.0-py3-none-any.whl +0 -0
- package/compose/ui_runtime/rich-15.0.0-py3-none-any.whl +0 -0
- package/compose/ui_runtime/textual-8.2.8-py3-none-any.whl +0 -0
- package/compose/ui_runtime/typing_extensions-4.16.0-py3-none-any.whl +0 -0
- package/docs/advanced-launch.md +131 -0
- package/docs/assets/corpus-studio.svg +227 -0
- package/docs/corpus.md +117 -0
- package/docs/recovery.md +201 -0
- package/docs/session-model.md +120 -0
- package/docs/setup.md +190 -0
- package/docs/understand.md +40 -0
- package/install.sh +75 -46
- package/launch/agent-launch.py +91 -47
- package/launch/provision-venv.sh +44 -13
- package/learn/collect-learning.py +14 -5
- package/learn/learning.schema.json +2 -2
- package/package.json +14 -2
- package/provenance.json +1 -1
- package/wrappers/claude-run.sh +10 -13
|
@@ -26,6 +26,9 @@ import tomllib
|
|
|
26
26
|
import uuid
|
|
27
27
|
from typing import Any
|
|
28
28
|
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
sys.dont_write_bytecode = True
|
|
31
|
+
|
|
29
32
|
from corpus_transaction import (
|
|
30
33
|
_valid_release,
|
|
31
34
|
TransactionError,
|
|
@@ -345,7 +348,8 @@ class CorpusInstaller:
|
|
|
345
348
|
# CorpusStore delegates compile/load to a module name for normal package
|
|
346
349
|
# execution. Bind this instance to the release's exact catalog so a
|
|
347
350
|
# long-lived manager cannot retain an older release through sys.modules.
|
|
348
|
-
|
|
351
|
+
catalog = self._private_module(package_root, "corpus_catalog")
|
|
352
|
+
store._catalog_module = lambda: catalog
|
|
349
353
|
return store
|
|
350
354
|
|
|
351
355
|
def _catalog(self, package_root: Path) -> dict[str, Any]:
|
|
@@ -374,7 +378,9 @@ class CorpusInstaller:
|
|
|
374
378
|
"""Keep the legacy launcher panel readable without claiming activation."""
|
|
375
379
|
manifest = _read_json(package_root / "compose" / "domains.json")
|
|
376
380
|
available = sorted((manifest.get("domains") or {}).keys())
|
|
377
|
-
|
|
381
|
+
package_id = manifest.get("package_id", "@agent-bios/core")
|
|
382
|
+
applied = available if "all" in selection or package_id in selection else [
|
|
383
|
+
name for name in available if f"{package_id}/{name}" in selection]
|
|
378
384
|
_atomic_json(self.state_root / "corpus-status.json", {
|
|
379
385
|
"repo": str(package_root), "current_version": None, "latest_version": None,
|
|
380
386
|
"rolled_back_to": None, "versions": None, "summary": None,
|
|
@@ -387,7 +393,9 @@ class CorpusInstaller:
|
|
|
387
393
|
"""The status projection is part of the same publication as its record."""
|
|
388
394
|
manifest = _read_json(package_root / "compose" / "domains.json")
|
|
389
395
|
available = sorted((manifest.get("domains") or {}).keys())
|
|
390
|
-
|
|
396
|
+
package_id = manifest.get("package_id", "@agent-bios/core")
|
|
397
|
+
applied = available if "all" in selection or package_id in selection else [
|
|
398
|
+
name for name in available if f"{package_id}/{name}" in selection]
|
|
391
399
|
return _canonical({
|
|
392
400
|
"repo": str(package_root), "current_version": None, "latest_version": None,
|
|
393
401
|
"rolled_back_to": None, "versions": None, "summary": None,
|
|
@@ -544,6 +552,85 @@ class CorpusInstaller:
|
|
|
544
552
|
spec.loader.exec_module(module)
|
|
545
553
|
return module.ShellIntegration(self.env, self.repo)
|
|
546
554
|
|
|
555
|
+
def setup_catalog(self) -> dict[str, Any]:
|
|
556
|
+
catalog = self._catalog(self.repo)
|
|
557
|
+
catalog = json.loads(json.dumps(catalog))
|
|
558
|
+
present = {package["package_id"] for package in catalog["packages"]}
|
|
559
|
+
for package_id, label in (("@local/personal", "Personal corpus"),
|
|
560
|
+
("@local/learnings-claude", "Claude learning records"),
|
|
561
|
+
("@local/learnings-codex", "Codex learning records")):
|
|
562
|
+
if package_id not in present:
|
|
563
|
+
catalog["packages"].append({"package_id": package_id, "domains": {"personal": label}})
|
|
564
|
+
return catalog
|
|
565
|
+
|
|
566
|
+
def setup_discover(self, project_roots=None) -> dict[str, Any]:
|
|
567
|
+
try:
|
|
568
|
+
from .corpus_import import discover
|
|
569
|
+
except ImportError:
|
|
570
|
+
from corpus_import import discover
|
|
571
|
+
return discover(environ=self.env, project_roots=project_roots)
|
|
572
|
+
|
|
573
|
+
def setup_revision(self) -> str:
|
|
574
|
+
"""Bind an installation review to the current private selection and authoring."""
|
|
575
|
+
paths = (self.record_path, self.runtime / "state.json", self.user_root / "state.json")
|
|
576
|
+
return hashlib.sha256(_canonical({str(path): self._file_version(path) for path in paths})).hexdigest()
|
|
577
|
+
|
|
578
|
+
def _app_manager(self):
|
|
579
|
+
try:
|
|
580
|
+
from .corpus_app import AppBridge
|
|
581
|
+
except ImportError:
|
|
582
|
+
from corpus_app import AppBridge
|
|
583
|
+
return AppBridge(self.repo, self.env)
|
|
584
|
+
|
|
585
|
+
def _app_lifecycle(self, action: str, dry_run: bool = False) -> dict[str, Any]:
|
|
586
|
+
try:
|
|
587
|
+
manager = self._app_manager()
|
|
588
|
+
if action == "remove":
|
|
589
|
+
return (manager.unregister(dry_run) if manager.has_owned_registration()
|
|
590
|
+
else manager.managed_status())
|
|
591
|
+
return manager.refresh_registration()
|
|
592
|
+
except (OSError, RuntimeError) as exc:
|
|
593
|
+
return {"changed": False, "needs_action": [str(exc)]}
|
|
594
|
+
|
|
595
|
+
def setup_extras(self, plan: dict[str, Any], dry_run: bool = False) -> dict[str, Any]:
|
|
596
|
+
try:
|
|
597
|
+
from .corpus_import import capture
|
|
598
|
+
except ImportError:
|
|
599
|
+
from corpus_import import capture
|
|
600
|
+
result: dict[str, Any] = {}
|
|
601
|
+
if plan.get("app_bridge"):
|
|
602
|
+
bridge = self._app_manager()
|
|
603
|
+
status = bridge.status()
|
|
604
|
+
if status.get("needs_action"):
|
|
605
|
+
raise InstallError("; ".join(status["needs_action"]))
|
|
606
|
+
result["app_bridge"] = ({"requested": True, "dry_run": True} if dry_run
|
|
607
|
+
else bridge.register())
|
|
608
|
+
paths = plan.get("import_paths") or []
|
|
609
|
+
if paths:
|
|
610
|
+
discovered = self.setup_discover(plan.get("project_roots"))
|
|
611
|
+
allowed = {row["path"] for row in discovered["sources"]}
|
|
612
|
+
if not set(paths) <= allowed:
|
|
613
|
+
raise InstallError("instruction import selection is not in the reviewed discovery set")
|
|
614
|
+
if dry_run:
|
|
615
|
+
result["import"] = {"paths": paths, "classification": "requires model review", "dry_run": True}
|
|
616
|
+
else:
|
|
617
|
+
record = self._old_record()
|
|
618
|
+
if record is None:
|
|
619
|
+
raise InstallError("install the private runtime before capturing instructions")
|
|
620
|
+
try:
|
|
621
|
+
captured = capture(self._store(Path(record["package_root"])), paths,
|
|
622
|
+
environ=self.env, project_roots=plan.get("project_roots"),
|
|
623
|
+
expected_source_digests=plan.get("_expected_source_digests"))
|
|
624
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
625
|
+
failure = InstallError(str(exc))
|
|
626
|
+
failure.completed_extras = result
|
|
627
|
+
raise failure from exc
|
|
628
|
+
result["import"] = {"capture_id": captured["capture_id"], "source_count": len(captured["sources"]),
|
|
629
|
+
"classification": "requires model review",
|
|
630
|
+
"next_command": "agent-bios import prompt " + captured["capture_id"],
|
|
631
|
+
"app_request": "Use $agent-bios to import capture " + captured["capture_id"]}
|
|
632
|
+
return result
|
|
633
|
+
|
|
547
634
|
def _shell_paths(self, action: str) -> list[dict[str, Any]]:
|
|
548
635
|
try:
|
|
549
636
|
manager = self._shell_manager()
|
|
@@ -561,14 +648,45 @@ class CorpusInstaller:
|
|
|
561
648
|
for row in changes]
|
|
562
649
|
|
|
563
650
|
@_serialized
|
|
564
|
-
def install(self, domains: str | None = None, dry_run: bool = False
|
|
651
|
+
def install(self, domains: str | None = None, dry_run: bool = False, *,
|
|
652
|
+
selection_mode: str | None = None, targets: list[str] | None = None) -> dict[str, Any]:
|
|
565
653
|
files = self._package_files()
|
|
566
|
-
|
|
654
|
+
explicit_selection = domains is not None or selection_mode is not None or targets is not None
|
|
655
|
+
if domains is not None and selection_mode is None:
|
|
656
|
+
selection_mode = "default"
|
|
657
|
+
if selection_mode not in {None, "default", "selected", "none"}:
|
|
658
|
+
raise InstallError("selection_mode must be default, selected, or none")
|
|
659
|
+
if targets is not None and (not isinstance(targets, list) or not all(isinstance(x, str) for x in targets)):
|
|
660
|
+
raise InstallError("corpus targets must be a list of qualified names")
|
|
661
|
+
if domains is not None and targets is not None:
|
|
662
|
+
raise InstallError("use domain flags or corpus targets, not both")
|
|
663
|
+
if domains is not None and selection_mode == "none":
|
|
664
|
+
raise InstallError("no-corpus installation cannot include domain flags")
|
|
665
|
+
requested = list(targets) if targets is not None else ([] if selection_mode == "none" else self._normalized_domains(domains))
|
|
666
|
+
if selection_mode == "none" and requested:
|
|
667
|
+
raise InstallError("no-corpus installation cannot include targets")
|
|
668
|
+
if selection_mode == "selected" and not requested:
|
|
669
|
+
raise InstallError("selected corpus installation needs at least one target")
|
|
567
670
|
prior = self._old_record()
|
|
568
671
|
# An update without an explicit selection must not broaden a saved
|
|
569
672
|
# core-only or domain-limited environment back to every domain.
|
|
570
|
-
if domains is None and isinstance((prior or {}).get("selection"), list):
|
|
673
|
+
if domains is None and not explicit_selection and isinstance((prior or {}).get("selection"), list):
|
|
571
674
|
requested = prior["selection"]
|
|
675
|
+
selection_mode = prior.get("selection_mode")
|
|
676
|
+
if selection_mode is not None:
|
|
677
|
+
preview_store = self._store(self.repo)
|
|
678
|
+
catalog = self.setup_catalog()
|
|
679
|
+
try:
|
|
680
|
+
available = list(catalog["items"])
|
|
681
|
+
runtime = preview_store._runtime_state()
|
|
682
|
+
if runtime.get("selected_baseline_ref"):
|
|
683
|
+
user = preview_store._user_state()
|
|
684
|
+
for host in ("claude", "codex"):
|
|
685
|
+
available.extend(item for item in preview_store._effective_items(runtime, user, host=host)[0]
|
|
686
|
+
if item["package_id"].startswith("@local/") and item.get("active", True))
|
|
687
|
+
preview_store._validate_snapshot_selection(requested, catalog, available)
|
|
688
|
+
except (ValueError, RuntimeError) as exc:
|
|
689
|
+
raise InstallError(str(exc)) from exc
|
|
572
690
|
# A preview is strictly read-only. A real install first refuses an
|
|
573
691
|
# unrelated reset, then finishes only its own pending install before
|
|
574
692
|
# creating an immutable release directory.
|
|
@@ -581,7 +699,8 @@ class CorpusInstaller:
|
|
|
581
699
|
release, entries, digest = self._copy_release(files, dry_run)
|
|
582
700
|
if dry_run:
|
|
583
701
|
return {"dry_run": True, "release": str(release), "release_digest": digest,
|
|
584
|
-
"files": len(entries), "selection": requested
|
|
702
|
+
"files": len(entries), "selection": requested, "selection_mode": selection_mode or "default",
|
|
703
|
+
"corpus_storage": "packaged library retained privately; selection controls delivery"}
|
|
585
704
|
shell_paths = self._shell_paths("restore")
|
|
586
705
|
# Stage the immutable baseline before publishing either source pointers
|
|
587
706
|
# or launcher projections. Store owns those source pointers; this
|
|
@@ -590,7 +709,8 @@ class CorpusInstaller:
|
|
|
590
709
|
prepare = getattr(store, "prepare_install", None)
|
|
591
710
|
if not callable(prepare):
|
|
592
711
|
raise InstallError("private corpus store does not support staged install recovery")
|
|
593
|
-
candidate = prepare(requested)
|
|
712
|
+
candidate = (prepare(requested, selection_mode=selection_mode, replace_selection=explicit_selection)
|
|
713
|
+
if selection_mode is not None else prepare(requested))
|
|
594
714
|
if not isinstance(candidate, dict) or not isinstance(candidate.get("details"), dict):
|
|
595
715
|
raise InstallError("private corpus store returned an invalid install candidate")
|
|
596
716
|
details = candidate["details"]
|
|
@@ -621,6 +741,8 @@ class CorpusInstaller:
|
|
|
621
741
|
"launcher": launcher, "config_files": config, "created_at": int(time.time()),
|
|
622
742
|
"mode": "private-session-scoped", "needs_action": [],
|
|
623
743
|
}
|
|
744
|
+
if selection_mode is not None:
|
|
745
|
+
record["selection_mode"] = selection_mode
|
|
624
746
|
paths = [{"path": str(path), "before": self._file_version(path),
|
|
625
747
|
"after": self._planned_version(content), "mode": mode}
|
|
626
748
|
for path, content, mode in owned]
|
|
@@ -649,7 +771,8 @@ class CorpusInstaller:
|
|
|
649
771
|
try:
|
|
650
772
|
with operation_scope(self.state_root):
|
|
651
773
|
self._finish_install_transaction(journal, journal_data, store, candidate)
|
|
652
|
-
return {"dry_run": False, "stored": True, "activation": "unverified", "record": record
|
|
774
|
+
return {"dry_run": False, "stored": True, "activation": "unverified", "record": record,
|
|
775
|
+
"app_bridge": self._app_lifecycle("refresh")}
|
|
653
776
|
except BaseException as exc:
|
|
654
777
|
journal_data["state"] = "NEEDS_RECOVERY"
|
|
655
778
|
journal_data["error"] = type(exc).__name__
|
|
@@ -813,7 +936,8 @@ class CorpusInstaller:
|
|
|
813
936
|
except (OSError, RuntimeError) as exc:
|
|
814
937
|
raise InstallError(str(exc)) from exc
|
|
815
938
|
if record is None:
|
|
816
|
-
|
|
939
|
+
app = self._app_lifecycle("remove", dry_run)
|
|
940
|
+
return {"removed": shell_removed, "preserved": ["no private install record"], "app_bridge": app}
|
|
817
941
|
removed: list[str] = list(shell_removed)
|
|
818
942
|
preserved: list[str] = []
|
|
819
943
|
for entry in [record.get("launcher"), *(record.get("config_files") or [])]:
|
|
@@ -840,7 +964,8 @@ class CorpusInstaller:
|
|
|
840
964
|
self.record_path.unlink(missing_ok=True)
|
|
841
965
|
# User package/overlays/learnings and session snapshots/pins are purposely
|
|
842
966
|
# not enumerated or deleted here. No whole state-root removal occurs.
|
|
843
|
-
|
|
967
|
+
app = self._app_lifecycle("remove", dry_run)
|
|
968
|
+
return {"removed": removed, "preserved": preserved, "app_bridge": app,
|
|
844
969
|
"retained": [str(self.user_root), str(self.state_root / "sessions")]}
|
|
845
970
|
|
|
846
971
|
def _reset_layout(self, record: dict[str, Any], release: Path) -> tuple[list[Path], Path, Path, list[tuple[Path, bytes, int]]]:
|
|
@@ -1617,7 +1742,7 @@ class CorpusInstaller:
|
|
|
1617
1742
|
|
|
1618
1743
|
|
|
1619
1744
|
def main(argv: list[str] | None = None) -> int:
|
|
1620
|
-
parser = argparse.ArgumentParser(prog="agent-bios
|
|
1745
|
+
parser = argparse.ArgumentParser(prog="agent-bios")
|
|
1621
1746
|
parser.add_argument("--repo", help=argparse.SUPPRESS)
|
|
1622
1747
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
1623
1748
|
install = subparsers.add_parser("install")
|
|
@@ -1628,6 +1753,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1628
1753
|
onboard.add_argument("--domains")
|
|
1629
1754
|
onboard.add_argument("--dry-run", action="store_true")
|
|
1630
1755
|
onboard.add_argument("--with", dest="with_capabilities")
|
|
1756
|
+
for command in (install, onboard):
|
|
1757
|
+
interaction = command.add_mutually_exclusive_group()
|
|
1758
|
+
interaction.add_argument("--interactive", action="store_true", help="open the Textual installation wizard (default)")
|
|
1759
|
+
interaction.add_argument("--non-interactive", action="store_true", help="run without the wizard and return JSON")
|
|
1760
|
+
command.add_argument("--corpus", choices=("all", "none", "selected"),
|
|
1761
|
+
help="active corpus policy; none retains library assets privately but delivers no corpus")
|
|
1762
|
+
command.add_argument("--select", action="append", default=[], metavar="TARGET",
|
|
1763
|
+
help="package/domain/item target; repeat for multiple targets")
|
|
1631
1764
|
subparsers.add_parser("verify")
|
|
1632
1765
|
subparsers.add_parser("status")
|
|
1633
1766
|
uninstall = subparsers.add_parser("uninstall")
|
|
@@ -1643,11 +1776,41 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1643
1776
|
reset.add_argument("--expected-revision", help="reset preview generation to accept")
|
|
1644
1777
|
args = parser.parse_args(argv)
|
|
1645
1778
|
installer = CorpusInstaller(Path(args.repo) if args.repo else Path(__file__).resolve().parent.parent)
|
|
1779
|
+
interactive = False
|
|
1646
1780
|
try:
|
|
1647
1781
|
if args.command in {"install", "onboard"}:
|
|
1648
1782
|
if args.with_capabilities:
|
|
1649
|
-
raise InstallError("
|
|
1650
|
-
|
|
1783
|
+
raise InstallError("use --interactive to select dependency installation; --with is legacy-only")
|
|
1784
|
+
interactive = not args.non_interactive
|
|
1785
|
+
if args.select and args.corpus != "selected":
|
|
1786
|
+
raise InstallError("--select requires --corpus selected")
|
|
1787
|
+
if interactive and args.domains is not None:
|
|
1788
|
+
raise InstallError("--domains is a compatibility selection flag; use --non-interactive or choose corpus in the wizard")
|
|
1789
|
+
if interactive:
|
|
1790
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
1791
|
+
raise InstallError("interactive installation needs an input/output terminal; use --non-interactive for automation or app tool calls")
|
|
1792
|
+
if os.environ.get("TERM") == "dumb":
|
|
1793
|
+
raise InstallError("this terminal cannot display the Textual wizard; use a capable terminal or --non-interactive")
|
|
1794
|
+
try:
|
|
1795
|
+
from .corpus_ui_runtime import activate_ui_runtime
|
|
1796
|
+
except ImportError:
|
|
1797
|
+
from corpus_ui_runtime import activate_ui_runtime
|
|
1798
|
+
activate_ui_runtime(installer.repo)
|
|
1799
|
+
try:
|
|
1800
|
+
from .corpus_setup_ui import run_setup_ui
|
|
1801
|
+
except ImportError:
|
|
1802
|
+
from corpus_setup_ui import run_setup_ui
|
|
1803
|
+
initial = None
|
|
1804
|
+
if args.corpus is not None:
|
|
1805
|
+
mode = "none" if args.corpus == "none" else "selected"
|
|
1806
|
+
targets = ["all"] if args.corpus == "all" else list(args.select) if args.corpus == "selected" else []
|
|
1807
|
+
initial = {"selection_mode": mode, "targets": targets, "dependencies": [],
|
|
1808
|
+
"app_bridge": False, "import_paths": [], "project_roots": []}
|
|
1809
|
+
result = run_setup_ui(installer, dry_run=args.dry_run, initial_plan=initial)
|
|
1810
|
+
else:
|
|
1811
|
+
mode = "none" if args.corpus == "none" else "selected" if args.corpus else None
|
|
1812
|
+
targets = ["all"] if args.corpus == "all" else args.select if args.corpus == "selected" else None
|
|
1813
|
+
result = installer.install(args.domains, args.dry_run, selection_mode=mode, targets=targets)
|
|
1651
1814
|
elif args.command == "verify":
|
|
1652
1815
|
result = installer.verify()
|
|
1653
1816
|
elif args.command == "status":
|
|
@@ -1659,10 +1822,25 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1659
1822
|
expected_revision=args.expected_revision)
|
|
1660
1823
|
else:
|
|
1661
1824
|
result = installer.migrate(apply=args.apply and not args.dry_run, yes=args.yes)
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1825
|
+
if interactive:
|
|
1826
|
+
try:
|
|
1827
|
+
from .corpus_setup import format_setup_result
|
|
1828
|
+
except ImportError:
|
|
1829
|
+
from corpus_setup import format_setup_result
|
|
1830
|
+
print(format_setup_result(result, language=result.get("ui_language", "en")))
|
|
1831
|
+
else:
|
|
1832
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
|
1833
|
+
return 1 if result.get("dependency_failed") or result.get("extras_error") or result.get("installation_error") or result.get("error") else 0
|
|
1834
|
+
except (InstallError, RuntimeError, ValueError, OSError) as exc:
|
|
1835
|
+
message = str(exc)
|
|
1836
|
+
language = getattr(exc, "ui_language", "en")
|
|
1837
|
+
if interactive and language != "en":
|
|
1838
|
+
try:
|
|
1839
|
+
from .corpus_setup_i18n import translate
|
|
1840
|
+
except ImportError:
|
|
1841
|
+
from corpus_setup_i18n import translate
|
|
1842
|
+
message = translate(language, "Setup could not finish. Details: {detail}", detail=message)
|
|
1843
|
+
print(f"corpus-install: {message}", file=sys.stderr)
|
|
1666
1844
|
return 1
|
|
1667
1845
|
|
|
1668
1846
|
|
|
@@ -23,6 +23,23 @@ class SessionError(RuntimeError):
|
|
|
23
23
|
pass
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def validate_working_directory_argv(host, argv):
|
|
27
|
+
"""Keep a private Codex snapshot and its native session in the same directory."""
|
|
28
|
+
if host != 'codex':
|
|
29
|
+
return
|
|
30
|
+
if not isinstance(argv, (list, tuple)) or not all(isinstance(token, str) for token in argv):
|
|
31
|
+
raise SessionError('private Codex activation requires a valid argument list')
|
|
32
|
+
for token in argv:
|
|
33
|
+
if token == '--':
|
|
34
|
+
break
|
|
35
|
+
if token == '--cd' or token.startswith('--cd=') or token.startswith('-C'):
|
|
36
|
+
raise SessionError(
|
|
37
|
+
'private Codex corpus activation cannot include --cd/-C: the selected corpus and '
|
|
38
|
+
'session pin use the launch working directory. cd into the target directory first, '
|
|
39
|
+
'then start a new activated session without a working-directory override'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
26
43
|
def _global_instruction_choice(host, include_global_instructions):
|
|
27
44
|
if type(include_global_instructions) is not bool:
|
|
28
45
|
raise SessionError('include_global_instructions must be a boolean')
|
|
@@ -104,6 +121,7 @@ def _settings_values(argv):
|
|
|
104
121
|
|
|
105
122
|
|
|
106
123
|
def _record_instruction_choice(record, env, check_paths=True):
|
|
124
|
+
validate_working_directory_argv(record['host'], record.get('argv'))
|
|
107
125
|
include = _global_instruction_choice(record['host'], record.get('include_global_instructions', True))
|
|
108
126
|
if not include:
|
|
109
127
|
values = _settings_values(record['argv'])
|
|
@@ -455,6 +473,7 @@ def _verified_launch_snapshot(state_root, snapshot):
|
|
|
455
473
|
|
|
456
474
|
def compose_argv(command, argv, host, snapshot, cwd=None, env=None, include_global_instructions=True):
|
|
457
475
|
"""Preserve native instructions before the selected corpus and launch contract."""
|
|
476
|
+
validate_working_directory_argv(host, argv)
|
|
458
477
|
_global_instruction_choice(host, include_global_instructions)
|
|
459
478
|
native_env = dict(os.environ if env is None else env)
|
|
460
479
|
native_cwd = pathlib.Path(cwd or pathlib.Path.cwd()).resolve()
|
|
@@ -733,6 +752,7 @@ def recover_activations(state_root, command=None, host=None, env=None):
|
|
|
733
752
|
|
|
734
753
|
|
|
735
754
|
def create_codex_session(command, argv, state_root, record, cwd, env):
|
|
755
|
+
validate_working_directory_argv('codex', argv)
|
|
736
756
|
with CodexServer(command, config_flags(argv), cwd, env) as server:
|
|
737
757
|
params = {'cwd': str(cwd), 'developerInstructions': instruction_value(argv, 'codex'),
|
|
738
758
|
'ephemeral': False, 'experimentalRawEvents': False}
|
|
@@ -763,12 +783,19 @@ def create_codex_session(command, argv, state_root, record, cwd, env):
|
|
|
763
783
|
def launch(command, argv, state_root, host, snapshot, cwd=None, env=None, resume_id=None,
|
|
764
784
|
include_global_instructions=True):
|
|
765
785
|
"""Start a pinned native session, returning its exit status."""
|
|
786
|
+
validate_working_directory_argv(host, argv)
|
|
766
787
|
cwd = pathlib.Path(cwd or pathlib.Path.cwd()).resolve()
|
|
767
788
|
env = dict(os.environ if env is None else env)
|
|
768
789
|
if resume_id and include_global_instructions is not True:
|
|
769
790
|
raise SessionError('resume uses its recorded global instruction choice; omit the override or start a new session')
|
|
770
791
|
if not resume_id:
|
|
771
792
|
_global_instruction_choice(host, include_global_instructions)
|
|
793
|
+
elif host == 'codex':
|
|
794
|
+
_, pins = session_paths(state_root)
|
|
795
|
+
session_id = validate_session_id(resume_id)
|
|
796
|
+
pin_path = pins / host / f'{session_id}.json'
|
|
797
|
+
if pin_path.exists() or pin_path.is_symlink():
|
|
798
|
+
read_pin(state_root, host, session_id)
|
|
772
799
|
recovered = recover_activations(state_root, command, host, env)
|
|
773
800
|
if recovered['pending']:
|
|
774
801
|
print(f"agent-bios: {len(recovered['pending'])} activation(s) lack host evidence; their snapshots remain retained.", file=sys.stderr)
|