@agentikos/omega-os 0.19.52 → 0.19.54
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/common.sh +24 -0
- package/bootstrap/lib/steps.sh +9 -0
- package/omega/Agentik_Engine/omega_engine/__init__.py +248 -184
- 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__/provider_state.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 +34 -5
- package/omega/Agentik_Engine/omega_engine/provider_state.py +7 -1
- package/omega/Agentik_Engine/omega_engine/tmux.py +14 -0
- package/omega/Agentik_Engine/omega_engine/tui.py +48 -12
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- 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_53_cli_commands.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v19_53_cli_commands.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_tmux_palette.py +13 -0
- package/omega/Agentik_Engine/tests/test_v19_53_cli_commands.py +351 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/package.json +1 -1
package/bootstrap/lib/common.sh
CHANGED
|
@@ -676,6 +676,30 @@ except Exception: print("unknown")' 2>/dev/null)" || readiness="unknown"
|
|
|
676
676
|
printf ' %s %sissues%s %sgithub.com/agentik-os/OmegaOS/issues%s\n' "$bar" "$C_BOLD" "$C_RST" "$C_DIM" "$C_RST"
|
|
677
677
|
printf ' %s\n' "$bot"
|
|
678
678
|
|
|
679
|
+
# ── Terminal config tips for Option+key shortcuts ────────────────────
|
|
680
|
+
# tmux binds M-z / M-/ for the Omega popup menu + session switcher.
|
|
681
|
+
# macOS Terminal.app + Termius default send the literal Unicode
|
|
682
|
+
# character on Option+key (Ω, ÷) instead of the Esc+key (Meta) sequence
|
|
683
|
+
# tmux expects. The Unicode fallback binds in tmux.py cover that, but
|
|
684
|
+
# configuring the terminal once gives the user a proper Meta key.
|
|
685
|
+
log ""
|
|
686
|
+
log "${C_ORANGE}━ Terminal config required for Option+key shortcuts ━${C_RST}"
|
|
687
|
+
log ""
|
|
688
|
+
log " ${C_BOLD}Option+Z${C_RST} → Omega menu (popup)"
|
|
689
|
+
log " ${C_BOLD}Option+/${C_RST} → Session switcher (popup)"
|
|
690
|
+
log " ${C_BOLD}Option+O${C_RST} → Omega menu (alternate, when tmux-claude is preserved)"
|
|
691
|
+
log ""
|
|
692
|
+
log " For these to fire, your terminal must send Option as Esc+ (Meta):"
|
|
693
|
+
log ""
|
|
694
|
+
log " ${C_BOLD}iTerm2${C_RST} Settings → Profiles → Keys → Left Option Key: ${C_BOLD}Esc+${C_RST}"
|
|
695
|
+
log " ${C_BOLD}Terminal.app${C_RST} Settings → Profile → Keyboard → ☑ ${C_BOLD}Use Option as Meta key${C_RST}"
|
|
696
|
+
log " ${C_BOLD}Termius${C_RST} Settings → Terminal → Option key → ${C_BOLD}Send Esc${C_RST}"
|
|
697
|
+
log " ${C_BOLD}Warp${C_RST} Settings → Input → ☑ ${C_BOLD}Option as Meta${C_RST}"
|
|
698
|
+
log " ${C_BOLD}Alacritty${C_RST} add to alacritty.toml: ${C_DIM}[env]\\nALACRITTY_OPTION_AS_ALT = \"Both\"${C_RST}"
|
|
699
|
+
log ""
|
|
700
|
+
log " Without this, Option+Z sends ${C_DIM}Ω${C_RST} (Unicode) instead of ${C_DIM}\\e z${C_RST} — tmux never sees the bind."
|
|
701
|
+
log ""
|
|
702
|
+
|
|
679
703
|
# If PARTIAL or NOT READY, tell them where to look.
|
|
680
704
|
case "$verdict_text" in
|
|
681
705
|
PARTIAL|"NOT READY")
|
package/bootstrap/lib/steps.sh
CHANGED
|
@@ -427,6 +427,15 @@ step_claude_code_settings() {
|
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
step_engine() {
|
|
430
|
+
# v0.19.54 — neutralize broken claude-mem here too, NOT just in
|
|
431
|
+
# step_claude_plugins. The plugins step can be skipped/idempotent on
|
|
432
|
+
# re-install, but step_engine ALWAYS re-runs on version drift (it's
|
|
433
|
+
# in the force-rerun list). This guarantees the broken plugin is
|
|
434
|
+
# disabled on every upgrade, even if step 45 is somehow skipped.
|
|
435
|
+
if declare -f _disable_broken_claude_plugins >/dev/null 2>&1; then
|
|
436
|
+
_disable_broken_claude_plugins
|
|
437
|
+
fi
|
|
438
|
+
|
|
430
439
|
local uv_bin; uv_bin="$(command -v uv || echo "$HOME/.local/bin/uv")"
|
|
431
440
|
[ -x "$uv_bin" ] || { err "uv not found after step 10"; return 1; }
|
|
432
441
|
cd "$OMEGA_HOME/Agentik_Engine" || { err "engine block missing"; return 1; }
|
|
@@ -3,192 +3,256 @@
|
|
|
3
3
|
Event-sourced, verified-completion agent graphs. The engine is business-agnostic:
|
|
4
4
|
it executes graphs of Tasks. Who is an "oracle" and which topology runs lives in
|
|
5
5
|
Agentik_Orchestration/, never here.
|
|
6
|
+
|
|
7
|
+
v0.19.53 — PEP 562 lazy attribute loading. Previously, importing
|
|
8
|
+
``omega_engine`` (or any submodule like ``omega_engine.cli``) eagerly pulled
|
|
9
|
+
the full submodule tree — ``account``, ``autonomous``, ``audit``, ``educators``,
|
|
10
|
+
``rag``, ``skill_discovery``, ``genesis``, ``provider``, ``executor`` and 40+
|
|
11
|
+
others — at module-load time. On macOS uv-Python that's ~500-1500ms of import
|
|
12
|
+
cost paid before *any* CLI argument is parsed, which made bare ``omega``
|
|
13
|
+
(menu) feel sluggish even after v0.19.50 removed runtime tmux probes.
|
|
14
|
+
|
|
15
|
+
Each public re-export is now declared in ``_LAZY_MAP``: a mapping from the
|
|
16
|
+
public name to ``(submodule_path, attribute_name)``. ``__getattr__`` (PEP
|
|
17
|
+
562) resolves the import on first access, so ``from omega_engine import
|
|
18
|
+
WorkerBrief`` still works, but ``import omega_engine`` itself touches *zero*
|
|
19
|
+
heavy submodules.
|
|
20
|
+
|
|
21
|
+
Submodules (``omega_engine.tmux``, ``omega_engine.cli``, etc.) are still
|
|
22
|
+
auto-importable by Python's standard submodule mechanism — they don't need
|
|
23
|
+
to be in ``_LAZY_MAP``.
|
|
6
24
|
"""
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import importlib
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
__version__ = "0.19.54"
|
|
31
|
+
|
|
32
|
+
# Public-name → (module_path, attribute_name) map.
|
|
33
|
+
# ``attribute_name = None`` means "return the whole module" (used for the
|
|
34
|
+
# ``plan_v7`` alias that points at the ``omega_engine.plan`` submodule).
|
|
35
|
+
_LAZY_MAP: dict[str, tuple[str, str | None]] = {
|
|
36
|
+
# account pool + billing
|
|
37
|
+
"AccountPool": ("omega_engine.account", "AccountPool"),
|
|
38
|
+
"BillingAggregator": ("omega_engine.account", "BillingAggregator"),
|
|
39
|
+
"ClaudeAccount": ("omega_engine.account", "ClaudeAccount"),
|
|
40
|
+
# audit (the canonical AuditGate / AuditVerdict / AuditFinding)
|
|
41
|
+
"AuditFinding": ("omega_engine.audit", "AuditFinding"),
|
|
42
|
+
"AuditGate": ("omega_engine.audit", "AuditGate"),
|
|
43
|
+
"AuditVerdict": ("omega_engine.audit", "AuditVerdict"),
|
|
44
|
+
# audit_arsenal — multi-audit forensic harness
|
|
45
|
+
"ArsenalGate": ("omega_engine.audit_arsenal", "ArsenalGate"),
|
|
46
|
+
"ArsenalVerdict": ("omega_engine.audit_arsenal", "ArsenalVerdict"),
|
|
47
|
+
"Audit": ("omega_engine.audit_arsenal", "Audit"),
|
|
48
|
+
"AuditRegistry": ("omega_engine.audit_arsenal", "AuditRegistry"),
|
|
49
|
+
"run_forensic_audit": ("omega_engine.audit_arsenal", "run_forensic_audit"),
|
|
50
|
+
# autonomous-agent supervisor + charters
|
|
51
|
+
"AutonomousSupervisor": ("omega_engine.autonomous", "AutonomousSupervisor"),
|
|
52
|
+
"Charter": ("omega_engine.autonomous", "Charter"),
|
|
53
|
+
"build_supervisor_from_home": ("omega_engine.autonomous", "build_supervisor_from_home"),
|
|
54
|
+
"load_charters": ("omega_engine.autonomous", "load_charters"),
|
|
55
|
+
"next_fire": ("omega_engine.autonomous", "next_fire"),
|
|
56
|
+
"parse_cron": ("omega_engine.autonomous", "parse_cron"),
|
|
57
|
+
# barrier
|
|
58
|
+
"ScopeStatus": ("omega_engine.barrier", "ScopeStatus"),
|
|
59
|
+
"scope_status": ("omega_engine.barrier", "scope_status"),
|
|
60
|
+
# educators — the self-improving layer
|
|
61
|
+
"Artifact": ("omega_engine.educators", "Artifact"),
|
|
62
|
+
"ArtifactEducator": ("omega_engine.educators", "ArtifactEducator"),
|
|
63
|
+
"AutomationEducator": ("omega_engine.educators", "AutomationEducator"),
|
|
64
|
+
"ClaudecodeEducator": ("omega_engine.educators", "ClaudecodeEducator"),
|
|
65
|
+
"ConnectionEducator": ("omega_engine.educators", "ConnectionEducator"),
|
|
66
|
+
"CoworkerEducator": ("omega_engine.educators", "CoworkerEducator"),
|
|
67
|
+
"Educator": ("omega_engine.educators", "Educator"),
|
|
68
|
+
"EducatorProposal": ("omega_engine.educators", "EducatorProposal"),
|
|
69
|
+
"EducatorRegistry": ("omega_engine.educators", "EducatorRegistry"),
|
|
70
|
+
"LoopEducator": ("omega_engine.educators", "LoopEducator"),
|
|
71
|
+
"PromptEducator": ("omega_engine.educators", "PromptEducator"),
|
|
72
|
+
"SkillEducator": ("omega_engine.educators", "SkillEducator"),
|
|
73
|
+
"StagingPipeline": ("omega_engine.educators", "StagingPipeline"),
|
|
74
|
+
"StagingRecord": ("omega_engine.educators", "StagingRecord"),
|
|
75
|
+
# bus
|
|
76
|
+
"EventBus": ("omega_engine.bus", "EventBus"),
|
|
77
|
+
# events
|
|
78
|
+
"Event": ("omega_engine.events", "Event"),
|
|
79
|
+
"EventType": ("omega_engine.events", "EventType"),
|
|
80
|
+
# executor
|
|
81
|
+
"Executor": ("omega_engine.executor", "Executor"),
|
|
82
|
+
"MissionResult": ("omega_engine.executor", "MissionResult"),
|
|
83
|
+
# integrations — graphify
|
|
84
|
+
"GraphifyError": ("omega_engine.integrations", "GraphifyError"),
|
|
85
|
+
"GraphifyGraphMissing": ("omega_engine.integrations", "GraphifyGraphMissing"),
|
|
86
|
+
"GraphifyNotInstalled": ("omega_engine.integrations", "GraphifyNotInstalled"),
|
|
87
|
+
"GraphifyReport": ("omega_engine.integrations", "GraphifyReport"),
|
|
88
|
+
"graphify_install_skill": ("omega_engine.integrations", "graphify_install_skill"),
|
|
89
|
+
"graphify_into_rag": ("omega_engine.integrations", "graphify_into_rag"),
|
|
90
|
+
"graphify_load_graph": ("omega_engine.integrations", "graphify_load_graph"),
|
|
91
|
+
"graphify_version": ("omega_engine.integrations", "graphify_version"),
|
|
92
|
+
"is_graphify_installed": ("omega_engine.integrations", "is_graphify_installed"),
|
|
93
|
+
# progress
|
|
94
|
+
"MissionProgress": ("omega_engine.progress", "MissionProgress"),
|
|
95
|
+
"ProgressTracker": ("omega_engine.progress", "ProgressTracker"),
|
|
96
|
+
# provider
|
|
97
|
+
"AgentProvider": ("omega_engine.provider", "AgentProvider"),
|
|
98
|
+
"AgentRequest": ("omega_engine.provider", "AgentRequest"),
|
|
99
|
+
"AgentResult": ("omega_engine.provider", "AgentResult"),
|
|
100
|
+
"ClaudeProvider": ("omega_engine.provider", "ClaudeProvider"),
|
|
101
|
+
"DeepSeekProvider": ("omega_engine.provider", "DeepSeekProvider"),
|
|
102
|
+
"GLMProvider": ("omega_engine.provider", "GLMProvider"),
|
|
103
|
+
"MockProvider": ("omega_engine.provider", "MockProvider"),
|
|
104
|
+
"OpenAIProvider": ("omega_engine.provider", "OpenAIProvider"),
|
|
105
|
+
# rag
|
|
106
|
+
"AgenticRetriever": ("omega_engine.rag", "AgenticRetriever"),
|
|
107
|
+
"CorrectiveRetriever": ("omega_engine.rag", "CorrectiveRetriever"),
|
|
108
|
+
"Document": ("omega_engine.rag", "Document"),
|
|
109
|
+
"GraphRetriever": ("omega_engine.rag", "GraphRetriever"),
|
|
110
|
+
"HybridRetriever": ("omega_engine.rag", "HybridRetriever"),
|
|
111
|
+
"MultimodalRetriever": ("omega_engine.rag", "MultimodalRetriever"),
|
|
112
|
+
"RAGRouter": ("omega_engine.rag", "RAGRouter"),
|
|
113
|
+
"RetrievalResult": ("omega_engine.rag", "RetrievalResult"),
|
|
114
|
+
"Retriever": ("omega_engine.rag", "Retriever"),
|
|
115
|
+
# reducer
|
|
116
|
+
"IllegalTransition": ("omega_engine.reducer", "IllegalTransition"),
|
|
117
|
+
"reduce": ("omega_engine.reducer", "reduce"),
|
|
118
|
+
"reduce_task": ("omega_engine.reducer", "reduce_task"),
|
|
119
|
+
# router
|
|
120
|
+
"ModelRouter": ("omega_engine.router", "ModelRouter"),
|
|
121
|
+
# store
|
|
122
|
+
"EventStore": ("omega_engine.store", "EventStore"),
|
|
123
|
+
"SQLiteStore": ("omega_engine.store", "SQLiteStore"),
|
|
124
|
+
# cadence
|
|
125
|
+
"CadenceCharter": ("omega_engine.cadence", "CadenceCharter"),
|
|
126
|
+
"disable_cadence": ("omega_engine.cadence", "disable_cadence"),
|
|
127
|
+
"list_cadences": ("omega_engine.cadence", "list_cadences"),
|
|
128
|
+
"remove_cadence": ("omega_engine.cadence", "remove_cadence"),
|
|
129
|
+
"schedule_cadence": ("omega_engine.cadence", "schedule_cadence"),
|
|
130
|
+
# pursue
|
|
131
|
+
"PursueResult": ("omega_engine.pursue", "PursueResult"),
|
|
132
|
+
"pursue": ("omega_engine.pursue", "pursue"),
|
|
133
|
+
# webhooks
|
|
134
|
+
"WebhookRegistry": ("omega_engine.webhooks", "WebhookRegistry"),
|
|
135
|
+
"WebhookSpec": ("omega_engine.webhooks", "WebhookSpec"),
|
|
136
|
+
"generate_test_signature": ("omega_engine.webhooks", "generate_test_signature"),
|
|
137
|
+
"handle_webhook": ("omega_engine.webhooks", "handle_webhook"),
|
|
138
|
+
"load_webhook_registry": ("omega_engine.webhooks", "load_registry"),
|
|
139
|
+
"signature_header_for": ("omega_engine.webhooks", "signature_header_for"),
|
|
140
|
+
"verify_signature": ("omega_engine.webhooks", "verify_signature"),
|
|
141
|
+
# skill_discovery
|
|
142
|
+
"DiscoveredSkill": ("omega_engine.skill_discovery", "DiscoveredSkill"),
|
|
143
|
+
"Marketplace": ("omega_engine.skill_discovery", "Marketplace"),
|
|
144
|
+
"SkillAuditIssue": ("omega_engine.skill_discovery", "SkillAuditIssue"),
|
|
145
|
+
"SkillAuditVerdict": ("omega_engine.skill_discovery", "SkillAuditVerdict"),
|
|
146
|
+
"SkillInstallResult": ("omega_engine.skill_discovery", "SkillInstallResult"),
|
|
147
|
+
"audit_skill_file": ("omega_engine.skill_discovery", "audit_skill_file"),
|
|
148
|
+
"audit_skill_text": ("omega_engine.skill_discovery", "audit_skill_text"),
|
|
149
|
+
"discover_skills": ("omega_engine.skill_discovery", "discover_skills"),
|
|
150
|
+
"install_skill_from_github": ("omega_engine.skill_discovery", "install_skill_from_github"),
|
|
151
|
+
"list_curated": ("omega_engine.skill_discovery", "list_curated"),
|
|
152
|
+
"load_marketplaces": ("omega_engine.skill_discovery", "load_marketplaces"),
|
|
153
|
+
# sync
|
|
154
|
+
"ClaudeCodeAdapter": ("omega_engine.sync", "ClaudeCodeAdapter"),
|
|
155
|
+
"SyncEngine": ("omega_engine.sync", "SyncEngine"),
|
|
156
|
+
# done_signal
|
|
157
|
+
"DoneSignal": ("omega_engine.done_signal", "DoneSignal"),
|
|
158
|
+
"DoneSignalError": ("omega_engine.done_signal", "DoneSignalError"),
|
|
159
|
+
"attach_audit_to_done": ("omega_engine.done_signal", "attach_audit_to_done"),
|
|
160
|
+
"done_path": ("omega_engine.done_signal", "done_path"),
|
|
161
|
+
"read_done": ("omega_engine.done_signal", "read_done"),
|
|
162
|
+
"session_dir": ("omega_engine.done_signal", "session_dir"),
|
|
163
|
+
"write_done": ("omega_engine.done_signal", "write_done"),
|
|
164
|
+
# envelope
|
|
165
|
+
"DISPATCHER_ROLES": ("omega_engine.envelope", "DISPATCHER_ROLES"),
|
|
166
|
+
"EXECUTOR_ROLES": ("omega_engine.envelope", "EXECUTOR_ROLES"),
|
|
167
|
+
"EnvelopeContext": ("omega_engine.envelope", "EnvelopeContext"),
|
|
168
|
+
"VERIFIER_ROLES": ("omega_engine.envelope", "VERIFIER_ROLES"),
|
|
169
|
+
"build_envelope": ("omega_engine.envelope", "build_envelope"),
|
|
170
|
+
# prompts
|
|
171
|
+
"find_agent_file": ("omega_engine.prompts", "find_agent_file"),
|
|
172
|
+
"list_available_agents": ("omega_engine.prompts", "list_available_agents"),
|
|
173
|
+
"load_agent_prompt": ("omega_engine.prompts", "load_agent_prompt"),
|
|
174
|
+
# vault
|
|
175
|
+
"list_project_vaults": ("omega_engine.vault", "list_project_vaults"),
|
|
176
|
+
"vault_init": ("omega_engine.vault", "vault_init"),
|
|
177
|
+
"vault_read": ("omega_engine.vault", "vault_read"),
|
|
178
|
+
"vault_status": ("omega_engine.vault", "vault_status"),
|
|
179
|
+
"vault_write": ("omega_engine.vault", "vault_write"),
|
|
180
|
+
# task model + FSM
|
|
181
|
+
"TERMINAL": ("omega_engine.task", "TERMINAL"),
|
|
182
|
+
"Budget": ("omega_engine.task", "Budget"),
|
|
183
|
+
"Kind": ("omega_engine.task", "Kind"),
|
|
184
|
+
"Lifecycle": ("omega_engine.task", "Lifecycle"),
|
|
185
|
+
"Task": ("omega_engine.task", "Task"),
|
|
186
|
+
"TaskState": ("omega_engine.task", "TaskState"),
|
|
187
|
+
"Trigger": ("omega_engine.task", "Trigger"),
|
|
188
|
+
# tools
|
|
189
|
+
"Tool": ("omega_engine.tools", "Tool"),
|
|
190
|
+
"ToolRegistry": ("omega_engine.tools", "ToolRegistry"),
|
|
191
|
+
"install_from_catalog": ("omega_engine.tools", "install_from_catalog"),
|
|
192
|
+
# worker — persistent tmux worker briefs
|
|
193
|
+
"WorkerBrief": ("omega_engine.worker", "WorkerBrief"),
|
|
194
|
+
"default_model_for_role": ("omega_engine.worker", "default_model_for_role"),
|
|
195
|
+
"list_workers": ("omega_engine.worker", "list_workers"),
|
|
196
|
+
"read_brief": ("omega_engine.worker", "read_brief"),
|
|
197
|
+
"run_brief": ("omega_engine.worker", "run_brief"),
|
|
198
|
+
"spawn_worker": ("omega_engine.worker", "spawn_worker"),
|
|
199
|
+
"wait_for_done": ("omega_engine.worker", "wait_for_done"),
|
|
200
|
+
"worker_status": ("omega_engine.worker", "worker_status"),
|
|
201
|
+
"write_brief": ("omega_engine.worker", "write_brief"),
|
|
202
|
+
# cleanup
|
|
203
|
+
"TierResult": ("omega_engine.cleanup", "TierResult"),
|
|
204
|
+
"run_cleanup": ("omega_engine.cleanup", "cleanup"),
|
|
205
|
+
"cleanup_summary": ("omega_engine.cleanup", "format_summary"),
|
|
206
|
+
# skill_routing
|
|
207
|
+
"POWER_SKILLS": ("omega_engine.skill_routing", "POWER_SKILLS"),
|
|
208
|
+
"skill_catalog_for_home": ("omega_engine.skill_routing", "catalog_for_home"),
|
|
209
|
+
"format_skill_block": ("omega_engine.skill_routing", "format_skill_block"),
|
|
210
|
+
"recommend_skills": ("omega_engine.skill_routing", "recommend"),
|
|
211
|
+
"skills_for": ("omega_engine.skill_routing", "skills_for"),
|
|
212
|
+
# genesis
|
|
213
|
+
"GenesisOrchestrator": ("omega_engine.genesis", "GenesisOrchestrator"),
|
|
214
|
+
"GenesisState": ("omega_engine.genesis", "GenesisState"),
|
|
215
|
+
"PhaseResult": ("omega_engine.genesis", "PhaseResult"),
|
|
216
|
+
"Stack": ("omega_engine.genesis", "Stack"),
|
|
217
|
+
"StackConfig": ("omega_engine.genesis", "StackConfig"),
|
|
218
|
+
"StackQuestion": ("omega_engine.genesis", "StackQuestion"),
|
|
219
|
+
"genesis_init_project": ("omega_engine.genesis", "init_project"),
|
|
220
|
+
"genesis_load_state": ("omega_engine.genesis", "load_state"),
|
|
221
|
+
"pick_stack": ("omega_engine.genesis", "pick_stack"),
|
|
222
|
+
"genesis_project_root": ("omega_engine.genesis", "project_root"),
|
|
223
|
+
"stack_questions": ("omega_engine.genesis", "stack_questions"),
|
|
224
|
+
# plan submodule re-export (was: `from omega_engine import plan as plan_v7`)
|
|
225
|
+
"plan_v7": ("omega_engine.plan", None),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def __getattr__(name: str) -> Any:
|
|
230
|
+
"""PEP 562 lazy attribute loader.
|
|
231
|
+
|
|
232
|
+
Resolves the import on first access, caches the value on this module so
|
|
233
|
+
subsequent lookups bypass __getattr__. Submodules (``omega_engine.tmux``
|
|
234
|
+
etc.) are NOT in _LAZY_MAP; Python's standard submodule import handles
|
|
235
|
+
those automatically when they're referenced as ``omega_engine.<name>`` or
|
|
236
|
+
``from omega_engine import <submodule>``.
|
|
237
|
+
"""
|
|
238
|
+
entry = _LAZY_MAP.get(name)
|
|
239
|
+
if entry is None:
|
|
240
|
+
raise AttributeError(
|
|
241
|
+
f"module 'omega_engine' has no attribute {name!r}"
|
|
242
|
+
)
|
|
243
|
+
mod_path, attr = entry
|
|
244
|
+
module = importlib.import_module(mod_path)
|
|
245
|
+
value = module if attr is None else getattr(module, attr)
|
|
246
|
+
# Cache so subsequent lookups are direct dict reads.
|
|
247
|
+
globals()[name] = value
|
|
248
|
+
return value
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def __dir__() -> list[str]:
|
|
252
|
+
"""Make tab-completion and introspection show all public names without
|
|
253
|
+
triggering a load."""
|
|
254
|
+
return sorted(list(globals().keys()) + list(_LAZY_MAP.keys()))
|
|
190
255
|
|
|
191
|
-
__version__ = "0.19.52"
|
|
192
256
|
|
|
193
257
|
__all__ = [
|
|
194
258
|
"__version__",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2949,11 +2949,28 @@ def _attach_or_spawn_chat(session_name: str, *,
|
|
|
2949
2949
|
print(f" could not spawn {label}: {exc}")
|
|
2950
2950
|
print(f" try manually: tmux new -d -s {session_name}")
|
|
2951
2951
|
return 2
|
|
2952
|
-
# Inside tmux already?
|
|
2952
|
+
# Inside tmux already? Do the switch ourselves — v0.19.54 fix.
|
|
2953
|
+
# Previously we PRINTED instructions ("Switch with: tmux switch-client
|
|
2954
|
+
# -t Omega:hermes") and exited, leaving the user staring at a useless
|
|
2955
|
+
# hint. Same bug pattern as cmd_menu had in v0.19.40 (fixed v0.19.41).
|
|
2956
|
+
# tmux switch-client works fine from inside a tmux client and is
|
|
2957
|
+
# exactly what the user wants.
|
|
2953
2958
|
if os.environ.get("TMUX"):
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2959
|
+
import subprocess as _sp
|
|
2960
|
+
# Window of Omega session, or standalone session — try
|
|
2961
|
+
# select-window first (for Omega:<name>), then switch-client.
|
|
2962
|
+
if ":" in session_name:
|
|
2963
|
+
sess, win = session_name.split(":", 1)
|
|
2964
|
+
rc = _sp.run(["tmux", "select-window", "-t", session_name],
|
|
2965
|
+
capture_output=True).returncode
|
|
2966
|
+
if rc != 0:
|
|
2967
|
+
_sp.run(["tmux", "switch-client", "-t", sess],
|
|
2968
|
+
capture_output=True)
|
|
2969
|
+
_sp.run(["tmux", "select-window", "-t", session_name],
|
|
2970
|
+
capture_output=True)
|
|
2971
|
+
else:
|
|
2972
|
+
_sp.run(["tmux", "switch-client", "-t", session_name],
|
|
2973
|
+
capture_output=True)
|
|
2957
2974
|
return 0
|
|
2958
2975
|
# Outside tmux — replace this process with `tmux attach`.
|
|
2959
2976
|
print(f" attaching to {label} ({session_name})…")
|
|
@@ -3577,9 +3594,21 @@ def cmd_hermes(args: argparse.Namespace) -> int:
|
|
|
3577
3594
|
if sub == "chat-loop":
|
|
3578
3595
|
# REPL that runs inside the Hermes-chat tmux session.
|
|
3579
3596
|
# Uses Anthropic API directly (separate from AISB's Max OAuth).
|
|
3597
|
+
#
|
|
3598
|
+
# v0.19.53 — DO NOT `import os, sys` here. Both are already
|
|
3599
|
+
# imported at module scope (lines 11–12). Re-importing them
|
|
3600
|
+
# inside this branch made Python treat `sys` and `os` as
|
|
3601
|
+
# local-to-`cmd_hermes` for the WHOLE function — so the
|
|
3602
|
+
# `ask` / `env` / `brief observe` / help-fallback branches
|
|
3603
|
+
# then crashed with `UnboundLocalError: cannot access local
|
|
3604
|
+
# variable 'sys'` when their try/except caught a HermesError
|
|
3605
|
+
# and tried `print(..., file=sys.stderr)`. End result for a
|
|
3606
|
+
# fresh Mac install: `omega hermes ask hi` printed a raw
|
|
3607
|
+
# traceback instead of `error: \`hermes\` not on PATH — run
|
|
3608
|
+
# \`omega hermes install\``. The user reported "hermes doesn't
|
|
3609
|
+
# work" because of this misleading crash.
|
|
3580
3610
|
from omega_engine.aisb_chat import chat_once
|
|
3581
3611
|
from omega_engine.vault import vault_read
|
|
3582
|
-
import os, sys
|
|
3583
3612
|
home = _omega_home()
|
|
3584
3613
|
api_key = (vault_read(home, "ANTHROPIC_API_KEY_HERMES") or "").strip()
|
|
3585
3614
|
if not api_key:
|
|
@@ -20,7 +20,13 @@ DEFAULT_PROVIDER = "claude_code"
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _omega_home() -> Path:
|
|
23
|
-
|
|
23
|
+
# v0.19.53 — avoid Path.home() on Mac uv-Python (slow: 200-500ms
|
|
24
|
+
# via pwd.getpwuid() + DirectoryService lookup). Use $HOME env
|
|
25
|
+
# directly via os.path.expanduser — milliseconds.
|
|
26
|
+
omega = os.environ.get("OMEGA_HOME")
|
|
27
|
+
if omega:
|
|
28
|
+
return Path(omega)
|
|
29
|
+
return Path(os.path.expanduser("~/Omega"))
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
def provider_state_path() -> Path:
|
|
@@ -631,6 +631,13 @@ bind-key S choose-tree -Zs
|
|
|
631
631
|
bind-key -n M-/ display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
|
|
632
632
|
bind-key -n M-z display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
|
|
633
633
|
|
|
634
|
+
# Fallback for terminals that don't send Esc+ on Option+key (e.g. macOS
|
|
635
|
+
# Terminal.app without "Option as Meta", Termius default). These bind
|
|
636
|
+
# the LITERAL Unicode character that Option+Z / Option+/ produces.
|
|
637
|
+
# Safe — these characters are extremely rarely typed intentionally.
|
|
638
|
+
bind-key -n "Ω" display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
|
|
639
|
+
bind-key -n "÷" display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
|
|
640
|
+
|
|
634
641
|
# ════════════════════════════════════════════════════════════════════
|
|
635
642
|
# Pixel/CRT amber theme (tmux-claude inspired)
|
|
636
643
|
# xterm colour137 ≈ #af8700 — amber/gold on whatever your terminal bg is.
|
|
@@ -768,6 +775,13 @@ bind-key r source-file ~/.tmux.conf \\; display-message "tmux.conf reloaded"
|
|
|
768
775
|
bind-key -n M-/ display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
|
|
769
776
|
bind-key -n M-z display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
|
|
770
777
|
|
|
778
|
+
# Fallback for terminals that don't send Esc+ on Option+key (e.g. macOS
|
|
779
|
+
# Terminal.app without "Option as Meta", Termius default). These bind
|
|
780
|
+
# the LITERAL Unicode character that Option+Z / Option+/ produces.
|
|
781
|
+
# Safe — these characters are extremely rarely typed intentionally.
|
|
782
|
+
bind-key -n "Ω" display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
|
|
783
|
+
bind-key -n "÷" display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
|
|
784
|
+
|
|
771
785
|
# ════════════════════════════════════════════════════════════════════
|
|
772
786
|
# Pixel/CRT amber theme (tmux-claude inspired)
|
|
773
787
|
# ════════════════════════════════════════════════════════════════════
|
|
@@ -48,16 +48,26 @@ import shlex
|
|
|
48
48
|
from pathlib import Path
|
|
49
49
|
from typing import Any
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
# v0.19.53 — defer the textual import to runtime. Importing
|
|
52
|
+
# ``textual.app`` triggers ~80-150ms of import cost on macOS uv-Python (60+
|
|
53
|
+
# transitive modules: rich, markupsafe, jinja-ish, etc.). The default
|
|
54
|
+
# ``omega`` UX is the fzf arrow-key menu (``_arrow_menu``), which does NOT
|
|
55
|
+
# need textual at all. We only need it when the user explicitly opts in
|
|
56
|
+
# with ``--textual``. So we probe lazily and build the class on demand.
|
|
57
|
+
TEXTUAL_AVAILABLE: bool | None = None # tri-state: None = not probed yet
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _probe_textual() -> bool:
|
|
61
|
+
"""Return True iff the ``textual`` package is importable. Caches the
|
|
62
|
+
answer so the probe only runs once per process."""
|
|
63
|
+
global TEXTUAL_AVAILABLE
|
|
64
|
+
if TEXTUAL_AVAILABLE is None:
|
|
65
|
+
try:
|
|
66
|
+
import importlib.util as _util
|
|
67
|
+
TEXTUAL_AVAILABLE = _util.find_spec("textual") is not None
|
|
68
|
+
except Exception: # noqa: BLE001
|
|
69
|
+
TEXTUAL_AVAILABLE = False
|
|
70
|
+
return TEXTUAL_AVAILABLE
|
|
61
71
|
|
|
62
72
|
|
|
63
73
|
# ---------------------------------------------------------------------------
|
|
@@ -256,7 +266,29 @@ def dispatch_slash(line: str) -> CommandResult:
|
|
|
256
266
|
# ---------------------------------------------------------------------------
|
|
257
267
|
|
|
258
268
|
|
|
259
|
-
|
|
269
|
+
def _build_omega_tui_class():
|
|
270
|
+
"""Build the ``OmegaTUI`` class on first request.
|
|
271
|
+
|
|
272
|
+
v0.19.53 — previously the class lived at module level inside
|
|
273
|
+
``if TEXTUAL_AVAILABLE: class OmegaTUI(App): ...``, which forced
|
|
274
|
+
``textual.app`` (+ 60 transitive modules including ``rich``) to load
|
|
275
|
+
every time ``omega_engine.tui`` was imported — even for the default
|
|
276
|
+
fzf arrow-key menu that never touches textual.
|
|
277
|
+
|
|
278
|
+
Building the class on demand keeps the default ``omega`` startup path
|
|
279
|
+
free of the textual import cost. The class is cached on a function
|
|
280
|
+
attribute so subsequent calls return the same class.
|
|
281
|
+
"""
|
|
282
|
+
cached = getattr(_build_omega_tui_class, "_cls", None)
|
|
283
|
+
if cached is not None:
|
|
284
|
+
return cached
|
|
285
|
+
|
|
286
|
+
from textual.app import App, ComposeResult
|
|
287
|
+
from textual.widgets import (
|
|
288
|
+
Header, Footer, Input, RichLog, ListView, ListItem, Label,
|
|
289
|
+
)
|
|
290
|
+
from textual.containers import Horizontal, Vertical
|
|
291
|
+
from textual.binding import Binding
|
|
260
292
|
|
|
261
293
|
class OmegaTUI(App):
|
|
262
294
|
"""OmegaOS Textual TUI — the v0.19.27 control surface."""
|
|
@@ -409,6 +441,9 @@ if TEXTUAL_AVAILABLE:
|
|
|
409
441
|
if res.session_jump:
|
|
410
442
|
_switch_client(res.session_jump)
|
|
411
443
|
|
|
444
|
+
_build_omega_tui_class._cls = OmegaTUI # type: ignore[attr-defined]
|
|
445
|
+
return OmegaTUI
|
|
446
|
+
|
|
412
447
|
|
|
413
448
|
def _arrow_menu() -> int:
|
|
414
449
|
"""v0.19.30 — fzf-driven arrow-key menu (the default `omega` UX).
|
|
@@ -962,8 +997,9 @@ def run_tui(prefer_textual: bool = False,
|
|
|
962
997
|
- `--repl` → `_plain_repl()` (input() loop, slash commands)
|
|
963
998
|
- `--textual` → Textual TUI (opt-in, may break under tmux)
|
|
964
999
|
"""
|
|
965
|
-
if prefer_textual and
|
|
1000
|
+
if prefer_textual and _probe_textual():
|
|
966
1001
|
try:
|
|
1002
|
+
OmegaTUI = _build_omega_tui_class()
|
|
967
1003
|
app = OmegaTUI()
|
|
968
1004
|
app.run()
|
|
969
1005
|
return 0
|
package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313-pytest-8.4.2.pyc
CHANGED
|
Binary file
|
|
Binary file
|
package/omega/Agentik_Engine/tests/__pycache__/test_v19_53_cli_commands.cpython-313-pytest-8.4.2.pyc
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -247,6 +247,19 @@ class TestTmuxBindsFixedAndPathsAbsolute(unittest.TestCase):
|
|
|
247
247
|
self.assertIn('"Omega:hermes"', src,
|
|
248
248
|
"spawn_hermes_chat must return 'Omega:hermes'")
|
|
249
249
|
|
|
250
|
+
def test_unicode_fallback_binds_for_macos_terminal(self):
|
|
251
|
+
"""v0.19.53 — Option+Z / Option+/ on macOS Terminal.app (without
|
|
252
|
+
Option=Meta config) sends Unicode Ω / ÷. Bind those characters
|
|
253
|
+
too as a fallback so the menu fires anyway."""
|
|
254
|
+
from omega_engine.tmux import _PRO_CONFIG, _DEFAULT_CONFIG
|
|
255
|
+
for name, cfg in (("_PRO_CONFIG", _PRO_CONFIG),
|
|
256
|
+
("_DEFAULT_CONFIG", _DEFAULT_CONFIG)):
|
|
257
|
+
self.assertIn('"Ω"', cfg,
|
|
258
|
+
f"{name} must bind the literal Ω character (Option+Z on macOS) "
|
|
259
|
+
f"as a fallback for terminals without Option=Meta config")
|
|
260
|
+
self.assertIn('"÷"', cfg,
|
|
261
|
+
f"{name} must bind the literal ÷ character (Option+/ on macOS)")
|
|
262
|
+
|
|
250
263
|
|
|
251
264
|
if __name__ == "__main__":
|
|
252
265
|
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Regression tests for v0.19.53 — ``omega hermes`` and ``omega paperclip``
|
|
2
|
+
must produce user-friendly output (or open the chat session), NEVER crash
|
|
3
|
+
with a raw Python traceback.
|
|
4
|
+
|
|
5
|
+
User-reported bug on a fresh Mac install (v0.19.51): both commands
|
|
6
|
+
"don't work". Diagnosis traced two distinct issues:
|
|
7
|
+
|
|
8
|
+
Bug 1 — UnboundLocalError in ``cmd_hermes``
|
|
9
|
+
--------------------------------------------
|
|
10
|
+
``omega hermes ask "anything"`` crashed with::
|
|
11
|
+
|
|
12
|
+
UnboundLocalError: cannot access local variable 'sys' where it
|
|
13
|
+
is not associated with a value
|
|
14
|
+
|
|
15
|
+
Root cause: the ``chat-loop`` branch had ``import os, sys`` inside
|
|
16
|
+
the function. Python's static-binding rule then treated ``sys``
|
|
17
|
+
(and ``os``) as local to the WHOLE function — so the ``ask`` /
|
|
18
|
+
``env`` / ``brief observe`` / help-fallback branches blew up when
|
|
19
|
+
their ``except HermesError`` handler tried ``print(..., file=
|
|
20
|
+
sys.stderr)``.
|
|
21
|
+
|
|
22
|
+
Visible consequence on a fresh install (where Hermes is not yet
|
|
23
|
+
installed, so ``H.ask`` raises ``HermesError``): the user saw a
|
|
24
|
+
raw 15-line Python traceback instead of the intended friendly
|
|
25
|
+
``error: \\`hermes\\` not on PATH — run \\`omega hermes install\\``.
|
|
26
|
+
|
|
27
|
+
Fix (cli.py only): remove the redundant ``import os, sys`` from
|
|
28
|
+
the ``chat-loop`` branch — both are already imported at module
|
|
29
|
+
scope.
|
|
30
|
+
|
|
31
|
+
Bug 2 — paperclip works but the test surface had no regression
|
|
32
|
+
---------------------------------------------------------------
|
|
33
|
+
``omega paperclip`` (no subcommand) and ``omega paperclip status``
|
|
34
|
+
already produce friendly output on an un-installed Paperclip
|
|
35
|
+
(defaulting to ``status`` via ``sub = ... or "status"``). We lock
|
|
36
|
+
that behaviour in so a future refactor cannot regress it.
|
|
37
|
+
|
|
38
|
+
Each test exercises the user-visible behaviour, not implementation
|
|
39
|
+
details — a later refactor of the friendly-error wording is fine as
|
|
40
|
+
long as the command still exits cleanly with help, not a traceback.
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import io
|
|
45
|
+
import os
|
|
46
|
+
import sys
|
|
47
|
+
import tempfile
|
|
48
|
+
import unittest
|
|
49
|
+
from contextlib import redirect_stderr, redirect_stdout
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from unittest import mock
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
HERE = Path(__file__).resolve().parent
|
|
55
|
+
ENGINE_ROOT = HERE.parent
|
|
56
|
+
|
|
57
|
+
# Make sure ``omega_engine`` is importable even when pytest is run from
|
|
58
|
+
# the project root.
|
|
59
|
+
sys.path.insert(0, str(ENGINE_ROOT))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _run_omega(argv: list[str]) -> tuple[int, str, str]:
|
|
63
|
+
"""Invoke ``omega_engine.cli.main`` in-process with the given argv.
|
|
64
|
+
|
|
65
|
+
Returns ``(rc, stdout, stderr)``. Swallows ``SystemExit`` so callers
|
|
66
|
+
can assert on the exit code without an exception bubbling up.
|
|
67
|
+
"""
|
|
68
|
+
from omega_engine.cli import main
|
|
69
|
+
|
|
70
|
+
out, err = io.StringIO(), io.StringIO()
|
|
71
|
+
rc = 0
|
|
72
|
+
saved_argv = sys.argv[:]
|
|
73
|
+
sys.argv = ["omega"] + argv
|
|
74
|
+
try:
|
|
75
|
+
with redirect_stdout(out), redirect_stderr(err):
|
|
76
|
+
try:
|
|
77
|
+
rc = main()
|
|
78
|
+
except SystemExit as exc: # argparse exits via SystemExit
|
|
79
|
+
rc = int(exc.code or 0)
|
|
80
|
+
finally:
|
|
81
|
+
sys.argv = saved_argv
|
|
82
|
+
return rc, out.getvalue(), err.getvalue()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestHermesCLI(unittest.TestCase):
|
|
86
|
+
"""`omega hermes ...` must never print a raw traceback."""
|
|
87
|
+
|
|
88
|
+
def setUp(self) -> None:
|
|
89
|
+
self._tmp = tempfile.mkdtemp(prefix="omega-test-")
|
|
90
|
+
self._home = Path(self._tmp) / "Omega"
|
|
91
|
+
(self._home / "Agentik_Extra" / "var" / "logs").mkdir(
|
|
92
|
+
parents=True, exist_ok=True
|
|
93
|
+
)
|
|
94
|
+
self._env_patch = mock.patch.dict(
|
|
95
|
+
os.environ, {"OMEGA_HOME": str(self._home)}, clear=False,
|
|
96
|
+
)
|
|
97
|
+
self._env_patch.start()
|
|
98
|
+
|
|
99
|
+
def tearDown(self) -> None:
|
|
100
|
+
self._env_patch.stop()
|
|
101
|
+
import shutil as _sh
|
|
102
|
+
_sh.rmtree(self._tmp, ignore_errors=True)
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------
|
|
105
|
+
# Bug 1 — UnboundLocalError regression (the headline fix)
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
def test_omega_hermes_ask_with_no_hermes_installed_gives_friendly_error(
|
|
108
|
+
self,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""When hermes is not on PATH (fresh install), ``omega hermes ask``
|
|
111
|
+
MUST print a friendly one-liner and exit non-zero — NOT crash
|
|
112
|
+
with ``UnboundLocalError: ... 'sys'``.
|
|
113
|
+
"""
|
|
114
|
+
from omega_engine import hermes as H
|
|
115
|
+
|
|
116
|
+
# Simulate "hermes binary not installed" — ``H.ask`` raises
|
|
117
|
+
# HermesError. The cli handler must catch it and print a hint.
|
|
118
|
+
with mock.patch.object(
|
|
119
|
+
H, "ask",
|
|
120
|
+
side_effect=H.HermesError(
|
|
121
|
+
"`hermes` not on PATH — run `omega hermes install`"
|
|
122
|
+
),
|
|
123
|
+
):
|
|
124
|
+
rc, out, err = _run_omega(["hermes", "ask", "hi"])
|
|
125
|
+
|
|
126
|
+
# The bug was UnboundLocalError — surface it loudly if it ever
|
|
127
|
+
# comes back.
|
|
128
|
+
full = out + err
|
|
129
|
+
self.assertNotIn("UnboundLocalError", full,
|
|
130
|
+
msg=f"UnboundLocalError regression:\n{full}")
|
|
131
|
+
self.assertNotIn("Traceback", full,
|
|
132
|
+
msg=f"raw traceback leaked to user:\n{full}")
|
|
133
|
+
# Friendly hint must be on stderr (where the handler writes it).
|
|
134
|
+
self.assertIn("hermes", err.lower())
|
|
135
|
+
# And we exit with rc=2 (the existing contract for an ask failure).
|
|
136
|
+
self.assertEqual(rc, 2)
|
|
137
|
+
|
|
138
|
+
def test_omega_hermes_env_friendly_error_when_creds_unavailable(self) -> None:
|
|
139
|
+
"""`omega hermes env` MUST handle ``HermesError`` from ``build_env``
|
|
140
|
+
without crashing on ``sys`` being unbound.
|
|
141
|
+
"""
|
|
142
|
+
from omega_engine import hermes as H
|
|
143
|
+
|
|
144
|
+
with mock.patch.object(
|
|
145
|
+
H, "build_env",
|
|
146
|
+
side_effect=H.HermesError("no Claude OAuth token found"),
|
|
147
|
+
):
|
|
148
|
+
rc, out, err = _run_omega(["hermes", "env"])
|
|
149
|
+
|
|
150
|
+
full = out + err
|
|
151
|
+
self.assertNotIn("UnboundLocalError", full)
|
|
152
|
+
self.assertNotIn("Traceback", full)
|
|
153
|
+
self.assertEqual(rc, 2)
|
|
154
|
+
self.assertIn("no Claude OAuth token", err)
|
|
155
|
+
|
|
156
|
+
# ------------------------------------------------------------------
|
|
157
|
+
# `omega hermes status` — should always work, even without hermes
|
|
158
|
+
# ------------------------------------------------------------------
|
|
159
|
+
def test_omega_hermes_status_renders_without_crashing(self) -> None:
|
|
160
|
+
"""`omega hermes status` reads the in-process probe and prints a
|
|
161
|
+
snapshot. It must succeed even if hermes is not installed.
|
|
162
|
+
"""
|
|
163
|
+
rc, out, err = _run_omega(["hermes", "status"])
|
|
164
|
+
|
|
165
|
+
self.assertNotIn("UnboundLocalError", out + err)
|
|
166
|
+
self.assertNotIn("Traceback", out + err)
|
|
167
|
+
# Exit code 0 (all green) or 1 (something missing) — both are
|
|
168
|
+
# legitimate; what matters is no crash.
|
|
169
|
+
self.assertIn(rc, (0, 1))
|
|
170
|
+
# Output mentions the canonical fields.
|
|
171
|
+
self.assertIn("hermes", out.lower())
|
|
172
|
+
|
|
173
|
+
def test_omega_hermes_status_json_emits_parsable_json(self) -> None:
|
|
174
|
+
"""`omega hermes status --json` must emit valid JSON, not a
|
|
175
|
+
traceback — used by external tooling.
|
|
176
|
+
"""
|
|
177
|
+
import json as _json
|
|
178
|
+
|
|
179
|
+
rc, out, err = _run_omega(["hermes", "status", "--json"])
|
|
180
|
+
self.assertNotIn("Traceback", out + err)
|
|
181
|
+
self.assertIn(rc, (0, 1))
|
|
182
|
+
# The JSON object lives on stdout. Parse the first complete
|
|
183
|
+
# JSON document we find (the handler emits one indented object).
|
|
184
|
+
try:
|
|
185
|
+
parsed = _json.loads(out)
|
|
186
|
+
except _json.JSONDecodeError as exc:
|
|
187
|
+
self.fail(f"--json did not produce parseable JSON: {exc}\n{out!r}")
|
|
188
|
+
self.assertIn("installed", parsed)
|
|
189
|
+
self.assertIn("hermes_home", parsed)
|
|
190
|
+
|
|
191
|
+
# ------------------------------------------------------------------
|
|
192
|
+
# `omega hermes install --dry-run` — must not actually run install.sh
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
def test_omega_hermes_install_dry_run_prints_command(self) -> None:
|
|
195
|
+
"""`omega hermes install --dry-run` prints the curl command it
|
|
196
|
+
WOULD run, exits 0, and does NOT crash.
|
|
197
|
+
"""
|
|
198
|
+
rc, out, err = _run_omega(["hermes", "install", "--dry-run"])
|
|
199
|
+
self.assertNotIn("Traceback", out + err)
|
|
200
|
+
self.assertEqual(rc, 0)
|
|
201
|
+
# Either "dry-run" appears or the install URL — both prove the
|
|
202
|
+
# branch executed.
|
|
203
|
+
self.assertTrue(
|
|
204
|
+
"dry-run" in out.lower() or "github" in out.lower(),
|
|
205
|
+
msg=f"unexpected output:\n{out}",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestPaperclipCLI(unittest.TestCase):
|
|
210
|
+
"""`omega paperclip` must default to a useful status snapshot, and
|
|
211
|
+
its subcommands must never blow up with a Python traceback."""
|
|
212
|
+
|
|
213
|
+
def setUp(self) -> None:
|
|
214
|
+
self._tmp = tempfile.mkdtemp(prefix="omega-test-")
|
|
215
|
+
self._home = Path(self._tmp) / "Omega"
|
|
216
|
+
self._pc_home = Path(self._tmp) / ".paperclip"
|
|
217
|
+
(self._home / "Agentik_Extra" / "var" / "logs").mkdir(
|
|
218
|
+
parents=True, exist_ok=True
|
|
219
|
+
)
|
|
220
|
+
self._env_patch = mock.patch.dict(
|
|
221
|
+
os.environ,
|
|
222
|
+
{
|
|
223
|
+
"OMEGA_HOME": str(self._home),
|
|
224
|
+
"PAPERCLIP_HOME": str(self._pc_home),
|
|
225
|
+
},
|
|
226
|
+
clear=False,
|
|
227
|
+
)
|
|
228
|
+
self._env_patch.start()
|
|
229
|
+
|
|
230
|
+
def tearDown(self) -> None:
|
|
231
|
+
self._env_patch.stop()
|
|
232
|
+
import shutil as _sh
|
|
233
|
+
_sh.rmtree(self._tmp, ignore_errors=True)
|
|
234
|
+
|
|
235
|
+
def test_omega_paperclip_no_args_runs_status_without_crashing(self) -> None:
|
|
236
|
+
"""Bare ``omega paperclip`` (no subcommand) MUST default to
|
|
237
|
+
``status`` and render a snapshot — not crash, not show argparse
|
|
238
|
+
help (which exits non-zero from the user's POV).
|
|
239
|
+
"""
|
|
240
|
+
rc, out, err = _run_omega(["paperclip"])
|
|
241
|
+
self.assertNotIn("Traceback", out + err)
|
|
242
|
+
self.assertEqual(rc, 0)
|
|
243
|
+
# Status output mentions the canonical fields.
|
|
244
|
+
self.assertIn("paperclip", out.lower())
|
|
245
|
+
self.assertIn("agents registered", out.lower())
|
|
246
|
+
|
|
247
|
+
def test_omega_paperclip_status_renders_canonical_fields(self) -> None:
|
|
248
|
+
"""`omega paperclip status` always prints the 7 canonical fields:
|
|
249
|
+
installed / paperclip_home / omegaos company / agents registered /
|
|
250
|
+
workspaces / heartbeats pending / bridge_version."""
|
|
251
|
+
rc, out, err = _run_omega(["paperclip", "status"])
|
|
252
|
+
self.assertNotIn("Traceback", out + err)
|
|
253
|
+
self.assertEqual(rc, 0)
|
|
254
|
+
low = out.lower()
|
|
255
|
+
for needle in (
|
|
256
|
+
"paperclip installed",
|
|
257
|
+
"paperclip_home",
|
|
258
|
+
"omegaos company",
|
|
259
|
+
"agents registered",
|
|
260
|
+
"workspaces",
|
|
261
|
+
"heartbeats pending",
|
|
262
|
+
"bridge_version",
|
|
263
|
+
):
|
|
264
|
+
self.assertIn(needle, low,
|
|
265
|
+
msg=f"missing field {needle!r} in:\n{out}")
|
|
266
|
+
|
|
267
|
+
def test_omega_paperclip_register_dry_run_writes_14_agents(self) -> None:
|
|
268
|
+
"""`omega paperclip register --dry-run` must report 14 agents
|
|
269
|
+
written (Hermès + 13 AISB suite — the contract from v0.19.33)."""
|
|
270
|
+
rc, out, err = _run_omega(["paperclip", "register", "--dry-run"])
|
|
271
|
+
self.assertNotIn("Traceback", out + err)
|
|
272
|
+
self.assertEqual(rc, 0)
|
|
273
|
+
# Output is `key: value` lines from PB.register's summary dict.
|
|
274
|
+
self.assertIn("agents_written: 14", out)
|
|
275
|
+
self.assertIn("dry_run: True", out)
|
|
276
|
+
|
|
277
|
+
def test_omega_paperclip_url_default_loopback(self) -> None:
|
|
278
|
+
"""`omega paperclip url` prints a dashboard URL, no traceback."""
|
|
279
|
+
rc, out, err = _run_omega(["paperclip", "url"])
|
|
280
|
+
self.assertNotIn("Traceback", out + err)
|
|
281
|
+
self.assertEqual(rc, 0)
|
|
282
|
+
self.assertIn("paperclip dashboard", out.lower())
|
|
283
|
+
|
|
284
|
+
def test_omega_paperclip_remote_setup_prints_help(self) -> None:
|
|
285
|
+
"""`omega paperclip remote-setup` prints the SSH/Tailscale/Tunnel
|
|
286
|
+
instructions without crashing."""
|
|
287
|
+
rc, out, err = _run_omega(["paperclip", "remote-setup"])
|
|
288
|
+
self.assertNotIn("Traceback", out + err)
|
|
289
|
+
self.assertEqual(rc, 0)
|
|
290
|
+
self.assertGreater(len(out.strip()), 0,
|
|
291
|
+
msg="remote-setup produced no output")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class TestCmdHermesNoLocalImports(unittest.TestCase):
|
|
295
|
+
"""White-box guard: ensure ``cmd_hermes`` never re-imports ``sys`` or
|
|
296
|
+
``os`` in a way that would shadow the module-level imports.
|
|
297
|
+
|
|
298
|
+
This is the structural invariant that catches Bug 1 at the source.
|
|
299
|
+
If a future commit adds ``import sys`` (or ``import os, sys``, or
|
|
300
|
+
``from sys import ...``) inside ``cmd_hermes``, this test fails
|
|
301
|
+
immediately, with a clear pointer to the v0.19.53 root cause.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def test_cmd_hermes_does_not_reimport_sys_or_os_locally(self) -> None:
|
|
305
|
+
import ast
|
|
306
|
+
import inspect
|
|
307
|
+
|
|
308
|
+
from omega_engine import cli as _cli
|
|
309
|
+
|
|
310
|
+
src = inspect.getsource(_cli.cmd_hermes)
|
|
311
|
+
# Trim leading indentation so ast.parse can handle the snippet.
|
|
312
|
+
import textwrap
|
|
313
|
+
tree = ast.parse(textwrap.dedent(src))
|
|
314
|
+
|
|
315
|
+
offenders: list[str] = []
|
|
316
|
+
|
|
317
|
+
class _Visitor(ast.NodeVisitor):
|
|
318
|
+
def visit_Import(self, node: ast.Import) -> None: # noqa: N802
|
|
319
|
+
for alias in node.names:
|
|
320
|
+
if alias.name in {"sys", "os"} and alias.asname is None:
|
|
321
|
+
offenders.append(
|
|
322
|
+
f"line {node.lineno}: `import {alias.name}` "
|
|
323
|
+
f"shadows module-level import"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def visit_ImportFrom( # noqa: N802
|
|
327
|
+
self, node: ast.ImportFrom,
|
|
328
|
+
) -> None:
|
|
329
|
+
if node.module in {"sys", "os"}:
|
|
330
|
+
for alias in node.names:
|
|
331
|
+
if alias.asname is None and alias.name != "*":
|
|
332
|
+
offenders.append(
|
|
333
|
+
f"line {node.lineno}: "
|
|
334
|
+
f"`from {node.module} import {alias.name}`"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
_Visitor().visit(tree)
|
|
338
|
+
self.assertEqual(
|
|
339
|
+
offenders, [],
|
|
340
|
+
msg=(
|
|
341
|
+
"cmd_hermes re-imports `sys` / `os` locally — this "
|
|
342
|
+
"shadows the module-level imports and breaks branches "
|
|
343
|
+
"that reference them in `except` handlers (the v0.19.53 "
|
|
344
|
+
"UnboundLocalError bug). Offenders:\n "
|
|
345
|
+
+ "\n ".join(offenders)
|
|
346
|
+
),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
if __name__ == "__main__": # pragma: no cover
|
|
351
|
+
unittest.main()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.19.
|
|
1
|
+
0.19.54
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentikos/omega-os",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.54",
|
|
4
4
|
"description": "Omega OS — installable agentic operating system with verified-completion orchestration. Event-sourced engine, 8-block rack, autonomous agents, MCP.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"omega-os": "bin/omega-os.js"
|