agent-bios 0.16.0 → 0.17.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.
@@ -0,0 +1,267 @@
1
+ """Opt-in zsh entrypoints, independent of native instruction files and corpus content."""
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 re
11
+ import shlex
12
+ import tempfile
13
+ import time
14
+
15
+
16
+ START = b"# >>> agent-bios shell connection >>>\n"
17
+ END = b"# <<< agent-bios shell connection <<<\n"
18
+ SCRIPT_HEADER = b"# agent-bios optional shell connection; managed by agent-bios shell\n"
19
+
20
+
21
+ class ShellIntegrationError(RuntimeError):
22
+ pass
23
+
24
+
25
+ class ShellIntegration:
26
+ def __init__(self, environ=None, source_root=None):
27
+ self.env = dict(os.environ if environ is None else environ)
28
+ self.home = Path(self.env.get("HOME", str(Path.home()))).expanduser().absolute()
29
+ self.zdotdir = Path(self.env.get("ZDOTDIR") or str(self.home)).expanduser().absolute()
30
+ self.state = Path(self.env.get("AGENT_BIOS_STATE_DIR", str(self.home / ".local/share/agent-bios"))).expanduser().absolute()
31
+ self.startup = self.zdotdir / ".zshrc"
32
+ self.script = self.home / ".config/agent-launch/shell.zsh"
33
+ self.receipt = self.state / "runtime/shell-connection.json"
34
+ self.source = Path(source_root) if source_root else Path(__file__).resolve().parent.parent
35
+
36
+ @staticmethod
37
+ def _safe_ancestors(path):
38
+ import sys
39
+ compose = str(Path(__file__).resolve().parent.parent / "compose")
40
+ if compose not in sys.path:
41
+ sys.path.insert(0, compose)
42
+ from corpus_transaction import reject_symlink_ancestors, TransactionError
43
+ try:
44
+ reject_symlink_ancestors(path)
45
+ except TransactionError as exc:
46
+ raise ShellIntegrationError(str(exc)) from exc
47
+
48
+ @staticmethod
49
+ def _safe(path):
50
+ ShellIntegration._safe_ancestors(path)
51
+ if path.exists() and not path.is_file():
52
+ raise ShellIntegrationError(f"shell connection target is not a regular file: {path}")
53
+
54
+ def _read(self, path):
55
+ self._safe(path)
56
+ return path.read_bytes() if path.exists() else None
57
+
58
+ def _record(self, raw):
59
+ if raw is None:
60
+ return None
61
+ try:
62
+ value = json.loads(raw)
63
+ except (ValueError, UnicodeError) as exc:
64
+ raise ShellIntegrationError(f"invalid shell connection record: {self.receipt}") from exc
65
+ if (not isinstance(value, dict) or value.get("schema_version") != 1
66
+ or value.get("startup") != str(self.startup)
67
+ or value.get("script") != str(self.script)
68
+ or not isinstance(value.get("startup_existed"), bool)
69
+ or not re.fullmatch(r"[0-9a-f]{64}", str(value.get("sha256", "")))):
70
+ raise ShellIntegrationError(f"shell connection record does not match HOME/ZDOTDIR: {self.receipt}")
71
+ return value
72
+
73
+ def _block(self):
74
+ path = shlex.quote(str(self.script))
75
+ return START + f"[ -r {path} ] && source {path}\n".encode() + END
76
+
77
+ def _strip(self, raw):
78
+ body = raw or b""
79
+ starts, ends = body.count(START.rstrip(b"\n")), body.count(END.rstrip(b"\n"))
80
+ if not starts and not ends:
81
+ return body, False
82
+ block = self._block()
83
+ if starts != 1 or ends != 1 or body.count(block) != 1:
84
+ raise ShellIntegrationError(f"shell connection block was edited; preserve and reconcile it: {self.startup}")
85
+ return body.replace(block, b"", 1), True
86
+
87
+ def _script_body(self):
88
+ source = self.source / "launch/agent-launch.zsh"
89
+ body = self._read(source)
90
+ if body is None:
91
+ raise ShellIntegrationError(f"shell connection source missing: {source}")
92
+ return SCRIPT_HEADER + (
93
+ f"typeset -g _agent_launch_private_connection={shlex.quote(str(self.script))}\n").encode() + body
94
+
95
+ def has_connection(self):
96
+ """Discover interrupted opt-ins without inspecting an ordinary user's rc.
97
+
98
+ A header is only a reason to inspect: plan still requires exact managed
99
+ bytes or a valid receipt hash before modifying any discovered script.
100
+ """
101
+ if self.receipt.exists() or self.receipt.is_symlink():
102
+ return True
103
+ if not self.script.is_file():
104
+ return False
105
+ try:
106
+ self._safe(self.script)
107
+ except ShellIntegrationError:
108
+ # Without a receipt this is not evidence of our ownership. Leave a
109
+ # foreign symlink/directory alone; explicit shell actions still refuse it.
110
+ return False
111
+ current = self._read(self.script)
112
+ return current is not None and current.startswith(SCRIPT_HEADER)
113
+
114
+ def plan(self, action, *, installing=False):
115
+ if action not in {"restore", "remove", "status"}:
116
+ raise ShellIntegrationError(f"unknown shell connection action: {action}")
117
+ before = {path: self._read(path) for path in (self.startup, self.script, self.receipt)}
118
+ record = self._record(before[self.receipt])
119
+ remaining, connected = self._strip(before[self.startup])
120
+ expected = self._script_body()
121
+ current = before[self.script]
122
+ if current is not None and current != expected and (
123
+ record is None or hashlib.sha256(current).hexdigest() != record["sha256"]):
124
+ raise ShellIntegrationError(f"shell connection will not replace an unowned or edited file: {self.script}")
125
+ launcher = self.home / ".local/bin/agent-launch"
126
+ try:
127
+ self._safe(launcher)
128
+ launcher_ready = launcher.is_file() and os.access(launcher, os.X_OK)
129
+ except ShellIntegrationError:
130
+ launcher_ready = False
131
+ active = connected and current is not None and record is not None and launcher_ready
132
+ needs_action = []
133
+ if not active and (connected or current is not None or record is not None):
134
+ needs_action.append("shell connection is incomplete; run agent-bios shell restore or remove")
135
+ if action == "restore":
136
+ installed = self._read(self.state / "runtime/private-install.json")
137
+ try:
138
+ private = json.loads(installed) if installed is not None else {}
139
+ except (ValueError, UnicodeError) as exc:
140
+ raise ShellIntegrationError("private installation record is invalid; run agent-bios verify") from exc
141
+ if not installing and (not launcher_ready
142
+ or not isinstance(private, dict) or private.get("mode") != "private-session-scoped"
143
+ or not isinstance(private.get("launcher"), dict)
144
+ or private["launcher"].get("path") != str(launcher)):
145
+ raise ShellIntegrationError("restore needs a private installation; run agent-bios install or migrate first")
146
+ receipt = {"schema_version": 1, "startup": str(self.startup), "script": str(self.script),
147
+ "sha256": hashlib.sha256(expected).hexdigest(),
148
+ "startup_existed": record["startup_existed"] if record else before[self.startup] is not None}
149
+ receipt_body = (json.dumps(receipt, sort_keys=True) + "\n").encode()
150
+ # Persist first-opt-in ownership before publishing the script/hook.
151
+ # On updates, retain the old hash until the new script is published:
152
+ # either old recorded bytes or current source bytes remain recoverable.
153
+ after = {self.receipt: receipt_body} if record is None else {}
154
+ after.update({self.script: expected,
155
+ self.startup: before[self.startup] if connected else self._block() + remaining,
156
+ self.receipt: receipt_body})
157
+ elif action == "remove":
158
+ after = {self.startup: remaining if before[self.startup] is not None else None,
159
+ self.script: None, self.receipt: None}
160
+ if record and not record["startup_existed"] and not remaining:
161
+ after[self.startup] = None
162
+ else:
163
+ after = {}
164
+ changes = [{"path": path, "before": before[path], "after": body,
165
+ "mode": path.stat().st_mode & 0o777 if before[path] is not None else 0o600}
166
+ for path, body in after.items() if before[path] != body]
167
+ return {"enabled": active, "startup_path": str(self.startup), "shell_path": str(self.script),
168
+ "needs_action": needs_action, "changes": changes}
169
+
170
+ def status(self):
171
+ try:
172
+ result = self.plan("status")
173
+ result.pop("changes")
174
+ return result
175
+ except (ShellIntegrationError, OSError) as exc:
176
+ return {"enabled": False, "startup_path": str(self.startup), "shell_path": str(self.script),
177
+ "needs_action": [str(exc)]}
178
+
179
+ @staticmethod
180
+ def _write(path, body, mode):
181
+ if body is None:
182
+ path.unlink(missing_ok=True)
183
+ return
184
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
185
+ descriptor, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
186
+ try:
187
+ with os.fdopen(descriptor, "wb") as output:
188
+ output.write(body)
189
+ output.flush()
190
+ os.fsync(output.fileno())
191
+ os.chmod(name, mode)
192
+ os.replace(name, path)
193
+ finally:
194
+ Path(name).unlink(missing_ok=True)
195
+
196
+ def apply(self, action, dry_run=False):
197
+ # One writer lock is shared with install/reset/migrate. Imports stay lazy so
198
+ # status and help do not create state or require an installed corpus.
199
+ import sys
200
+ compose = str(Path(__file__).resolve().parent.parent / "compose")
201
+ if compose not in sys.path:
202
+ sys.path.insert(0, compose)
203
+ from corpus_transaction import transaction_lock, guard_pending
204
+ lock = contextlib.nullcontext() if dry_run else transaction_lock(self.state)
205
+ with lock:
206
+ guard_pending(self.state)
207
+ plan = self.plan(action)
208
+ changes = plan.pop("changes")
209
+ result = {**plan, "preview": dry_run, "action": action,
210
+ "changed_paths": [str(row["path"]) for row in changes]}
211
+ if dry_run or not changes:
212
+ return result
213
+ for row in changes:
214
+ if self._read(row["path"]) != row["before"]:
215
+ raise ShellIntegrationError(f"shell file changed during planning: {row['path']}")
216
+ backups = self.state / "runtime/shell-backups"
217
+ self._safe_ancestors(backups)
218
+ backups.mkdir(parents=True, exist_ok=True, mode=0o700)
219
+ backup = Path(tempfile.mkdtemp(prefix=f"{int(time.time())}-", dir=backups))
220
+ metadata = []
221
+ for index, row in enumerate(changes):
222
+ saved = backup / str(index)
223
+ if row["before"] is not None:
224
+ self._write(saved, row["before"], 0o600)
225
+ if saved.read_bytes() != row["before"]:
226
+ raise ShellIntegrationError(f"shell backup did not verify: {saved}")
227
+ metadata.append({"path": str(row["path"]), "existed": row["before"] is not None,
228
+ "file": str(index), "mode": row["mode"]})
229
+ self._write(backup / "paths.json", (json.dumps(metadata) + "\n").encode(), 0o600)
230
+ completed = []
231
+ try:
232
+ for row in changes:
233
+ if self._read(row["path"]) != row["before"]:
234
+ raise ShellIntegrationError(f"shell file changed during apply: {row['path']}")
235
+ completed.append(row)
236
+ self._write(row["path"], row["after"], row["mode"])
237
+ for row in changes:
238
+ if self._read(row["path"]) != row["after"]:
239
+ raise ShellIntegrationError(f"shell change did not verify: {row['path']}")
240
+ except BaseException:
241
+ # Roll back only our exact writes; retain concurrent user edits and
242
+ # the durable originals for recovery rather than overwriting them.
243
+ for row in reversed(completed):
244
+ with contextlib.suppress(OSError, ShellIntegrationError):
245
+ if self._read(row["path"]) == row["after"]:
246
+ self._write(row["path"], row["before"], row["mode"])
247
+ raise ShellIntegrationError(f"shell connection apply interrupted; originals saved at {backup}")
248
+ return {**result, **self.status(), "backup_root": str(backup)}
249
+
250
+
251
+ def main(argv=None):
252
+ parser = argparse.ArgumentParser(description=__doc__)
253
+ parser.add_argument("action", choices=("status", "restore", "remove"), nargs="?", default="status")
254
+ parser.add_argument("--dry-run", action="store_true", help="preview without editing shell files")
255
+ args = parser.parse_args(argv)
256
+ manager = ShellIntegration()
257
+ try:
258
+ result = manager.status() if args.action == "status" else manager.apply(args.action, args.dry_run)
259
+ print(json.dumps(result, ensure_ascii=False, indent=2))
260
+ return 1 if result.get("needs_action") else 0
261
+ except (ShellIntegrationError, OSError, RuntimeError) as exc:
262
+ print(f"shell connection: {exc}", file=__import__("sys").stderr)
263
+ return 1
264
+
265
+
266
+ if __name__ == "__main__":
267
+ raise SystemExit(main())
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-bios",
3
- "version": "0.16.0",
4
- "releaseDate": "2026-09-10",
3
+ "version": "0.17.0",
4
+ "releaseDate": "2026-09-11",
5
5
  "description": "A thin, low-level instruction layer for LLM CLI agents: one set of principles and behavior whichever model you run. Stores a private, editable corpus for explicitly activated sessions.",
6
6
  "bin": {
7
7
  "agent-bios": "install.sh"
@@ -24,6 +24,7 @@
24
24
  "learn/promotions.json",
25
25
  "launch/agent-launch.zsh",
26
26
  "launch/agent-launch.py",
27
+ "launch/shell_integration.py",
27
28
  "compose/pkgid.py",
28
29
  "compose/register-hooks.py",
29
30
  "compose/assemble.py",
@@ -39,6 +40,7 @@
39
40
  "compose/corpus_session.py",
40
41
  "compose/corpus_install.py",
41
42
  "compose/corpus_transaction.py",
43
+ "compose/corpus_understand.py",
42
44
  "compose/bootstrap/",
43
45
  "launch/check-prompting-targets.sh",
44
46
  "learn/check-learning.py",
package/provenance.json CHANGED
@@ -1 +1 @@
1
- {"commit":"79b4adb99a51410411cdb416a56b40fe243be4c8","committedAt":"2026-09-10T08:03:58+09:00","dirty":false}
1
+ {"commit":"7b616f4ae5366b1395560b3a88556287ac36122d","committedAt":"2026-09-11T15:52:49+09:00","dirty":false}