agent-bios 0.19.1 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/DEPENDENCIES.md +58 -30
  2. package/INSTALL.md +4 -4
  3. package/README.md +105 -28
  4. package/claude/CLAUDE.md +1 -1
  5. package/claude/guides/claude-prompting.md +1 -1
  6. package/claude/guides/cli-multi-model-workflow.md +4 -4
  7. package/claude/guides/coding-staged-workflow.md +17 -0
  8. package/claude/guides/documentation-hygiene.md +3 -0
  9. package/claude/guides/gpt-prompting.md +1 -1
  10. package/claude/guides/korean-writing.md +153 -0
  11. package/claude/guides/learning-flow.md +4 -4
  12. package/claude/guides/llm-capability-boundary.md +7 -1
  13. package/claude/guides/session-distill-workflow.md +8 -8
  14. package/claude/guides/slide-writing/RUNBOOK.md +5 -5
  15. package/claude/guides/tooling-gotchas.md +20 -1
  16. package/claude/guides/ui-design/visual-direction.md +88 -0
  17. package/claude/guides/ui-design.md +90 -0
  18. package/claude/guides/verification-discipline.md +10 -1
  19. package/claude/hooks/tooling-gotchas-hook.py +41 -0
  20. package/claude/skills/repo-charter/SKILL.md +3 -3
  21. package/claude/skills/understand/SKILL.md +5 -5
  22. package/codex/AGENTS.md +1 -1
  23. package/codex/guides/claude-prompting.md +1 -1
  24. package/codex/guides/cli-multi-model-workflow.md +4 -4
  25. package/codex/guides/coding-staged-workflow.md +17 -0
  26. package/codex/guides/documentation-hygiene.md +3 -0
  27. package/codex/guides/gpt-prompting.md +1 -1
  28. package/codex/guides/korean-writing.md +153 -0
  29. package/codex/guides/learning-flow.md +4 -4
  30. package/codex/guides/llm-capability-boundary.md +7 -1
  31. package/codex/guides/session-distill-workflow.md +8 -8
  32. package/codex/guides/slide-writing/RUNBOOK.md +5 -5
  33. package/codex/guides/tooling-gotchas.md +20 -1
  34. package/codex/guides/ui-design/visual-direction.md +88 -0
  35. package/codex/guides/ui-design.md +90 -0
  36. package/codex/guides/verification-discipline.md +10 -1
  37. package/compose/app_bridge/SKILL.md +12 -12
  38. package/compose/app_bridge/scripts/bridge.py +35 -10
  39. package/compose/app_desktop/server.py +250 -0
  40. package/compose/assemble.py +5 -5
  41. package/compose/bootstrap/SKILL.md +18 -18
  42. package/compose/canary.sh +4 -4
  43. package/compose/check-domains.py +6 -6
  44. package/compose/corpus-state.py +16 -1168
  45. package/compose/corpus.py +13 -402
  46. package/compose/corpus_app.py +14 -450
  47. package/compose/corpus_catalog.py +15 -926
  48. package/compose/corpus_import.py +14 -523
  49. package/compose/corpus_install.py +14 -1847
  50. package/compose/corpus_session.py +16 -848
  51. package/compose/corpus_setup.py +16 -672
  52. package/compose/corpus_setup_cli.py +15 -580
  53. package/compose/corpus_setup_i18n.py +20 -324
  54. package/compose/corpus_setup_ui.py +18 -645
  55. package/compose/corpus_store.py +16 -1664
  56. package/compose/corpus_transaction.py +15 -284
  57. package/compose/corpus_ui.py +17 -972
  58. package/compose/corpus_ui_runtime.py +16 -274
  59. package/compose/corpus_understand.py +13 -671
  60. package/compose/domains.json +3 -1
  61. package/compose/host_platform.py +121 -0
  62. package/compose/instructions-state.py +1178 -0
  63. package/compose/instructions.py +409 -0
  64. package/compose/instructions_app.py +697 -0
  65. package/compose/instructions_catalog.py +931 -0
  66. package/compose/instructions_import.py +537 -0
  67. package/compose/instructions_install.py +1932 -0
  68. package/compose/instructions_session.py +852 -0
  69. package/compose/instructions_setup.py +713 -0
  70. package/compose/instructions_setup_cli.py +607 -0
  71. package/compose/instructions_setup_i18n.py +327 -0
  72. package/compose/instructions_setup_ui.py +647 -0
  73. package/compose/instructions_store.py +1668 -0
  74. package/compose/instructions_transaction.py +308 -0
  75. package/compose/instructions_ui.py +975 -0
  76. package/compose/instructions_ui_runtime.py +279 -0
  77. package/compose/instructions_understand.py +678 -0
  78. package/compose/native_cli.py +52 -0
  79. package/compose/register-hooks.py +1 -1
  80. package/compose/runtime_entry.py +58 -0
  81. package/compose/setup/START.md +11 -11
  82. package/compose/windows_deploy.py +719 -0
  83. package/docs/advanced-launch.md +11 -11
  84. package/docs/instructions-compatibility.md +86 -0
  85. package/docs/{corpus.md → instructions.md} +36 -8
  86. package/docs/recovery.md +10 -10
  87. package/docs/releases/0.19.2.md +38 -0
  88. package/docs/releases/0.19.3.md +107 -0
  89. package/docs/session-model.md +31 -20
  90. package/docs/setup.md +63 -26
  91. package/docs/understand.md +6 -6
  92. package/docs/windows.md +99 -0
  93. package/install.sh +71 -69
  94. package/launch/agent-launch.py +309 -293
  95. package/launch/agent-launch.toml +2 -2
  96. package/launch/agent-launch.zsh +11 -1
  97. package/launch/i18n/en.toml +55 -55
  98. package/launch/i18n/ja.toml +56 -56
  99. package/launch/i18n/ko.toml +56 -56
  100. package/launch/shell_integration.py +4 -4
  101. package/learn/collect-learning.py +10 -10
  102. package/learn/learning.schema.json +1 -1
  103. package/learn/migrate-learnings.py +51 -51
  104. package/package.json +33 -12
  105. package/provenance.json +1 -1
  106. /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
