@agentikos/omega-os 0.19.42 → 0.19.44
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/bootstrap/lib/__pycache__/llm-clis.cpython-313.pyc +0 -0
- package/bootstrap/lib/common.sh +39 -9
- package/bootstrap/lib/llm-clis.py +6 -0
- package/bootstrap/lib/manifest-helpers.py +110 -0
- package/bootstrap/lib/steps.sh +253 -28
- package/bootstrap/templates/aisb/CLAUDE.md +13 -0
- package/install.sh +8 -2
- package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/hermes.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/personas.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +44 -7
- package/omega/Agentik_Engine/omega_engine/hermes.py +43 -1
- package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +22 -0
- package/omega/Agentik_Engine/omega_engine/personas.py +11 -3
- package/omega/Agentik_Engine/omega_engine/provider.py +18 -3
- package/omega/Agentik_Engine/omega_engine/tmux.py +41 -21
- package/omega/Agentik_Engine/omega_engine/tui.py +8 -7
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps_v0_19_43.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps_v0_19_43.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v19_43_fixes.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v19_43_fixes.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_install_steps_v0_19_43.py +242 -0
- package/omega/Agentik_Engine/tests/test_installer_wiring.py +128 -1
- package/omega/Agentik_Engine/tests/test_tmux_palette.py +51 -0
- package/omega/Agentik_Engine/tests/test_v19_43_fixes.py +265 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/omega/Agentik_SSOT/docs/AUDIT-V0.19.43.md +92 -0
- package/omega/Agentik_SSOT/rules/audit-gates.md +2 -2
- package/omega/Agentik_SSOT/rules/constitution.md +18 -0
- package/omega/Agentik_SSOT/rules/three-laws.md +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""v0.19.43 install-step gap tests.
|
|
2
|
+
|
|
3
|
+
Three system-dep regressions had bitten fresh-VPS installs:
|
|
4
|
+
|
|
5
|
+
1. Node.js + npm were never installed by step_system_deps, yet 7 of 13
|
|
6
|
+
supported LLM CLIs (claude_code, gemini_cli, codex, opencode,
|
|
7
|
+
qwen_code, continue_dev, gh_copilot) install via `npm -g`.
|
|
8
|
+
|
|
9
|
+
2. detect_os only matched apt-get and dnf on Linux — Arch (pacman),
|
|
10
|
+
Alpine (apk), and openSUSE/SLES (zypper) hosts hit
|
|
11
|
+
"no supported package manager" and aborted.
|
|
12
|
+
|
|
13
|
+
3. step_engine called `uv venv` with no `--python` pin, so on macOS
|
|
14
|
+
Tahoe the broken brew Python 3.14 (libexpat ABI mismatch) was
|
|
15
|
+
silently inherited and the engine venv failed at first import.
|
|
16
|
+
|
|
17
|
+
These tests grep the install scripts directly (no execve of the
|
|
18
|
+
installer) so they run on any host, run fast, and never touch real
|
|
19
|
+
package state.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
import unittest
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
28
|
+
COMMON_SH = REPO_ROOT / "bootstrap" / "lib" / "common.sh"
|
|
29
|
+
STEPS_SH = REPO_ROOT / "bootstrap" / "lib" / "steps.sh"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestNodeNpmInPkgLists(unittest.TestCase):
|
|
33
|
+
"""FIX 1 — every pkg-manager branch in step_system_deps must install
|
|
34
|
+
nodejs + npm so the npm-backed LLM CLIs work after a fresh install."""
|
|
35
|
+
|
|
36
|
+
def setUp(self) -> None:
|
|
37
|
+
self.text = STEPS_SH.read_text()
|
|
38
|
+
# Isolate the body of step_system_deps so we don't accidentally
|
|
39
|
+
# match nodejs/npm in some other helper (e.g. step_clis comments).
|
|
40
|
+
m = re.search(
|
|
41
|
+
r"step_system_deps\(\)\s*\{(.+?)^\}\s*$",
|
|
42
|
+
self.text, re.DOTALL | re.MULTILINE,
|
|
43
|
+
)
|
|
44
|
+
self.assertIsNotNone(m, "step_system_deps body not found in steps.sh")
|
|
45
|
+
self.body = m.group(1)
|
|
46
|
+
# The main package install case statement is the FIRST case block
|
|
47
|
+
# inside step_system_deps. The age install + tmux version check
|
|
48
|
+
# come AFTER it. We pin our assertions to the first case.
|
|
49
|
+
case_match = re.search(
|
|
50
|
+
r"case\s+\"\$OMEGA_PKG\"\s+in\s*\n(.*?)\s+esac",
|
|
51
|
+
self.body, re.DOTALL,
|
|
52
|
+
)
|
|
53
|
+
self.assertIsNotNone(case_match, "OMEGA_PKG case block not found")
|
|
54
|
+
self.case_block = case_match.group(1)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _branch_body(case_block: str, name: str) -> str | None:
|
|
58
|
+
"""Return the full body (possibly multi-line via `\\` continuation)
|
|
59
|
+
of a `name)` arm in a case statement, ending at `;;`."""
|
|
60
|
+
m = re.search(
|
|
61
|
+
rf"^\s*{name}\)(.*?);;", case_block,
|
|
62
|
+
re.MULTILINE | re.DOTALL,
|
|
63
|
+
)
|
|
64
|
+
return m.group(0) if m else None
|
|
65
|
+
|
|
66
|
+
def test_apt_installs_nodejs_npm(self):
|
|
67
|
+
line = self._branch_body(self.case_block, "apt")
|
|
68
|
+
self.assertIsNotNone(line, "apt branch missing")
|
|
69
|
+
self.assertIn("nodejs", line, f"apt: nodejs missing\n{line}")
|
|
70
|
+
self.assertIn("npm", line, f"apt: npm missing\n{line}")
|
|
71
|
+
|
|
72
|
+
def test_dnf_installs_nodejs_npm(self):
|
|
73
|
+
line = self._branch_body(self.case_block, "dnf")
|
|
74
|
+
self.assertIsNotNone(line, "dnf branch missing")
|
|
75
|
+
self.assertIn("nodejs", line)
|
|
76
|
+
self.assertIn("npm", line)
|
|
77
|
+
|
|
78
|
+
def test_brew_installs_node(self):
|
|
79
|
+
# On macOS Homebrew, `node` bundles npm — no separate `npm`
|
|
80
|
+
# formula. Asserting on the formula name keeps the test honest.
|
|
81
|
+
line = self._branch_body(self.case_block, "brew")
|
|
82
|
+
self.assertIsNotNone(line, "brew branch missing")
|
|
83
|
+
self.assertIn("node", line)
|
|
84
|
+
|
|
85
|
+
def test_pacman_installs_nodejs_npm(self):
|
|
86
|
+
line = self._branch_body(self.case_block, "pacman")
|
|
87
|
+
self.assertIsNotNone(line, "pacman branch missing — Arch/Manjaro not supported")
|
|
88
|
+
self.assertIn("nodejs", line)
|
|
89
|
+
self.assertIn("npm", line)
|
|
90
|
+
|
|
91
|
+
def test_apk_installs_nodejs_npm(self):
|
|
92
|
+
line = self._branch_body(self.case_block, "apk")
|
|
93
|
+
self.assertIsNotNone(line, "apk branch missing — Alpine not supported")
|
|
94
|
+
self.assertIn("nodejs", line)
|
|
95
|
+
self.assertIn("npm", line)
|
|
96
|
+
|
|
97
|
+
def test_zypper_installs_nodejs_npm(self):
|
|
98
|
+
line = self._branch_body(self.case_block, "zypper")
|
|
99
|
+
self.assertIsNotNone(line, "zypper branch missing — openSUSE/SLES not supported")
|
|
100
|
+
self.assertIn("nodejs", line)
|
|
101
|
+
self.assertIn("npm", line)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestDetectOsSupports5Managers(unittest.TestCase):
|
|
105
|
+
"""FIX 2 — common.sh::detect_os must probe all 5 Linux pkg managers
|
|
106
|
+
+ Darwin/brew before deciding 'unknown'."""
|
|
107
|
+
|
|
108
|
+
def test_detect_os_checks_5_pkg_managers(self):
|
|
109
|
+
text = COMMON_SH.read_text()
|
|
110
|
+
# Isolate the detect_os body.
|
|
111
|
+
m = re.search(
|
|
112
|
+
r"detect_os\(\)\s*\{(.+?)^\}\s*$",
|
|
113
|
+
text, re.DOTALL | re.MULTILINE,
|
|
114
|
+
)
|
|
115
|
+
self.assertIsNotNone(m, "detect_os body not found in common.sh")
|
|
116
|
+
body = m.group(1)
|
|
117
|
+
for cmd in ("apt-get", "dnf", "pacman", "apk", "zypper"):
|
|
118
|
+
self.assertIn(
|
|
119
|
+
f"have {cmd}", body,
|
|
120
|
+
f"detect_os does not probe '{cmd}' — distro will hit unknown",
|
|
121
|
+
)
|
|
122
|
+
# macOS still resolves to brew.
|
|
123
|
+
self.assertIn('OMEGA_PKG="brew"', body)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestStepEnginePinsPython313(unittest.TestCase):
|
|
127
|
+
"""FIX 3 — step_engine must request Python 3.13 from uv with a
|
|
128
|
+
graceful fallback so macOS Tahoe's broken brew Python 3.14 (libexpat
|
|
129
|
+
ABI mismatch) cannot silently break the engine venv."""
|
|
130
|
+
|
|
131
|
+
def test_step_engine_pins_python_3_13(self):
|
|
132
|
+
text = STEPS_SH.read_text()
|
|
133
|
+
m = re.search(
|
|
134
|
+
r"step_engine\(\)\s*\{(.+?)^\}\s*$",
|
|
135
|
+
text, re.DOTALL | re.MULTILINE,
|
|
136
|
+
)
|
|
137
|
+
self.assertIsNotNone(m, "step_engine body not found")
|
|
138
|
+
body = m.group(1)
|
|
139
|
+
self.assertIn(
|
|
140
|
+
"--python 3.13", body,
|
|
141
|
+
"step_engine no longer pins uv venv to Python 3.13 — "
|
|
142
|
+
"macOS Tahoe brew Python 3.14 will leak in again",
|
|
143
|
+
)
|
|
144
|
+
# And there must be a fallback to a bare `uv venv` so the install
|
|
145
|
+
# still completes when Python 3.13 cannot be procured.
|
|
146
|
+
self.assertIn("uv_bin", body)
|
|
147
|
+
self.assertRegex(
|
|
148
|
+
body, r'"\$uv_bin"\s+venv\s+(?:>/dev/null|--python|\s*\|\|)',
|
|
149
|
+
"expected at least one fallback `uv venv` call after the pinned attempt",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestTmuxVersionCheckPresent(unittest.TestCase):
|
|
154
|
+
"""FIX 4 — step_system_deps must warn (non-fatal) when tmux < 3.3,
|
|
155
|
+
because the bundled pro config uses 3.3+ syntax."""
|
|
156
|
+
|
|
157
|
+
def test_tmux_version_check_present(self):
|
|
158
|
+
text = STEPS_SH.read_text()
|
|
159
|
+
m = re.search(
|
|
160
|
+
r"step_system_deps\(\)\s*\{(.+?)^\}\s*$",
|
|
161
|
+
text, re.DOTALL | re.MULTILINE,
|
|
162
|
+
)
|
|
163
|
+
self.assertIsNotNone(m, "step_system_deps body not found")
|
|
164
|
+
body = m.group(1)
|
|
165
|
+
self.assertIn("tmux -V", body, "no tmux version detection")
|
|
166
|
+
# The warning must mention 3.3 explicitly so an operator can
|
|
167
|
+
# grep the install log and find it.
|
|
168
|
+
self.assertIn("3.3", body, "no mention of the 3.3 minimum in the warning")
|
|
169
|
+
# And it must use info/warn (non-fatal) — never `die`/`return 1`.
|
|
170
|
+
# We assert the check sits AFTER the package install and DOES
|
|
171
|
+
# NOT return non-zero.
|
|
172
|
+
tmux_block = re.search(
|
|
173
|
+
r"tmux -V.+?fi\s*\n\s*fi", body, re.DOTALL,
|
|
174
|
+
)
|
|
175
|
+
self.assertIsNotNone(tmux_block, "tmux version check block not delimited")
|
|
176
|
+
self.assertNotIn("return 1", tmux_block.group(0),
|
|
177
|
+
"tmux version check must be non-fatal")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestPkgNameTranslationForPacmanApkZypper(unittest.TestCase):
|
|
181
|
+
"""The 3 newly-supported managers use different package names for
|
|
182
|
+
whiptail (libnewt/newt) and python3-yaml (python-yaml/py3-yaml/
|
|
183
|
+
python3-PyYAML). This test pins those translations so a future edit
|
|
184
|
+
cannot silently drop them."""
|
|
185
|
+
|
|
186
|
+
def setUp(self) -> None:
|
|
187
|
+
text = STEPS_SH.read_text()
|
|
188
|
+
m = re.search(
|
|
189
|
+
r"step_system_deps\(\)\s*\{(.+?)^\}\s*$",
|
|
190
|
+
text, re.DOTALL | re.MULTILINE,
|
|
191
|
+
)
|
|
192
|
+
self.assertIsNotNone(m)
|
|
193
|
+
body = m.group(1)
|
|
194
|
+
case_match = re.search(
|
|
195
|
+
r"case\s+\"\$OMEGA_PKG\"\s+in\s*\n(.*?)\s+esac",
|
|
196
|
+
body, re.DOTALL,
|
|
197
|
+
)
|
|
198
|
+
self.assertIsNotNone(case_match)
|
|
199
|
+
self.case_block = case_match.group(1)
|
|
200
|
+
|
|
201
|
+
def _branch(self, name: str) -> str:
|
|
202
|
+
m = re.search(
|
|
203
|
+
rf"^\s*{name}\)(.*?);;", self.case_block,
|
|
204
|
+
re.MULTILINE | re.DOTALL,
|
|
205
|
+
)
|
|
206
|
+
self.assertIsNotNone(m, f"{name} branch not found in case block")
|
|
207
|
+
return m.group(0)
|
|
208
|
+
|
|
209
|
+
def test_pacman_uses_libnewt_and_python_yaml(self):
|
|
210
|
+
line = self._branch("pacman")
|
|
211
|
+
# Arch packages whiptail's library as `libnewt`, not `whiptail`
|
|
212
|
+
# or `newt` — passing `whiptail` would 404.
|
|
213
|
+
self.assertIn("libnewt", line, "pacman: must use libnewt, not whiptail/newt")
|
|
214
|
+
# Arch ships PyYAML as `python-yaml`.
|
|
215
|
+
self.assertIn("python-yaml", line, "pacman: must use python-yaml")
|
|
216
|
+
# And it must NOT carry the apt/dnf name through.
|
|
217
|
+
self.assertNotIn("whiptail", line)
|
|
218
|
+
self.assertNotIn("python3-yaml", line)
|
|
219
|
+
self.assertNotIn("python3-pyyaml", line)
|
|
220
|
+
|
|
221
|
+
def test_apk_uses_newt_and_py3_yaml(self):
|
|
222
|
+
line = self._branch("apk")
|
|
223
|
+
# Alpine packages whiptail as `newt`.
|
|
224
|
+
self.assertIn("newt", line, "apk: must use newt")
|
|
225
|
+
# Alpine ships PyYAML as `py3-yaml`.
|
|
226
|
+
self.assertIn("py3-yaml", line, "apk: must use py3-yaml")
|
|
227
|
+
self.assertNotIn("whiptail", line)
|
|
228
|
+
self.assertNotIn("python3-yaml", line)
|
|
229
|
+
self.assertNotIn("python3-pyyaml", line)
|
|
230
|
+
|
|
231
|
+
def test_zypper_uses_newt_and_python3_PyYAML(self):
|
|
232
|
+
line = self._branch("zypper")
|
|
233
|
+
# openSUSE packages whiptail's library as `newt`.
|
|
234
|
+
self.assertIn("newt", line, "zypper: must use newt")
|
|
235
|
+
# openSUSE ships PyYAML with the canonical PyYAML capitalisation.
|
|
236
|
+
self.assertIn("python3-PyYAML", line, "zypper: must use python3-PyYAML")
|
|
237
|
+
self.assertNotIn("whiptail", line)
|
|
238
|
+
self.assertNotIn("python-yaml ", line) # trailing space avoids matching python3-PyYAML
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
unittest.main(verbosity=2)
|
|
@@ -49,7 +49,15 @@ class TestInstallerStepList(unittest.TestCase):
|
|
|
49
49
|
("36-tmux-config", "step_tmux_config"),
|
|
50
50
|
("37-hermes-brief", "step_hermes_brief"),
|
|
51
51
|
("38-personas", "step_personas"),
|
|
52
|
-
("
|
|
52
|
+
("47-paperclip", "step_paperclip"),
|
|
53
|
+
# v0.19.43 — split the misnamed 59-hermes-session into two
|
|
54
|
+
# distinct steps. 59-aisb-session spawns the always-on AISB
|
|
55
|
+
# master chat window (was the actual behaviour of the old
|
|
56
|
+
# 59-hermes-session, despite the misleading name);
|
|
57
|
+
# 60-hermes-session is NEW and conditionally spawns the Hermès
|
|
58
|
+
# chat when the vault key is present.
|
|
59
|
+
("59-aisb-session", "step_aisb_session"),
|
|
60
|
+
("60-hermes-session", "step_hermes_session"),
|
|
53
61
|
]
|
|
54
62
|
|
|
55
63
|
def setUp(self):
|
|
@@ -86,6 +94,48 @@ class TestInstallerStepList(unittest.TestCase):
|
|
|
86
94
|
"step_engine must NOT call write_default_config directly anymore "
|
|
87
95
|
"— that's step_tmux_config's job")
|
|
88
96
|
|
|
97
|
+
def test_step_tmux_config_wires_omega_addon_after_upstream(self):
|
|
98
|
+
"""v0.19.43 — step_tmux_config MUST call install_into_home_tmux_conf
|
|
99
|
+
AFTER the upstream tmux-claude installer succeeds (or detects an
|
|
100
|
+
existing install). Otherwise the user's ~/.tmux.conf is tmux-claude's
|
|
101
|
+
with NO `source-file` line for the OmegaOS add-on, so the Alt+O bind
|
|
102
|
+
is never sourced. The fallback path already wires it; this guards the
|
|
103
|
+
happy path."""
|
|
104
|
+
m = re.search(r"^step_tmux_config\(\) \{(.+?)^\}", self.steps_text,
|
|
105
|
+
re.DOTALL | re.MULTILINE)
|
|
106
|
+
self.assertIsNotNone(m, "step_tmux_config function not found")
|
|
107
|
+
body = m.group(1)
|
|
108
|
+
self.assertIn("install_into_home_tmux_conf", body,
|
|
109
|
+
"step_tmux_config must call install_into_home_tmux_conf to wire "
|
|
110
|
+
"the Omega add-on (Alt+O bind) into ~/.tmux.conf even when the "
|
|
111
|
+
"upstream tmux-claude installer succeeded on the happy path")
|
|
112
|
+
|
|
113
|
+
def test_step_aisb_session_and_step_hermes_session_distinct(self):
|
|
114
|
+
"""v0.19.43 — the misnamed step_hermes_session (which actually
|
|
115
|
+
spawned AISB) was split into step_aisb_session + a new real
|
|
116
|
+
step_hermes_session. Both must exist; step_aisb_session must call
|
|
117
|
+
spawn_aisb_chat; step_hermes_session must call spawn_hermes_chat."""
|
|
118
|
+
# step_aisb_session body must call spawn_aisb_chat
|
|
119
|
+
m_aisb = re.search(r"^step_aisb_session\(\) \{(.+?)^\}",
|
|
120
|
+
self.steps_text, re.DOTALL | re.MULTILINE)
|
|
121
|
+
self.assertIsNotNone(m_aisb,
|
|
122
|
+
"step_aisb_session function not found (rename from "
|
|
123
|
+
"step_hermes_session in v0.19.43)")
|
|
124
|
+
self.assertIn("spawn_aisb_chat", m_aisb.group(1),
|
|
125
|
+
"step_aisb_session must call spawn_aisb_chat")
|
|
126
|
+
# step_hermes_session body must call spawn_hermes_chat AND gate
|
|
127
|
+
# on the vault key (no fallback to Max OAuth for the 24/7 chat).
|
|
128
|
+
m_h = re.search(r"^step_hermes_session\(\) \{(.+?)^\}",
|
|
129
|
+
self.steps_text, re.DOTALL | re.MULTILINE)
|
|
130
|
+
self.assertIsNotNone(m_h,
|
|
131
|
+
"step_hermes_session function not found (new in v0.19.43)")
|
|
132
|
+
h_body = m_h.group(1)
|
|
133
|
+
self.assertIn("spawn_hermes_chat", h_body,
|
|
134
|
+
"step_hermes_session must call spawn_hermes_chat")
|
|
135
|
+
self.assertIn("ANTHROPIC_API_KEY_HERMES", h_body,
|
|
136
|
+
"step_hermes_session must gate the spawn on the vault key — "
|
|
137
|
+
"no silent fallback to Max OAuth for the 24/7 chat")
|
|
138
|
+
|
|
89
139
|
|
|
90
140
|
# ---------------------------------------------------------------------------
|
|
91
141
|
# Step semantics — exercise the same Python the heredocs run, against a
|
|
@@ -627,5 +677,82 @@ class TestDashDashSeparatorAccepted(unittest.TestCase):
|
|
|
627
677
|
"not found")
|
|
628
678
|
|
|
629
679
|
|
|
680
|
+
class TestStepPaperclipBehavior(unittest.TestCase):
|
|
681
|
+
"""v0.19.43 — step_paperclip auto-registers OmegaOS as the 'omegaos'
|
|
682
|
+
company with Paperclip at install time. Without this, the user must
|
|
683
|
+
run `omega paperclip register` by hand or Paperclip never sees OmegaOS."""
|
|
684
|
+
|
|
685
|
+
def test_paperclip_registers_14_agents_on_install(self):
|
|
686
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
687
|
+
home = Path(tmp) / "Omega"
|
|
688
|
+
home.mkdir()
|
|
689
|
+
pc_home = Path(tmp) / "paperclip"
|
|
690
|
+
# Deploy engine code.
|
|
691
|
+
engine_src = REPO_ROOT / "omega" / "Agentik_Engine"
|
|
692
|
+
engine_dst = home / "Agentik_Engine"
|
|
693
|
+
engine_dst.mkdir()
|
|
694
|
+
import shutil
|
|
695
|
+
for entry in engine_src.iterdir():
|
|
696
|
+
if entry.name in {"tests", "__pycache__"}:
|
|
697
|
+
continue
|
|
698
|
+
if entry.is_dir():
|
|
699
|
+
shutil.copytree(entry, engine_dst / entry.name)
|
|
700
|
+
else:
|
|
701
|
+
(engine_dst / entry.name).write_bytes(entry.read_bytes())
|
|
702
|
+
|
|
703
|
+
old_home = os.environ.get("OMEGA_HOME")
|
|
704
|
+
old_pc = os.environ.get("PAPERCLIP_HOME")
|
|
705
|
+
os.environ["OMEGA_HOME"] = str(home)
|
|
706
|
+
os.environ["PAPERCLIP_HOME"] = str(pc_home)
|
|
707
|
+
sys.path.insert(0, str(engine_dst))
|
|
708
|
+
try:
|
|
709
|
+
import importlib
|
|
710
|
+
import omega_engine.paperclip_bridge as PB
|
|
711
|
+
importlib.reload(PB)
|
|
712
|
+
result = PB.register()
|
|
713
|
+
self.assertEqual(result.get("agents_written"), 14,
|
|
714
|
+
f"register() must write 14 agents (Hermès + 13 AISB); "
|
|
715
|
+
f"got {result.get('agents_written')}")
|
|
716
|
+
# Verify files on disk.
|
|
717
|
+
company_dir = pc_home / "companies" / "omegaos"
|
|
718
|
+
self.assertTrue(company_dir.exists(),
|
|
719
|
+
f"company dir must land at {company_dir}")
|
|
720
|
+
self.assertTrue((company_dir / "company.json").exists())
|
|
721
|
+
agents_dir = company_dir / "agents"
|
|
722
|
+
self.assertTrue(agents_dir.exists())
|
|
723
|
+
json_files = list(agents_dir.glob("*.json"))
|
|
724
|
+
self.assertEqual(len(json_files), 14,
|
|
725
|
+
f"expected 14 agent JSON files, found {len(json_files)}")
|
|
726
|
+
finally:
|
|
727
|
+
if old_home is None:
|
|
728
|
+
os.environ.pop("OMEGA_HOME", None)
|
|
729
|
+
else:
|
|
730
|
+
os.environ["OMEGA_HOME"] = old_home
|
|
731
|
+
if old_pc is None:
|
|
732
|
+
os.environ.pop("PAPERCLIP_HOME", None)
|
|
733
|
+
else:
|
|
734
|
+
os.environ["PAPERCLIP_HOME"] = old_pc
|
|
735
|
+
sys.path.remove(str(engine_dst))
|
|
736
|
+
|
|
737
|
+
def test_step_paperclip_heredoc_calls_register(self):
|
|
738
|
+
"""Lock in that the heredoc body calls paperclip_bridge.register()."""
|
|
739
|
+
body = STEPS_SH.read_text()
|
|
740
|
+
m = re.search(r"^step_paperclip\(\) \{(.+?)^\}", body,
|
|
741
|
+
re.DOTALL | re.MULTILINE)
|
|
742
|
+
self.assertIsNotNone(m, "step_paperclip function not found in steps.sh")
|
|
743
|
+
fn_body = m.group(1)
|
|
744
|
+
self.assertIn("paperclip_bridge", fn_body,
|
|
745
|
+
"step_paperclip must import paperclip_bridge module")
|
|
746
|
+
self.assertIn("register()", fn_body,
|
|
747
|
+
"step_paperclip must call PB.register()")
|
|
748
|
+
|
|
749
|
+
def test_heartbeat_on_spawn_is_silent_on_failure(self):
|
|
750
|
+
"""Even if PAPERCLIP_HOME is unwritable, heartbeat_on_spawn must
|
|
751
|
+
never raise — it's optional infrastructure."""
|
|
752
|
+
from omega_engine.paperclip_bridge import heartbeat_on_spawn
|
|
753
|
+
# Should not raise.
|
|
754
|
+
heartbeat_on_spawn("nobody@test", project="nothing")
|
|
755
|
+
|
|
756
|
+
|
|
630
757
|
if __name__ == "__main__":
|
|
631
758
|
unittest.main()
|
|
@@ -196,6 +196,57 @@ class TestTmuxBindsFixedAndPathsAbsolute(unittest.TestCase):
|
|
|
196
196
|
self.assertNotIn("session-manager-v2.sh", home_conf.read_text(),
|
|
197
197
|
"force=True must clobber tmux-claude")
|
|
198
198
|
|
|
199
|
+
# v0.19.43 — spawn_aisb_chat + spawn_hermes_chat must use the
|
|
200
|
+
# WINDOW-under-Omega pattern (spawn_chat_in_omega), not the standalone
|
|
201
|
+
# session pattern (_spawn_with_shell_then_run). This pins the v0.19.31+
|
|
202
|
+
# design promise: everything under Omega.
|
|
203
|
+
|
|
204
|
+
def test_spawn_aisb_chat_uses_window_pattern(self):
|
|
205
|
+
"""spawn_aisb_chat must use spawn_chat_in_omega (window under Omega),
|
|
206
|
+
not _spawn_with_shell_then_run (standalone session)."""
|
|
207
|
+
import inspect
|
|
208
|
+
from omega_engine.tmux import spawn_aisb_chat
|
|
209
|
+
src = inspect.getsource(spawn_aisb_chat)
|
|
210
|
+
self.assertIn("spawn_chat_in_omega", src,
|
|
211
|
+
"spawn_aisb_chat must use the window-based pattern, not a "
|
|
212
|
+
"standalone session (the v0.19.31+ design promise)")
|
|
213
|
+
self.assertNotIn("_spawn_with_shell_then_run", src,
|
|
214
|
+
"spawn_aisb_chat must NOT use the standalone-session helper "
|
|
215
|
+
"anymore — refactor to spawn_chat_in_omega")
|
|
216
|
+
|
|
217
|
+
def test_spawn_hermes_chat_uses_window_pattern(self):
|
|
218
|
+
import inspect
|
|
219
|
+
from omega_engine.tmux import spawn_hermes_chat
|
|
220
|
+
src = inspect.getsource(spawn_hermes_chat)
|
|
221
|
+
self.assertIn("spawn_chat_in_omega", src,
|
|
222
|
+
"spawn_hermes_chat must use the window-based pattern")
|
|
223
|
+
self.assertNotIn("_spawn_with_shell_then_run", src,
|
|
224
|
+
"spawn_hermes_chat must NOT use the standalone-session helper "
|
|
225
|
+
"anymore — refactor to spawn_chat_in_omega")
|
|
226
|
+
# Credential isolation MUST be preserved — Hermès still reads the
|
|
227
|
+
# vault key directly so its budget stays separate from Max OAuth.
|
|
228
|
+
self.assertIn("ANTHROPIC_API_KEY_HERMES", src,
|
|
229
|
+
"spawn_hermes_chat must still read the vault key — isolation "
|
|
230
|
+
"from Max OAuth is mandatory (see omega_engine/hermes.py docs)")
|
|
231
|
+
|
|
232
|
+
def test_spawn_aisb_chat_returns_omega_window_ref(self):
|
|
233
|
+
"""The return value contract changed from 'AISB-chat' (standalone
|
|
234
|
+
session) to 'Omega:aisb' (window under Omega). Callers in cli.py
|
|
235
|
+
+ tui.py rely on this for tmux switch-client targets."""
|
|
236
|
+
import inspect
|
|
237
|
+
from omega_engine.tmux import spawn_aisb_chat
|
|
238
|
+
src = inspect.getsource(spawn_aisb_chat)
|
|
239
|
+
self.assertIn('"Omega:aisb"', src,
|
|
240
|
+
"spawn_aisb_chat must return 'Omega:aisb' so callers can pass "
|
|
241
|
+
"it directly to `tmux switch-client -t`")
|
|
242
|
+
|
|
243
|
+
def test_spawn_hermes_chat_returns_omega_window_ref(self):
|
|
244
|
+
import inspect
|
|
245
|
+
from omega_engine.tmux import spawn_hermes_chat
|
|
246
|
+
src = inspect.getsource(spawn_hermes_chat)
|
|
247
|
+
self.assertIn('"Omega:hermes"', src,
|
|
248
|
+
"spawn_hermes_chat must return 'Omega:hermes'")
|
|
249
|
+
|
|
199
250
|
|
|
200
251
|
if __name__ == "__main__":
|
|
201
252
|
unittest.main(verbosity=2)
|