agent-bios 0.19.2 → 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 (46) hide show
  1. package/DEPENDENCIES.md +32 -4
  2. package/README.md +54 -4
  3. package/claude/guides/cli-multi-model-workflow.md +1 -1
  4. package/claude/guides/coding-staged-workflow.md +17 -0
  5. package/claude/guides/llm-capability-boundary.md +7 -1
  6. package/claude/guides/tooling-gotchas.md +20 -1
  7. package/claude/guides/ui-design/visual-direction.md +88 -0
  8. package/claude/guides/ui-design.md +90 -0
  9. package/claude/guides/verification-discipline.md +10 -1
  10. package/claude/hooks/tooling-gotchas-hook.py +41 -0
  11. package/codex/guides/cli-multi-model-workflow.md +1 -1
  12. package/codex/guides/coding-staged-workflow.md +17 -0
  13. package/codex/guides/llm-capability-boundary.md +7 -1
  14. package/codex/guides/tooling-gotchas.md +20 -1
  15. package/codex/guides/ui-design/visual-direction.md +88 -0
  16. package/codex/guides/ui-design.md +90 -0
  17. package/codex/guides/verification-discipline.md +10 -1
  18. package/compose/app_bridge/scripts/bridge.py +23 -6
  19. package/compose/app_desktop/server.py +250 -0
  20. package/compose/domains.json +1 -0
  21. package/compose/host_platform.py +121 -0
  22. package/compose/instructions-state.py +5 -2
  23. package/compose/instructions_app.py +281 -48
  24. package/compose/instructions_catalog.py +2 -2
  25. package/compose/instructions_import.py +19 -11
  26. package/compose/instructions_install.py +77 -11
  27. package/compose/instructions_session.py +6 -6
  28. package/compose/instructions_setup.py +41 -4
  29. package/compose/instructions_setup_cli.py +31 -10
  30. package/compose/instructions_setup_i18n.py +3 -0
  31. package/compose/instructions_store.py +6 -6
  32. package/compose/instructions_transaction.py +8 -6
  33. package/compose/instructions_ui_runtime.py +2 -1
  34. package/compose/native_cli.py +52 -0
  35. package/compose/runtime_entry.py +58 -0
  36. package/compose/windows_deploy.py +719 -0
  37. package/docs/instructions.md +1 -0
  38. package/docs/releases/0.19.3.md +107 -0
  39. package/docs/session-model.md +8 -0
  40. package/docs/setup.md +36 -0
  41. package/docs/windows.md +99 -0
  42. package/install.sh +1 -0
  43. package/launch/agent-launch.py +12 -4
  44. package/launch/agent-launch.zsh +11 -1
  45. package/package.json +8 -3
  46. package/provenance.json +1 -1