@@ -0,0 +1,279 @@
1
+ """Load the shipped pure-Python UI wheels offline into one process-owned directory."""
2
+ from __future__ import annotations
3
+
4
+ import atexit
5
+ from email.parser import BytesParser
6
+ import hashlib
7
+ import importlib
8
+ import io
9
+ import json
10
+ import os
11
+ from pathlib import Path, PurePosixPath
12
+ import re
13
+ import stat
14
+ import sys
15
+ import tempfile
16
+ import threading
17
+ from typing import Any
18
+ import zipfile
19
+ import zlib
20
+
21
+
22
+ SCHEMA_VERSION = 1
23
+ MAX_ARCHIVE_BYTES = 32 * 1024 * 1024
24
+ MAX_EXPANDED_BYTES = 128 * 1024 * 1024
25
+ _LOCK = threading.RLock()
26
+ _ACTIVE: tuple[str, tempfile.TemporaryDirectory[str]] | None = None
27
+
28
+
29
+ class UiRuntimeError(RuntimeError):
30
+ pass
31
+
32
+
33
+ def _canonical(value: Any) -> bytes:
34
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
35
+
36
+
37
+ def _bundle_id(manifest: dict[str, Any]) -> str:
38
+ return hashlib.sha256(_canonical({key: value for key, value in manifest.items() if key != "bundle_id"})).hexdigest()
39
+
40
+
41
+ def _normalized_name(name: str) -> str:
42
+ return re.sub(r"[-_.]+", "-", name).lower()
43
+
44
+
45
+ def _members(archive: zipfile.ZipFile) -> list[zipfile.ZipInfo]:
46
+ result = []
47
+ seen = set()
48
+ expanded = 0
49
+ for info in archive.infolist():
50
+ raw = info.filename.rstrip("/")
51
+ path = PurePosixPath(raw)
52
+ if not raw or path.is_absolute() or "\\" in raw or path.as_posix() != raw \
53
+ or any(part in {".", ".."} for part in path.parts) \
54
+ or any(ord(character) < 32 or ord(character) == 127 for character in raw):
55
+ raise UiRuntimeError(f"unsafe UI wheel member: {info.filename!r}")
56
+ if raw in seen:
57
+ raise UiRuntimeError(f"duplicate UI wheel member: {raw}")
58
+ seen.add(raw)
59
+ mode = info.external_attr >> 16
60
+ if stat.S_ISLNK(mode) or (stat.S_IFMT(mode) not in {0, stat.S_IFREG, stat.S_IFDIR}):
61
+ raise UiRuntimeError(f"non-regular UI wheel member: {raw}")
62
+ if info.is_dir():
63
+ continue
64
+ if info.flag_bits & 1:
65
+ raise UiRuntimeError(f"encrypted UI wheel member: {raw}")
66
+ if path.suffix.lower() in {".so", ".pyd", ".dll", ".dylib", ".pth", ".pyc"} \
67
+ or path.parts[0].endswith(".data"):
68
+ raise UiRuntimeError(f"UI wheel requires unsupported installation behavior: {raw}")
69
+ expanded += info.file_size
70
+ if expanded > MAX_EXPANDED_BYTES:
71
+ raise UiRuntimeError("UI wheel exceeds the extraction size limit")
72
+ result.append(info)
73
+ if not result:
74
+ raise UiRuntimeError("UI wheel has no files")
75
+ return result
76
+
77
+
78
+ def wheel_metadata(filename: str, data: bytes) -> dict[str, Any]:
79
+ """Read wheel metadata and licensing without importing its Python code."""
80
+ if Path(filename).name != filename or not re.fullmatch(r"[A-Za-z0-9_.!+-]+-py[0-9.]+-none-any\.whl", filename):
81
+ raise UiRuntimeError(f"UI runtime requires a pure Python universal wheel: {filename}")
82
+ if not data or len(data) > MAX_ARCHIVE_BYTES:
83
+ raise UiRuntimeError(f"invalid UI wheel size: {filename}")
84
+ try:
85
+ with zipfile.ZipFile(io.BytesIO(data)) as archive:
86
+ members = _members(archive)
87
+ names = {member.filename for member in members}
88
+ metadata_paths = [name for name in names if name.endswith(".dist-info/METADATA")]
89
+ if len(metadata_paths) != 1:
90
+ raise UiRuntimeError(f"UI wheel needs exactly one distribution metadata file: {filename}")
91
+ metadata_path = metadata_paths[0]
92
+ info_root = metadata_path.rsplit("/", 1)[0]
93
+ wheel_path = info_root + "/WHEEL"
94
+ if wheel_path not in names:
95
+ raise UiRuntimeError(f"UI wheel lacks WHEEL metadata: {filename}")
96
+ metadata = BytesParser().parsebytes(archive.read(metadata_path))
97
+ wheel = BytesParser().parsebytes(archive.read(wheel_path))
98
+ tags = wheel.get_all("Tag", [])
99
+ if wheel.get("Root-Is-Purelib", "").lower() != "true" or not tags \
100
+ or any(not re.fullmatch(r"py[0-9.]+-none-any", tag) for tag in tags):
101
+ raise UiRuntimeError(f"UI wheel is not pure Python: {filename}")
102
+ license_files = sorted(name for name in names if name.startswith(info_root + "/") and (
103
+ "/licenses/" in name.lower() or PurePosixPath(name).name.lower().startswith(("license", "licence", "copying"))))
104
+ if not license_files or any(not archive.read(name).strip() for name in license_files):
105
+ raise UiRuntimeError(f"UI wheel lacks an upstream license: {filename}")
106
+ modules = set()
107
+ for name in names:
108
+ parts = PurePosixPath(name).parts
109
+ if parts[0].endswith(".dist-info"):
110
+ continue
111
+ root = parts[0][:-3] if len(parts) == 1 and parts[0].endswith(".py") else parts[0]
112
+ if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", root):
113
+ modules.add(root)
114
+ name, version = metadata.get("Name"), metadata.get("Version")
115
+ if not isinstance(name, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+", name) \
116
+ or not isinstance(version, str) or not re.fullmatch(r"[A-Za-z0-9_.!+]+", version) or not modules:
117
+ raise UiRuntimeError(f"invalid UI distribution metadata: {filename}")
118
+ return {"name": _normalized_name(name), "version": version, "filename": filename,
119
+ "sha256": hashlib.sha256(data).hexdigest(), "size_bytes": len(data),
120
+ "expanded_bytes": sum(member.file_size for member in members),
121
+ "modules": sorted(modules), "license_files": license_files,
122
+ "requires_python": metadata.get("Requires-Python", ""),
123
+ "requires_dist": metadata.get_all("Requires-Dist", [])}
124
+ except (zipfile.BadZipFile, KeyError, OSError, UnicodeError, RuntimeError, ValueError, EOFError, zlib.error) as exc:
125
+ if isinstance(exc, UiRuntimeError):
126
+ raise
127
+ raise UiRuntimeError(f"cannot read UI wheel {filename}: {exc}") from exc
128
+
129
+
130
+ def _read_bundle(repo: Path) -> tuple[dict[str, Any], list[tuple[dict[str, Any], bytes]]]:
131
+ bundle = Path(repo) / "compose/ui_runtime"
132
+ manifest_path = bundle / "manifest.json"
133
+ if bundle.is_symlink() or manifest_path.is_symlink() or not manifest_path.is_file():
134
+ raise UiRuntimeError("bundled UI runtime manifest is missing or unsafe; reinstall the agent-bios package")
135
+ try:
136
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
137
+ except (OSError, ValueError) as exc:
138
+ raise UiRuntimeError(f"cannot read UI runtime manifest: {exc}") from exc
139
+ if not isinstance(manifest, dict) or manifest.get("schema_version") != SCHEMA_VERSION \
140
+ or manifest.get("python_requirement") != ">=3.11" or manifest.get("source") != "pypi":
141
+ raise UiRuntimeError("unsupported UI runtime manifest")
142
+ if manifest.get("bundle_id") != _bundle_id(manifest):
143
+ raise UiRuntimeError("UI runtime manifest fingerprint mismatch")
144
+ requirements = manifest.get("root_requirement")
145
+ rows = manifest.get("packages")
146
+ if not isinstance(requirements, str) or not re.fullmatch(r"textual==[0-9][A-Za-z0-9_.!+]*", requirements) \
147
+ or not isinstance(rows, list) or not rows:
148
+ raise UiRuntimeError("UI runtime manifest has no valid package inventory")
149
+ supplied = set()
150
+ filenames = set()
151
+ modules = set()
152
+ files = set()
153
+ expanded = 0
154
+ archives = []
155
+ for row in rows:
156
+ if not isinstance(row, dict) or not isinstance(row.get("filename"), str):
157
+ raise UiRuntimeError("UI runtime manifest has an invalid package row")
158
+ filename = row["filename"]
159
+ if Path(filename).name != filename or filename in filenames:
160
+ raise UiRuntimeError(f"invalid or duplicate UI wheel name: {filename}")
161
+ path = bundle / filename
162
+ if path.is_symlink() or not path.is_file():
163
+ raise UiRuntimeError(f"bundled UI wheel missing or unsafe: {filename}")
164
+ if path.stat().st_size != row.get("size_bytes") or path.stat().st_size > MAX_ARCHIVE_BYTES:
165
+ raise UiRuntimeError(f"UI wheel size mismatch: {filename}")
166
+ data = path.read_bytes()
167
+ if hashlib.sha256(data).hexdigest() != row.get("sha256"):
168
+ raise UiRuntimeError(f"UI wheel SHA256 mismatch: {filename}")
169
+ actual = wheel_metadata(filename, data)
170
+ if actual != row:
171
+ raise UiRuntimeError(f"UI wheel metadata differs from its manifest: {filename}")
172
+ if row["name"] in supplied or modules.intersection(row["modules"]):
173
+ raise UiRuntimeError(f"UI distributions overlap: {filename}")
174
+ supplied.add(row["name"])
175
+ filenames.add(filename)
176
+ modules.update(row["modules"])
177
+ with zipfile.ZipFile(io.BytesIO(data)) as archive:
178
+ names = {member.filename for member in _members(archive)}
179
+ if files.intersection(names):
180
+ raise UiRuntimeError(f"UI wheel paths overlap: {filename}")
181
+ files.update(names)
182
+ expanded += row["expanded_bytes"]
183
+ if expanded > MAX_EXPANDED_BYTES:
184
+ raise UiRuntimeError("UI runtime exceeds the extraction size limit")
185
+ archives.append((row, data))
186
+ if {path.name for path in bundle.iterdir() if path.name != "manifest.json"} != filenames:
187
+ raise UiRuntimeError("UI runtime directory differs from its exact wheel inventory")
188
+ if not any(f"{row['name']}=={row['version']}" == requirements for row in rows):
189
+ raise UiRuntimeError("UI runtime root Textual requirement is not present")
190
+ return manifest, archives
191
+
192
+
193
+ def runtime_inventory(repo: Path) -> dict[str, Any]:
194
+ """Report bundled hashes, purity, and licenses without extraction or UI imports."""
195
+ try:
196
+ manifest, _archives = _read_bundle(repo)
197
+ except (UiRuntimeError, OSError) as exc:
198
+ return {"schema_version": SCHEMA_VERSION, "status": "unavailable", "issues": [str(exc)], "packages": []}
199
+ return {**manifest, "status": "available", "issues": [],
200
+ "package_count": len(manifest["packages"]),
201
+ "size_bytes": sum(row["size_bytes"] for row in manifest["packages"]),
202
+ "expanded_bytes": sum(row["expanded_bytes"] for row in manifest["packages"]),
203
+ "license_file_count": sum(len(row["license_files"]) for row in manifest["packages"]),
204
+ "pure_python": True}
205
+
206
+
207
+ def _preloaded_modules(modules: set[str], active: Path | None) -> list[str]:
208
+ conflicts = []
209
+ for name, module in tuple(sys.modules.items()):
210
+ if name.split(".", 1)[0] not in modules:
211
+ continue
212
+ filename = getattr(module, "__file__", None)
213
+ if active is not None and isinstance(filename, str):
214
+ try:
215
+ Path(filename).resolve().relative_to(active)
216
+ continue
217
+ except (ValueError, OSError):
218
+ pass
219
+ conflicts.append(name)
220
+ return sorted(conflicts)
221
+
222
+
223
+ def _cleanup_runtime() -> None:
224
+ global _ACTIVE
225
+ if _ACTIVE is not None:
226
+ _bundle, temporary = _ACTIVE
227
+ sys.path[:] = [entry for entry in sys.path if entry != temporary.name]
228
+ temporary.cleanup()
229
+ _ACTIVE = None
230
+
231
+
232
+ def release_ui_runtime() -> None:
233
+ """Release owned files immediately before process handoff; UI reuse needs a fresh process."""
234
+ with _LOCK:
235
+ _cleanup_runtime()
236
+
237
+
238
+ def activate_ui_runtime(repo: Path) -> Path:
239
+ """Activate only verified shipped modules; imports must follow this call."""
240
+ global _ACTIVE
241
+ if sys.version_info < (3, 11):
242
+ raise UiRuntimeError("the bundled UI runtime requires Python 3.11 or newer")
243
+ with _LOCK:
244
+ manifest, archives = _read_bundle(repo)
245
+ if _ACTIVE is not None and _ACTIVE[0] != manifest["bundle_id"]:
246
+ raise UiRuntimeError("another UI bundle is active; start a fresh process for this agent-bios package")
247
+ active = Path(_ACTIVE[1].name).resolve() if _ACTIVE is not None else None
248
+ names = {name for row, _data in archives for name in row["modules"]}
249
+ conflicts = _preloaded_modules(names, active)
250
+ if conflicts:
251
+ raise UiRuntimeError("UI dependencies were imported before bundled activation: " + ", ".join(conflicts[:8])
252
+ + "; start a fresh process and activate the bundle before importing UI modules")
253
+ if active is not None:
254
+ if not active.is_dir():
255
+ raise UiRuntimeError("the active UI runtime directory is missing; start a fresh process")
256
+ return active
257
+ parent = Path(tempfile.gettempdir()).resolve()
258
+ home = Path.home().resolve()
259
+ if os.name != "nt" and (parent == home or home in parent.parents):
260
+ parent = Path("/tmp").resolve()
261
+ temporary = tempfile.TemporaryDirectory(prefix="agent-bios-ui-", dir=parent)
262
+ target = Path(temporary.name)
263
+ try:
264
+ for _row, data in archives:
265
+ with zipfile.ZipFile(io.BytesIO(data)) as archive:
266
+ for member in _members(archive):
267
+ path = target.joinpath(*PurePosixPath(member.filename).parts)
268
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
269
+ with path.open("xb") as output:
270
+ output.write(archive.read(member))
271
+ path.chmod(0o600)
272
+ sys.path.insert(0, str(target))
273
+ importlib.invalidate_caches()
274
+ _ACTIVE = (manifest["bundle_id"], temporary)
275
+ atexit.register(_cleanup_runtime)
276
+ return target
277
+ except BaseException:
278
+ temporary.cleanup()
279
+ raise