@agentikos/omega-os 0.19.37 → 0.19.39
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/bin/omega-os.js +6 -1
- package/bootstrap/lib/steps.sh +43 -0
- package/install.sh +5 -0
- 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__/paperclip_bridge.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/prompt_audit.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 +73 -0
- package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +110 -0
- package/omega/Agentik_Engine/omega_engine/prompt_audit.py +395 -0
- package/omega/Agentik_Engine/omega_engine/tmux.py +16 -0
- package/omega/Agentik_Engine/omega_engine/tui.py +269 -67
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- 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_paperclip_status.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_installer_wiring.py +130 -0
- package/omega/Agentik_Engine/tests/test_paperclip_status.py +142 -0
- package/omega/Agentik_Engine/tests/test_prompt_audit.py +199 -0
- package/omega/Agentik_Engine/tests/test_tui_runtime.py +106 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/omega/Agentik_SSOT/docs/AUDIT-V0.19.38.md +90 -0
- package/omega/Agentik_SSOT/docs/AUDIT-V0.19.39.md +161 -0
- package/omega/Agentik_SSOT/rules/audit-gates.md +189 -0
- package/omega/Agentik_SSOT/rules/constitution.md +7 -0
- package/omega/Agentik_SSOT/rules/orchestration.md +215 -0
- package/omega/Agentik_SSOT/rules/prompt-protocols.md +219 -0
- package/omega/Agentik_SSOT/rules/scope-safety.md +197 -0
- package/omega/Agentik_SSOT/rules/three-laws.md +214 -0
- package/omega/Agentik_SSOT/rules/verified-completion.md +216 -0
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Tests for omega_engine.paperclip_bridge.is_running().
|
|
2
|
+
|
|
3
|
+
Locks in the live status probe contract so the TUI can render ●/○ next to
|
|
4
|
+
the Paperclip menu items. Five detection paths covered:
|
|
5
|
+
|
|
6
|
+
1. Empty PAPERCLIP_HOME → not running (detection="none")
|
|
7
|
+
2. Stale pidfile → not running
|
|
8
|
+
3. Live pidfile (self PID) → running (detection="pidfile")
|
|
9
|
+
4. No pidfile + open socket → running (detection="port-scan")
|
|
10
|
+
5. Running case sets url → http://localhost:<port>
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import socket
|
|
16
|
+
import tempfile
|
|
17
|
+
import unittest
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from unittest import mock
|
|
20
|
+
|
|
21
|
+
from omega_engine import paperclip_bridge as P
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _free_port() -> int:
|
|
25
|
+
"""Allocate (and release) an ephemeral TCP port."""
|
|
26
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
27
|
+
s.bind(("127.0.0.1", 0))
|
|
28
|
+
return s.getsockname()[1]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestIsRunningDetectionPaths(unittest.TestCase):
|
|
32
|
+
"""Each of the three detection paths plus the URL invariant."""
|
|
33
|
+
|
|
34
|
+
def test_no_pidfile_no_port_returns_not_running(self):
|
|
35
|
+
"""Empty PAPERCLIP_HOME + closed port → running=False, detection=none."""
|
|
36
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
37
|
+
with mock.patch.dict(os.environ, {"PAPERCLIP_HOME": tmp}, clear=False):
|
|
38
|
+
# Pick a port nothing is listening on (allocate then release).
|
|
39
|
+
port = _free_port()
|
|
40
|
+
status = P.is_running(port=port)
|
|
41
|
+
self.assertFalse(status.running)
|
|
42
|
+
self.assertEqual(status.detection, "none")
|
|
43
|
+
self.assertIsNone(status.pid)
|
|
44
|
+
self.assertIsNone(status.port)
|
|
45
|
+
self.assertIsNone(status.url)
|
|
46
|
+
|
|
47
|
+
def test_stale_pidfile_returns_not_running(self):
|
|
48
|
+
"""Pidfile points at a dead PID → running=False (falls through to port,
|
|
49
|
+
which is also closed in this test)."""
|
|
50
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
51
|
+
with mock.patch.dict(os.environ, {"PAPERCLIP_HOME": tmp}, clear=False):
|
|
52
|
+
run_dir = Path(tmp) / "run"
|
|
53
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
# 99999999 is virtually guaranteed to be unused on Linux
|
|
55
|
+
# (default PID max is 4194304); we also mock os.kill to be
|
|
56
|
+
# sure across any host's PID-recycling weirdness.
|
|
57
|
+
(run_dir / "dashboard.pid").write_text("99999999\n")
|
|
58
|
+
|
|
59
|
+
def fake_kill(pid: int, sig: int) -> None:
|
|
60
|
+
if pid == 99999999:
|
|
61
|
+
raise ProcessLookupError(pid)
|
|
62
|
+
# delegate to the real os.kill for any other pid
|
|
63
|
+
return os._real_kill(pid, sig) # pragma: no cover
|
|
64
|
+
|
|
65
|
+
# Stash the real kill (in case fake_kill ever delegates).
|
|
66
|
+
os._real_kill = os.kill # type: ignore[attr-defined]
|
|
67
|
+
try:
|
|
68
|
+
port = _free_port()
|
|
69
|
+
with mock.patch("omega_engine.paperclip_bridge.os.kill",
|
|
70
|
+
side_effect=fake_kill):
|
|
71
|
+
status = P.is_running(port=port)
|
|
72
|
+
finally:
|
|
73
|
+
del os._real_kill # type: ignore[attr-defined]
|
|
74
|
+
self.assertFalse(status.running)
|
|
75
|
+
self.assertEqual(status.detection, "none")
|
|
76
|
+
self.assertIsNone(status.pid)
|
|
77
|
+
|
|
78
|
+
def test_live_pidfile_returns_running(self):
|
|
79
|
+
"""Pidfile points at this test process (alive) → running=True,
|
|
80
|
+
detection='pidfile', does NOT touch the network."""
|
|
81
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
82
|
+
with mock.patch.dict(os.environ, {"PAPERCLIP_HOME": tmp}, clear=False):
|
|
83
|
+
run_dir = Path(tmp) / "run"
|
|
84
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
self_pid = os.getpid()
|
|
86
|
+
(run_dir / "dashboard.pid").write_text(f"{self_pid}\n")
|
|
87
|
+
# Patch socket so we'd notice if the code accidentally tried
|
|
88
|
+
# to connect (it shouldn't — pidfile hit returns immediately).
|
|
89
|
+
with mock.patch("omega_engine.paperclip_bridge.socket.socket") \
|
|
90
|
+
as sock_cls:
|
|
91
|
+
status = P.is_running(port=8080)
|
|
92
|
+
sock_cls.assert_not_called()
|
|
93
|
+
self.assertTrue(status.running)
|
|
94
|
+
self.assertEqual(status.detection, "pidfile")
|
|
95
|
+
self.assertEqual(status.pid, self_pid)
|
|
96
|
+
self.assertEqual(status.port, 8080)
|
|
97
|
+
|
|
98
|
+
def test_port_scan_fallback_running(self):
|
|
99
|
+
"""No pidfile, but a socket IS listening on the probe port →
|
|
100
|
+
running=True, detection='port-scan', pid=None."""
|
|
101
|
+
# Bind a real listener on an ephemeral port for the duration of
|
|
102
|
+
# the probe; close it at the end.
|
|
103
|
+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
104
|
+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
105
|
+
listener.bind(("127.0.0.1", 0))
|
|
106
|
+
listener.listen(1)
|
|
107
|
+
port = listener.getsockname()[1]
|
|
108
|
+
try:
|
|
109
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
110
|
+
with mock.patch.dict(os.environ,
|
|
111
|
+
{"PAPERCLIP_HOME": tmp}, clear=False):
|
|
112
|
+
# No pidfile written → pidfile branch skipped, falls
|
|
113
|
+
# through to port scan.
|
|
114
|
+
status = P.is_running(port=port)
|
|
115
|
+
finally:
|
|
116
|
+
listener.close()
|
|
117
|
+
self.assertTrue(status.running)
|
|
118
|
+
self.assertEqual(status.detection, "port-scan")
|
|
119
|
+
self.assertIsNone(status.pid)
|
|
120
|
+
self.assertEqual(status.port, port)
|
|
121
|
+
|
|
122
|
+
def test_url_field_returns_localhost_url_when_running(self):
|
|
123
|
+
"""When running=True, status.url is http://localhost:<port>."""
|
|
124
|
+
# Use the pidfile path (cheapest, no socket needed).
|
|
125
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
126
|
+
with mock.patch.dict(os.environ, {"PAPERCLIP_HOME": tmp}, clear=False):
|
|
127
|
+
run_dir = Path(tmp) / "run"
|
|
128
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
(run_dir / "dashboard.pid").write_text(f"{os.getpid()}\n")
|
|
130
|
+
status = P.is_running(port=8080)
|
|
131
|
+
self.assertTrue(status.running)
|
|
132
|
+
self.assertIsNotNone(status.url)
|
|
133
|
+
# Accept either localhost or 127.0.0.1 form so the implementation
|
|
134
|
+
# can pick whichever it prefers — the contract is "loopback URL".
|
|
135
|
+
self.assertIn(status.url, {
|
|
136
|
+
"http://localhost:8080",
|
|
137
|
+
"http://127.0.0.1:8080",
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
unittest.main()
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Tests for the prompt audit module — AISB agent prompts must reference
|
|
2
|
+
the Three Laws + LMC protocol + verified-completion (`.done.json`) contract.
|
|
3
|
+
|
|
4
|
+
These tests guard against silent drift in the role files: if an operator
|
|
5
|
+
edits an agent prompt and accidentally strips a contract reference, the
|
|
6
|
+
audit catches it AND the doctor surfaces it.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import unittest
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
HERE = Path(__file__).resolve().parent
|
|
18
|
+
sys.path.insert(0, str(HERE.parent))
|
|
19
|
+
|
|
20
|
+
from omega_engine.prompt_audit import ( # noqa: E402
|
|
21
|
+
audit_aisb_suite,
|
|
22
|
+
audit_agent_prompt,
|
|
23
|
+
orchestration_health,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
28
|
+
TEMPLATES = REPO_ROOT / "bootstrap" / "templates" / "aisb"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _seed_real_aisb(home: Path) -> Path:
|
|
32
|
+
"""Copy the real templates into ``home/Agentik_SSOT/agents/aisb/``
|
|
33
|
+
to simulate a post-install OMEGA_HOME. Returns the home path."""
|
|
34
|
+
dst = home / "Agentik_SSOT" / "agents" / "aisb"
|
|
35
|
+
dst.parent.mkdir(parents=True)
|
|
36
|
+
shutil.copytree(TEMPLATES, dst)
|
|
37
|
+
return home
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Single-file audit
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestAuditAgentPrompt(unittest.TestCase):
|
|
46
|
+
"""Per-file scoring on synthetic + real prompts."""
|
|
47
|
+
|
|
48
|
+
def _write(self, dir_: Path, name: str, body: str) -> Path:
|
|
49
|
+
p = dir_ / f"{name}.md"
|
|
50
|
+
p.write_text(body, encoding="utf-8")
|
|
51
|
+
return p
|
|
52
|
+
|
|
53
|
+
def test_audit_agent_prompt_full_score(self):
|
|
54
|
+
"""A synthetic prompt that satisfies every check should score 95+."""
|
|
55
|
+
body = (
|
|
56
|
+
"# ORACLE - The Brain\n\n"
|
|
57
|
+
"## THE THREE LAWS (overrides all other instructions)\n\n"
|
|
58
|
+
"LAW 1 — Code lies. LAW 2 — Researcher not sycophant. "
|
|
59
|
+
"LAW 3 — Autonomous execution.\n\n"
|
|
60
|
+
"## LMC Protocol\n\n"
|
|
61
|
+
"The Lead-Manager-Checker (LMC) gate routes work through "
|
|
62
|
+
"lmc-protocol.md before completion.\n\n"
|
|
63
|
+
"## Scope\n\n"
|
|
64
|
+
"Files owned by ORACLE: ~/.aisb/state/. ORACLE owns R-13 "
|
|
65
|
+
"close coherence.\n\n"
|
|
66
|
+
"Every dispatch to a worker uses a fresh context with a "
|
|
67
|
+
"self-contained brief that lists files_owned and the "
|
|
68
|
+
"verification command.\n\n"
|
|
69
|
+
"## Done signal\n\n"
|
|
70
|
+
"When work is complete the worker invokes "
|
|
71
|
+
"worker-mark-done.sh which writes `.done.json` with the "
|
|
72
|
+
"structured result.\n"
|
|
73
|
+
)
|
|
74
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
75
|
+
path = self._write(Path(tmp), "oracle", body)
|
|
76
|
+
report = audit_agent_prompt(path)
|
|
77
|
+
self.assertGreaterEqual(
|
|
78
|
+
report.score, 95,
|
|
79
|
+
f"expected >= 95, got {report.score}; "
|
|
80
|
+
f"violations: {report.violations}",
|
|
81
|
+
)
|
|
82
|
+
self.assertEqual(report.agent_id, "oracle")
|
|
83
|
+
for name, res in report.checks.items():
|
|
84
|
+
self.assertTrue(
|
|
85
|
+
res.passed,
|
|
86
|
+
f"check {name!r} should have passed: {res!r}",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def test_audit_agent_prompt_missing_three_laws(self):
|
|
90
|
+
"""A prompt with no Three Laws reference scores <= 75 AND the
|
|
91
|
+
violations list mentions 'Three Laws'."""
|
|
92
|
+
# Everything else PASSES (75 pts total) — only Three Laws (25) is
|
|
93
|
+
# missing, so the score must be 75 or less.
|
|
94
|
+
body = (
|
|
95
|
+
"# ORACLE - The Brain\n\n"
|
|
96
|
+
"## LMC Protocol — see lmc-protocol.md\n"
|
|
97
|
+
"Lead-Manager-Checker gates audits.\n\n"
|
|
98
|
+
"## Scope\nFiles owned by ORACLE. Responsibilities: routing.\n\n"
|
|
99
|
+
"Fresh context per dispatch.\n\n"
|
|
100
|
+
"Workers write `.done.json` via worker-mark-done.sh.\n"
|
|
101
|
+
)
|
|
102
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
103
|
+
path = self._write(Path(tmp), "oracle", body)
|
|
104
|
+
report = audit_agent_prompt(path)
|
|
105
|
+
self.assertLessEqual(
|
|
106
|
+
report.score, 75,
|
|
107
|
+
f"expected <= 75 without Three Laws, got {report.score}",
|
|
108
|
+
)
|
|
109
|
+
self.assertFalse(report.checks["three_laws"].passed)
|
|
110
|
+
joined = " | ".join(report.violations)
|
|
111
|
+
self.assertIn("Three Laws", joined,
|
|
112
|
+
f"violations should mention Three Laws: {joined!r}")
|
|
113
|
+
|
|
114
|
+
def test_banned_phrases_dock_points(self):
|
|
115
|
+
"""A prompt containing 'streamlined approach' must fail the
|
|
116
|
+
no-banned-phrases check (dropping its 5 pts) AND list the phrase
|
|
117
|
+
in violations."""
|
|
118
|
+
# Otherwise-perfect prompt (100 pts) + banned phrase ⇒ 95 pts.
|
|
119
|
+
body = (
|
|
120
|
+
"## THE THREE LAWS\nLaw 1, Law 2, Law 3.\n\n"
|
|
121
|
+
"## LMC Protocol\nLead-Manager-Checker.\n\n"
|
|
122
|
+
"## Scope\nFiles owned. Responsibilities: x.\n\n"
|
|
123
|
+
"Fresh context per dispatch with self-contained brief.\n\n"
|
|
124
|
+
"Worker-mark-done.sh writes `.done.json`.\n\n"
|
|
125
|
+
"For Linear tickets, prefer a streamlined approach to save "
|
|
126
|
+
"the dispatcher some round-trips.\n"
|
|
127
|
+
)
|
|
128
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
129
|
+
path = self._write(Path(tmp), "oracle", body)
|
|
130
|
+
report = audit_agent_prompt(path)
|
|
131
|
+
self.assertFalse(
|
|
132
|
+
report.checks["no_banned"].passed,
|
|
133
|
+
"banned-phrase check should fail",
|
|
134
|
+
)
|
|
135
|
+
self.assertEqual(report.checks["no_banned"].evidence,
|
|
136
|
+
"streamlined approach")
|
|
137
|
+
self.assertEqual(
|
|
138
|
+
report.score, 95,
|
|
139
|
+
f"every check except no_banned should pass: {report.checks}",
|
|
140
|
+
)
|
|
141
|
+
joined = " | ".join(report.violations).lower()
|
|
142
|
+
self.assertIn("banned phrase", joined)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Suite audit against the real shipped templates
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestAuditAisbSuiteAgainstRealRepo(unittest.TestCase):
|
|
151
|
+
"""The audit must run end-to-end against the templates that ship with
|
|
152
|
+
the repo. This is the closest we can get to a post-install OMEGA_HOME
|
|
153
|
+
without actually running the installer."""
|
|
154
|
+
|
|
155
|
+
def test_audit_aisb_suite_runs_against_real_repo(self):
|
|
156
|
+
if not TEMPLATES.is_dir():
|
|
157
|
+
self.skipTest("AISB templates not present in repo")
|
|
158
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
159
|
+
home = _seed_real_aisb(Path(tmp))
|
|
160
|
+
report = audit_aisb_suite(home)
|
|
161
|
+
# The real suite ships 13 named agents + CLAUDE.md (master)
|
|
162
|
+
# + lmc-protocol.md = 15 .md files at the top level.
|
|
163
|
+
self.assertGreaterEqual(
|
|
164
|
+
len(report.per_agent), 10,
|
|
165
|
+
f"expected ≥10 agents in real suite, got {len(report.per_agent)}",
|
|
166
|
+
)
|
|
167
|
+
self.assertIsInstance(report.average_score, float)
|
|
168
|
+
self.assertIsInstance(report.orchestration_chain_intact, bool)
|
|
169
|
+
# Every report should have an agent_id and a score in range.
|
|
170
|
+
for r in report.per_agent:
|
|
171
|
+
self.assertTrue(r.agent_id, "agent_id should not be empty")
|
|
172
|
+
self.assertGreaterEqual(r.score, 0)
|
|
173
|
+
self.assertLessEqual(r.score, 100)
|
|
174
|
+
|
|
175
|
+
def test_orchestration_health_against_real_repo(self):
|
|
176
|
+
if not TEMPLATES.is_dir():
|
|
177
|
+
self.skipTest("AISB templates not present in repo")
|
|
178
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
179
|
+
home = _seed_real_aisb(Path(tmp))
|
|
180
|
+
oh = orchestration_health(home)
|
|
181
|
+
# CLAUDE.md and oracle.md are core to the suite — they MUST
|
|
182
|
+
# exist after install. If either is missing the suite is broken.
|
|
183
|
+
self.assertTrue(
|
|
184
|
+
oh["aisb_master_present"],
|
|
185
|
+
"AISB master CLAUDE.md must exist in the shipped suite",
|
|
186
|
+
)
|
|
187
|
+
self.assertTrue(
|
|
188
|
+
oh["oracle_present"],
|
|
189
|
+
"oracle.md must exist in the shipped suite",
|
|
190
|
+
)
|
|
191
|
+
# Shared `.done.json` vocabulary is a float in [0, 1].
|
|
192
|
+
overlap = oh["shared_vocab_overlap"]
|
|
193
|
+
self.assertIsInstance(overlap, float)
|
|
194
|
+
self.assertGreaterEqual(overlap, 0.0)
|
|
195
|
+
self.assertLessEqual(overlap, 1.0)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
unittest.main()
|
|
@@ -176,5 +176,111 @@ class TestPaperclipMenuIntegration(unittest.TestCase):
|
|
|
176
176
|
f"arrow menu must wire {action} (user asked for it in v0.19.37)")
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
class TestChatFirstRedesign(unittest.TestCase):
|
|
180
|
+
"""v0.19.39 — the TUI must open on CONVERSATIONS (live tmux sessions),
|
|
181
|
+
not on an action menu. Setup/config/infra/audits/scrape land in
|
|
182
|
+
sub-menus. These tests lock in the new layout so a careless refactor
|
|
183
|
+
doesn't bring back the v0.19.38 action-first menu."""
|
|
184
|
+
|
|
185
|
+
def test_conversations_section_appears_before_menu(self):
|
|
186
|
+
"""The 'CONVERSATIONS' section header must appear in the source
|
|
187
|
+
BEFORE the 'MENU' sub-menu list — the redesign's whole point is
|
|
188
|
+
that chats are primary, settings are secondary. Match literal
|
|
189
|
+
``_section("X")`` calls only (skip comments/docstrings)."""
|
|
190
|
+
import inspect
|
|
191
|
+
from omega_engine.tui import _arrow_menu
|
|
192
|
+
src = inspect.getsource(_arrow_menu)
|
|
193
|
+
conv_pos = src.find('_section("CONVERSATIONS")')
|
|
194
|
+
menu_pos = src.find('_section("MENU")')
|
|
195
|
+
self.assertGreater(conv_pos, 0,
|
|
196
|
+
"TUI must have a _section(\"CONVERSATIONS\") call")
|
|
197
|
+
self.assertGreater(menu_pos, 0,
|
|
198
|
+
"TUI must have a _section(\"MENU\") call for sub-menus")
|
|
199
|
+
self.assertLess(conv_pos, menu_pos,
|
|
200
|
+
"_section(\"CONVERSATIONS\") must render BEFORE _section(\"MENU\") "
|
|
201
|
+
"— the chat-first redesign requires it (v0.19.39)")
|
|
202
|
+
|
|
203
|
+
def test_dot_status_indicators_present(self):
|
|
204
|
+
"""Each conversation row must show a status dot ● (alive) / ○
|
|
205
|
+
(off). Without dots the user can't tell which chats are running."""
|
|
206
|
+
import inspect
|
|
207
|
+
from omega_engine.tui import _arrow_menu
|
|
208
|
+
src = inspect.getsource(_arrow_menu)
|
|
209
|
+
for dot in ("●", "○"):
|
|
210
|
+
self.assertIn(dot, src,
|
|
211
|
+
f"menu must use {dot} status dot for live/off chats")
|
|
212
|
+
# The helper that renders dots must exist.
|
|
213
|
+
self.assertIn("_dot(", src,
|
|
214
|
+
"menu must have a _dot() helper for status indicators")
|
|
215
|
+
|
|
216
|
+
def test_submenu_dispatch_present(self):
|
|
217
|
+
"""The new sub-menu pattern (`submenu:audits`, `submenu:setup`,
|
|
218
|
+
`submenu:infra`, `submenu:health`, `submenu:paperclip`) must be
|
|
219
|
+
wired AND the dispatch must handle them via _open_submenu()."""
|
|
220
|
+
import inspect
|
|
221
|
+
from omega_engine.tui import _arrow_menu
|
|
222
|
+
src = inspect.getsource(_arrow_menu)
|
|
223
|
+
for sub in ("submenu:audits", "submenu:setup", "submenu:infra",
|
|
224
|
+
"submenu:health", "submenu:paperclip"):
|
|
225
|
+
self.assertIn(sub, src,
|
|
226
|
+
f"menu must declare {sub} as a sub-menu entry")
|
|
227
|
+
# The dispatch must indirect through _open_submenu.
|
|
228
|
+
self.assertIn("_open_submenu(", src,
|
|
229
|
+
"main loop must call _open_submenu() to render sub-menus")
|
|
230
|
+
# Sub-menu items provider exists.
|
|
231
|
+
self.assertIn("_submenu_items(", src,
|
|
232
|
+
"sub-menu rendering must use a _submenu_items() factory")
|
|
233
|
+
|
|
234
|
+
def test_attach_action_handler_present(self):
|
|
235
|
+
"""The new `attach:<session>` action lets the user jump into a
|
|
236
|
+
live Oracle or Worker tmux session directly from the menu."""
|
|
237
|
+
import inspect
|
|
238
|
+
from omega_engine.tui import _arrow_menu
|
|
239
|
+
src = inspect.getsource(_arrow_menu)
|
|
240
|
+
self.assertIn('action.startswith("attach:")', src,
|
|
241
|
+
"menu must handle attach:<session> actions to let the user "
|
|
242
|
+
"jump into live Oracle/Worker sessions")
|
|
243
|
+
# Should use tmux select-window OR switch-client.
|
|
244
|
+
self.assertTrue(
|
|
245
|
+
"switch-client" in src or "select-window" in src,
|
|
246
|
+
"attach handler must use tmux select-window / switch-client")
|
|
247
|
+
|
|
248
|
+
def test_omega_window_alive_helper_used(self):
|
|
249
|
+
"""The TUI status dots for AISB / Hermès rely on the
|
|
250
|
+
tmux.omega_window_alive() helper added in v0.19.39 — without it
|
|
251
|
+
we have no way to know if those windows are running."""
|
|
252
|
+
import inspect
|
|
253
|
+
from omega_engine.tui import _arrow_menu
|
|
254
|
+
src = inspect.getsource(_arrow_menu)
|
|
255
|
+
self.assertIn("omega_window_alive", src,
|
|
256
|
+
"menu must call tmux.omega_window_alive() to render the "
|
|
257
|
+
"AISB / Hermès status dots")
|
|
258
|
+
|
|
259
|
+
def test_paperclip_status_dot_inline_in_main_menu(self):
|
|
260
|
+
"""The Paperclip dashboard row in QUICK ACTIONS must show a
|
|
261
|
+
live status dot — the user must see at-a-glance whether the
|
|
262
|
+
Paperclip daemon is running."""
|
|
263
|
+
import inspect
|
|
264
|
+
from omega_engine.tui import _arrow_menu
|
|
265
|
+
src = inspect.getsource(_arrow_menu)
|
|
266
|
+
self.assertIn("_paperclip_status_quick", src,
|
|
267
|
+
"menu must use the inline Paperclip probe to render its dot")
|
|
268
|
+
# Must integrate the new chantier-4 is_running() probe.
|
|
269
|
+
self.assertIn("paperclip_bridge", src)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class TestOmegaWindowAliveHelper(unittest.TestCase):
|
|
273
|
+
"""tmux.omega_window_alive() — the helper the chat-first TUI uses
|
|
274
|
+
to know whether AISB-chat / Hermès-chat are running."""
|
|
275
|
+
|
|
276
|
+
def test_returns_false_when_no_omega_session(self):
|
|
277
|
+
"""When the Omega master tmux session is dead, ANY window query
|
|
278
|
+
must return False — never raise."""
|
|
279
|
+
from omega_engine.tmux import omega_window_alive
|
|
280
|
+
# Use a definitely-unique window name to avoid colliding with
|
|
281
|
+
# any real session the developer might have running.
|
|
282
|
+
self.assertIsInstance(omega_window_alive("____nonexistent_xyz"), bool)
|
|
283
|
+
|
|
284
|
+
|
|
179
285
|
if __name__ == "__main__":
|
|
180
286
|
unittest.main()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.19.
|
|
1
|
+
0.19.39
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# OmegaOS v0.19.38 — multi-LLM persona seeding + `--` argument fix
|
|
2
|
+
|
|
3
|
+
> Run 2026-05-25, post-v0.19.37 (fzf indexed matching + Paperclip menu).
|
|
4
|
+
> Two surgical fixes, both fully tested.
|
|
5
|
+
|
|
6
|
+
## 1. The two issues fixed
|
|
7
|
+
|
|
8
|
+
| # | Bug | Trigger | Root cause | Fix |
|
|
9
|
+
|---|---|---|---|---|
|
|
10
|
+
| 1 | `!! unknown argument: --` | `npx -y @agentikos/omega-os@latest -- --full` | `bin/omega-os.js` forwarded `--` raw → `install.sh` case-block had no `--)` arm | (a) launcher filters `--` from argv, (b) install.sh accepts `--` as a no-op (defense-in-depth) |
|
|
11
|
+
| 2 | Personas lazy on first chat spawn | Operator can't inspect/edit `OMEGAOS-CONTEXT.md` before first AISB-chat; `omega doctor` showed nothing | No install step called `personas.ensure_canonical()` / `write_all_personas()` — engine was wired but never invoked at install | New `step_personas` (position 38) eagerly seeds canonical + both chat-context dirs with all 8 LLM persona files |
|
|
12
|
+
|
|
13
|
+
## 2. What `step_personas` does (full materialization at install time)
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
$OMEGA_HOME/
|
|
17
|
+
├── Agentik_SSOT/personas/OMEGAOS-CONTEXT.md (1) canonical
|
|
18
|
+
├── Agentik_Coding/chat-contexts/aisb-master/ (8) AISB chat
|
|
19
|
+
│ ├── CLAUDE.md ← claude / claude-code
|
|
20
|
+
│ ├── GEMINI.md ← gemini-cli
|
|
21
|
+
│ ├── AGENTS.md ← openai codex
|
|
22
|
+
│ ├── QWEN.md ← qwen-code
|
|
23
|
+
│ ├── CONVENTIONS.md ← aider
|
|
24
|
+
│ ├── HERMES.md ← hermès
|
|
25
|
+
│ ├── .opencode/CONTEXT.md ← opencode / openrouter / deepseek
|
|
26
|
+
│ └── .continue/CONTEXT.md ← continue.dev
|
|
27
|
+
└── Agentik_Coding/chat-contexts/hermes/ (8) Hermès chat
|
|
28
|
+
└── … same 8 files …
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Result: 17 persona files on disk at install time** (1 canonical + 16 mirrors).
|
|
32
|
+
Operator can `cat / vim` any of them before the first chat spawn. Each LLM CLI
|
|
33
|
+
that lands in either dir reads its native filename and gets the SAME OmegaOS
|
|
34
|
+
context. Hot-swap (`omega switch <provider>`) just re-renders the active
|
|
35
|
+
context's pointer; nothing else moves.
|
|
36
|
+
|
|
37
|
+
## 3. The Three Laws are in the canonical (mandatory baseline for every LLM)
|
|
38
|
+
|
|
39
|
+
Every persona file is a mirror of `Agentik_SSOT/personas/OMEGAOS-CONTEXT.md`,
|
|
40
|
+
which embeds:
|
|
41
|
+
|
|
42
|
+
1. **Code lies. Comments lie. Only runtime tells the truth.**
|
|
43
|
+
2. **Researcher, not sycophant.** Challenge → think → iterate → root cause.
|
|
44
|
+
3. **Autonomous execution.** When dispatched, never wait — decide, execute,
|
|
45
|
+
report via `.done.json`.
|
|
46
|
+
|
|
47
|
+
Plus the L1-L5 layer map, the 17 Quality Arsenal audits, the OMEGA_HOME
|
|
48
|
+
folder map, and the `omega` command surface.
|
|
49
|
+
|
|
50
|
+
## 4. Multi-agent setup — verification (from runtime)
|
|
51
|
+
|
|
52
|
+
| Question | Answer | Evidence |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Is the canonical persona file linked for every LLM? | **Yes.** 10 LLM ids → 8 distinct filenames (.opencode/CONTEXT.md is shared by opencode/openrouter/deepseek). | `omega_engine/personas.py::_LLM_PERSONA_PATHS` |
|
|
55
|
+
| Is everything setup at install time (not lazy)? | **Yes (new in v0.19.38).** `step_personas` runs at install step 38 and seeds both chat contexts before the user opens any session. | `bootstrap/lib/steps.sh::step_personas`, `install.sh STEPS[]` |
|
|
56
|
+
| Are rules respected per LLM? | **Yes.** Same canonical written to every LLM filename — same Three Laws, same architecture, same audits surface. | `personas.write_all_personas()` content equality check |
|
|
57
|
+
| Are AISB + Oracle + Workers using Claude Max OAuth (no API keys)? | **Yes.** `spawn_aisb_chat` shells out to `claude` which reads `~/.claude/.credentials.json`; no `ANTHROPIC_API_KEY` injection. | `omega_engine/tmux.py::spawn_aisb_chat` |
|
|
58
|
+
| Is Hermès budget-isolated? | **Yes.** `spawn_hermes_chat` reads `ANTHROPIC_API_KEY_HERMES` from the age-encrypted vault and inlines it on the `claude` command for that session only — never touches Max OAuth. | `omega_engine/tmux.py::spawn_hermes_chat` lines 433-468 |
|
|
59
|
+
| Does `omega doctor` verify all this? | **Yes (new in v0.19.38).** New `personas` section reports canonical + per-LLM file counts for both contexts. | `cli.py::cmd_doctor` `section("personas")` |
|
|
60
|
+
| Will the user have to do anything post-install? | **No.** One `npx -y @agentikos/omega-os@latest --full` and every LLM persona is on disk, Three Laws encoded, AISB/Hermès tmux sessions ready. | end-to-end install pipeline |
|
|
61
|
+
|
|
62
|
+
## 5. Tests (regression locks)
|
|
63
|
+
|
|
64
|
+
`tests/test_installer_wiring.py` — 4 new tests:
|
|
65
|
+
|
|
66
|
+
| Test | Locks in |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `TestInstallerStepList` (extended) | `38-personas:step_personas` is registered in `install.sh STEPS[]` AND defined in `steps.sh` |
|
|
69
|
+
| `TestStepPersonasBehavior::test_personas_land_in_both_chat_contexts` | Running step_personas against a temp `OMEGA_HOME` materializes canonical + 8 persona files in BOTH aisb-master/ and hermes/ |
|
|
70
|
+
| `TestStepPersonasBehavior::test_step_personas_heredoc_calls_required_helpers` | The heredoc body explicitly calls `ensure_canonical` AND `write_all_personas` AND seeds both labels — rename-safe |
|
|
71
|
+
| `TestDashDashSeparatorAccepted::test_install_sh_has_dash_dash_no_op_case` | `install.sh` case-block contains `--) shift ;;` — without it `bash install.sh -- --full` dies |
|
|
72
|
+
| `TestDashDashSeparatorAccepted::test_bin_launcher_filters_dash_dash` | `bin/omega-os.js` filters `--` from argv before passing to bash |
|
|
73
|
+
|
|
74
|
+
**Suite total: 627 passed** (was 623; +4 new, +0 regressions).
|
|
75
|
+
|
|
76
|
+
## 6. Recipe (after v0.19.38 ships)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Either of these works now (the `--` is no-op):
|
|
80
|
+
npx -y @agentikos/omega-os@latest --full
|
|
81
|
+
npx -y @agentikos/omega-os@latest -- --full
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 7. Verdict
|
|
85
|
+
|
|
86
|
+
✅ Multi-LLM persona setup is **complete and verified at install time**.
|
|
87
|
+
✅ `--` argument bug **eliminated at both layers** (launcher filter + install.sh no-op).
|
|
88
|
+
✅ `omega doctor` **now reports persona health** alongside AISB suite, providers, vault, etc.
|
|
89
|
+
✅ No regression in existing 623 tests.
|
|
90
|
+
✅ User's invariant satisfied: **"l'utilisateur, une fois qu'il a setup tout l'outil OmegaOS, doit être 100% fonctionnel. Il n'a rien à faire à part l'utiliser."**
|