@@ -0,0 +1,121 @@
1
+ """Small OS boundary shared by the native entrypoint, installer and launcher."""
2
+ from __future__ import annotations
3
+ import errno
4
+ import os
5
+ from pathlib import Path
6
+ import subprocess
7
+ import sys
8
+ import time
9
+
10
+ WINDOWS = os.name == 'nt'
11
+
12
+ if not WINDOWS:
13
+ import fcntl as file_locks
14
+ else:
15
+ import msvcrt
16
+
17
+ class file_locks:
18
+ LOCK_EX, LOCK_NB, LOCK_UN = 2, 4, 8
19
+
20
+ @staticmethod
21
+ def flock(file, operation):
22
+ fd = file if isinstance(file, int) else file.fileno()
23
+ position = os.lseek(fd, 0, os.SEEK_CUR)
24
+ try:
25
+ while True:
26
+ os.lseek(fd, 0, os.SEEK_SET)
27
+ try:
28
+ msvcrt.locking(fd, msvcrt.LK_UNLCK if operation & 8 else msvcrt.LK_NBLCK, 1)
29
+ return
30
+ except OSError as exc:
31
+ if operation & 8 or exc.errno not in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
32
+ raise
33
+ if operation & 4:
34
+ raise BlockingIOError(exc.errno, str(exc)) from exc
35
+ time.sleep(.05)
36
+ finally:
37
+ os.lseek(fd, position, os.SEEK_SET)
38
+
39
+
40
+ def cli_argv(root: Path, *args: str) -> list[str]:
41
+ if WINDOWS:
42
+ return python_argv(root / 'compose/native_cli.py', *args)
43
+ return ['/bin/bash', str(root / 'install.sh'), *args]
44
+
45
+
46
+ def runtime_environment(environ=None) -> dict[str, str]:
47
+ env = os.environ if environ is None else environ
48
+ entry, deps = env.get('AGENT_BIOS_PYTHON_ENTRY'), env.get('AGENT_BIOS_PYTHON_DEPS')
49
+ if bool(entry) != bool(deps):
50
+ raise RuntimeError('application Python entry and dependencies must be bound together')
51
+ if not entry:
52
+ return {}
53
+ if not Path(entry).is_absolute() or not Path(entry).is_file() or not Path(deps).is_absolute() or not Path(deps).is_dir():
54
+ raise RuntimeError('application Python binding is unavailable; repair the script installation')
55
+ executable = env.get('AGENT_BIOS_PYTHON_EXECUTABLE', sys.executable)
56
+ if not Path(executable).is_absolute() or not Path(executable).is_file():
57
+ raise RuntimeError('bound Python interpreter is unavailable')
58
+ return {'AGENT_BIOS_PYTHON_ENTRY': str(entry), 'AGENT_BIOS_PYTHON_DEPS': str(deps), 'AGENT_BIOS_PYTHON_EXECUTABLE': str(executable)}
59
+
60
+
61
+ def python_argv(script: Path, *args: str, interpreter=None, environ=None) -> list[str]:
62
+ binding = runtime_environment(environ)
63
+ executable = str(interpreter or binding.get("AGENT_BIOS_PYTHON_EXECUTABLE") or sys.executable)
64
+ if binding:
65
+ return [executable, '-I', '-X', 'utf8', binding['AGENT_BIOS_PYTHON_ENTRY'],
66
+ '--dependencies', binding['AGENT_BIOS_PYTHON_DEPS'], '--script', str(script), *args]
67
+ return [executable, str(script), *args]
68
+
69
+
70
+ def sync_directory(path: Path) -> None:
71
+ # Windows CRT cannot open directory descriptors. File bytes are flushed by
72
+ # the writer; replace/restart tests establish process recovery, not power-loss durability.
73
+ if WINDOWS:
74
+ return
75
+ fd = os.open(path, os.O_RDONLY)
76
+ try:
77
+ os.fsync(fd)
78
+ finally:
79
+ os.close(fd)
80
+
81
+
82
+ def redirected(path: Path) -> bool:
83
+ return path.is_symlink() or (hasattr(path, 'is_junction') and path.is_junction())
84
+
85
+
86
+ def create_junction(link: Path, target: Path) -> None:
87
+ """Create a directory junction without shell expansion or symlink privileges."""
88
+ if not WINDOWS:
89
+ raise OSError('junction creation is Windows-only')
90
+ import ctypes
91
+ from ctypes import wintypes
92
+ import struct
93
+ kernel = ctypes.WinDLL('kernel32', use_last_error=True)
94
+ kernel.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, ctypes.c_void_p, wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE]
95
+ kernel.CreateFileW.restype = wintypes.HANDLE
96
+ kernel.DeviceIoControl.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.c_void_p, wintypes.DWORD, ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), ctypes.c_void_p]
97
+ kernel.CloseHandle.argtypes = [wintypes.HANDLE]
98
+ target = target.resolve(strict=True)
99
+ substitute = ('\\??\\' + str(target)).encode('utf-16-le')
100
+ display = str(target).encode('utf-16-le')
101
+ names = substitute + b'\0\0' + display + b'\0\0'
102
+ payload = struct.pack('<HHHH', 0, len(substitute), len(substitute)+2, len(display)) + names
103
+ data = struct.pack('<IHH', 0xA0000003, len(payload), 0) + payload
104
+ link.mkdir()
105
+ handle = kernel.CreateFileW(str(link), 0x40000000, 0, None, 3, 0x02200000, None)
106
+ try:
107
+ if handle == ctypes.c_void_p(-1).value:
108
+ raise ctypes.WinError(ctypes.get_last_error())
109
+ returned = wintypes.DWORD()
110
+ buffer = ctypes.create_string_buffer(data)
111
+ if not kernel.DeviceIoControl(handle, 0x000900A4, buffer, len(data), None, 0, ctypes.byref(returned), None):
112
+ raise ctypes.WinError(ctypes.get_last_error())
113
+ except BaseException:
114
+ if handle != ctypes.c_void_p(-1).value:
115
+ kernel.CloseHandle(handle)
116
+ handle = ctypes.c_void_p(-1).value
117
+ link.rmdir()
118
+ raise
119
+ finally:
120
+ if handle != ctypes.c_void_p(-1).value:
121
+ kernel.CloseHandle(handle)
@@ -21,7 +21,10 @@ import datetime
21
21
  import io
22
22
  import json
23
23
  import contextlib
24
- import fcntl
24
+ try:
25
+ from host_platform import file_locks as fcntl
26
+ except ImportError:
27
+ from .host_platform import file_locks as fcntl
25
28
  import os
26
29
  import pathlib
27
30
  import shutil
@@ -561,7 +564,7 @@ def _flock(lock_path: pathlib.Path):
561
564
  lock_path.parent.mkdir(parents=True, exist_ok=True)
562
565
  # O_NOFOLLOW and no truncation: a "w" open follows a planted symlink and
563
566
  # truncates its target merely by running the command.
564
- fd = os.open(str(lock_path), os.O_CREAT | os.O_WRONLY | os.O_NOFOLLOW, 0o600)
567
+ fd = os.open(str(lock_path), os.O_CREAT | os.O_WRONLY | getattr(os, "O_NOFOLLOW", 0), 0o600)
565
568
  with os.fdopen(fd, "w") as handle:
566
569
  fcntl.flock(handle, fcntl.LOCK_EX)
567
570
  yield