@agentikos/omega-os 0.1.0 → 0.19.5
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/README.md +56 -14
- package/bootstrap/lib/__pycache__/claude-code-settings.cpython-313.pyc +0 -0
- package/bootstrap/lib/__pycache__/llm-clis.cpython-313.pyc +0 -0
- package/bootstrap/lib/__pycache__/manifest-helpers.cpython-313.pyc +0 -0
- package/bootstrap/lib/claude-code-settings.py +176 -0
- package/bootstrap/lib/common.sh +457 -1
- package/bootstrap/lib/llm-clis.py +341 -0
- package/bootstrap/lib/manifest-helpers.py +384 -0
- package/bootstrap/lib/steps.sh +1000 -26
- package/bootstrap/manifest.example.yaml +93 -2
- package/bootstrap/templates/aisb/CLAUDE.md +305 -0
- package/bootstrap/templates/aisb/architect.md +204 -0
- package/bootstrap/templates/aisb/checkers/CLAUDE.md +9 -0
- package/bootstrap/templates/aisb/checkers/checker-architect.md +151 -0
- package/bootstrap/templates/aisb/checkers/checker-common.md +171 -0
- package/bootstrap/templates/aisb/checkers/checker-construct.md +129 -0
- package/bootstrap/templates/aisb/checkers/checker-keymaker.md +204 -0
- package/bootstrap/templates/aisb/checkers/checker-link.md +205 -0
- package/bootstrap/templates/aisb/checkers/checker-merovingian.md +219 -0
- package/bootstrap/templates/aisb/checkers/checker-morpheus.md +211 -0
- package/bootstrap/templates/aisb/checkers/checker-neo.md +177 -0
- package/bootstrap/templates/aisb/checkers/checker-niobe.md +156 -0
- package/bootstrap/templates/aisb/checkers/checker-oracle.md +164 -0
- package/bootstrap/templates/aisb/checkers/checker-seraph.md +187 -0
- package/bootstrap/templates/aisb/checkers/checker-smith.md +195 -0
- package/bootstrap/templates/aisb/checkers/checker-zion.md +113 -0
- package/bootstrap/templates/aisb/construct.md +135 -0
- package/bootstrap/templates/aisb/keymaker.md +227 -0
- package/bootstrap/templates/aisb/link.md +170 -0
- package/bootstrap/templates/aisb/lmc-protocol.md +57 -0
- package/bootstrap/templates/aisb/merovingian.md +159 -0
- package/bootstrap/templates/aisb/morpheus.md +243 -0
- package/bootstrap/templates/aisb/neo.md +147 -0
- package/bootstrap/templates/aisb/niobe.md +197 -0
- package/bootstrap/templates/aisb/oracle.md +244 -0
- package/bootstrap/templates/aisb/protocols/handoff-templates.md +204 -0
- package/bootstrap/templates/aisb/protocols/shared-protocol.md +248 -0
- package/bootstrap/templates/aisb/pythia.md +153 -0
- package/bootstrap/templates/aisb/seraph.md +315 -0
- package/bootstrap/templates/aisb/smith.md +202 -0
- package/bootstrap/templates/aisb/zion.md +172 -0
- package/bootstrap/templates/autonomous/audit-patrol.yaml +41 -0
- package/bootstrap/templates/autonomous/smith-reflect.yaml +43 -0
- package/bootstrap/templates/autonomous/ssh-key-rotate.yaml +46 -0
- package/bootstrap/templates/autonomous/support-agent.yaml +38 -0
- package/docs/AUDITS.md +85 -0
- package/docs/COMPLETION-PLAN.md +48 -0
- package/docs/GAP-ANALYSIS.md +214 -0
- package/docs/INSTALL.md +47 -9
- package/docs/MCP-AND-PLUGINS.md +31 -4
- package/docs/SIMULATION.md +171 -0
- package/docs/simulate.sh +211 -0
- package/install.sh +164 -17
- package/omega/Agentik_Engine/README.md +27 -10
- package/omega/Agentik_Engine/omega_engine/__init__.py +212 -2
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/account.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/agent_messages.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/aisb_chat.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_diff.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_gate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/auto_update.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/backup.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cadence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/classifier.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cleanup.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__/completions.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/costs.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/done_signal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/envelope.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/handoff.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__/hermes_bootstrap.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/hermes_desktop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/learning.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/managed_agent.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/memory.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/menu.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/plan.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/prompts.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__/prune.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/pursue.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/skill_routing.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/smoke.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/sync.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/telegram_history.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__/tools.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/understand_anything.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/updater.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/validate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/vault.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/webhooks.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/worker.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/account.py +502 -0
- package/omega/Agentik_Engine/omega_engine/agent_messages.py +167 -0
- package/omega/Agentik_Engine/omega_engine/aisb_chat.py +128 -0
- package/omega/Agentik_Engine/omega_engine/audit_diff.py +99 -0
- package/omega/Agentik_Engine/omega_engine/audit_gate.py +149 -0
- package/omega/Agentik_Engine/omega_engine/audits/__init__.py +60 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/batcher.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/dispatcher.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/generator.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/history.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/batcher.py +218 -0
- package/omega/Agentik_Engine/omega_engine/audits/dispatcher.py +92 -0
- package/omega/Agentik_Engine/omega_engine/audits/generator.py +234 -0
- package/omega/Agentik_Engine/omega_engine/audits/history.py +168 -0
- package/omega/Agentik_Engine/omega_engine/audits/pipeline.py +198 -0
- package/omega/Agentik_Engine/omega_engine/auto_update.py +339 -0
- package/omega/Agentik_Engine/omega_engine/autonomous.py +538 -0
- package/omega/Agentik_Engine/omega_engine/backup.py +215 -0
- package/omega/Agentik_Engine/omega_engine/cadence.py +158 -0
- package/omega/Agentik_Engine/omega_engine/classifier.py +215 -0
- package/omega/Agentik_Engine/omega_engine/cleanup.py +673 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +4564 -56
- package/omega/Agentik_Engine/omega_engine/completions.py +260 -0
- package/omega/Agentik_Engine/omega_engine/costs.py +100 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__init__.py +14 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/engine.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/telegram.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/autonomous.py +56 -0
- package/omega/Agentik_Engine/omega_engine/daemons/engine.py +236 -0
- package/omega/Agentik_Engine/omega_engine/daemons/telegram.py +315 -0
- package/omega/Agentik_Engine/omega_engine/done_signal.py +154 -0
- package/omega/Agentik_Engine/omega_engine/educators/__init__.py +51 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/artifact.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/automation.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/base.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/claudecode.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/connection.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/coworker.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/loop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/prompt.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/skill.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/artifact.py +65 -0
- package/omega/Agentik_Engine/omega_engine/educators/automation.py +76 -0
- package/omega/Agentik_Engine/omega_engine/educators/base.py +327 -0
- package/omega/Agentik_Engine/omega_engine/educators/claudecode.py +71 -0
- package/omega/Agentik_Engine/omega_engine/educators/connection.py +75 -0
- package/omega/Agentik_Engine/omega_engine/educators/coworker.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/loop.py +82 -0
- package/omega/Agentik_Engine/omega_engine/educators/prompt.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/skill.py +69 -0
- package/omega/Agentik_Engine/omega_engine/envelope.py +219 -0
- package/omega/Agentik_Engine/omega_engine/executor.py +195 -16
- package/omega/Agentik_Engine/omega_engine/genesis/__init__.py +134 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/phases.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/stack.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/state.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/orchestrator.py +262 -0
- package/omega/Agentik_Engine/omega_engine/genesis/phases.py +950 -0
- package/omega/Agentik_Engine/omega_engine/genesis/stack.py +324 -0
- package/omega/Agentik_Engine/omega_engine/genesis/state.py +353 -0
- package/omega/Agentik_Engine/omega_engine/handoff.py +459 -0
- package/omega/Agentik_Engine/omega_engine/hermes.py +426 -0
- package/omega/Agentik_Engine/omega_engine/hermes_bootstrap.py +382 -0
- package/omega/Agentik_Engine/omega_engine/hermes_desktop.py +469 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__init__.py +30 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__pycache__/graphify.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/integrations/graphify.py +234 -0
- package/omega/Agentik_Engine/omega_engine/learning.py +268 -0
- package/omega/Agentik_Engine/omega_engine/managed_agent.py +467 -0
- package/omega/Agentik_Engine/omega_engine/memory.py +271 -0
- package/omega/Agentik_Engine/omega_engine/menu.py +1065 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__init__.py +144 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__pycache__/v0_14_0.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/migrations/v0_14_0.py +29 -0
- package/omega/Agentik_Engine/omega_engine/mission.py +29 -14
- package/omega/Agentik_Engine/omega_engine/plan.py +846 -0
- package/omega/Agentik_Engine/omega_engine/prompts.py +158 -0
- package/omega/Agentik_Engine/omega_engine/provider.py +408 -13
- package/omega/Agentik_Engine/omega_engine/prune.py +151 -0
- package/omega/Agentik_Engine/omega_engine/pursue.py +205 -0
- package/omega/Agentik_Engine/omega_engine/rag/__init__.py +21 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/agentic.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/base.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/corrective.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/graph.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/hybrid.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/multimodal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/agentic.py +83 -0
- package/omega/Agentik_Engine/omega_engine/rag/base.py +42 -0
- package/omega/Agentik_Engine/omega_engine/rag/corrective.py +119 -0
- package/omega/Agentik_Engine/omega_engine/rag/graph.py +169 -0
- package/omega/Agentik_Engine/omega_engine/rag/hybrid.py +205 -0
- package/omega/Agentik_Engine/omega_engine/rag/multimodal.py +136 -0
- package/omega/Agentik_Engine/omega_engine/rag/router.py +110 -0
- package/omega/Agentik_Engine/omega_engine/reducer.py +21 -3
- package/omega/Agentik_Engine/omega_engine/router.py +28 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__init__.py +48 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/auditor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/finder.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/installer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/marketplaces.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/auditor.py +232 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/finder.py +94 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/installer.py +129 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/marketplaces.py +80 -0
- package/omega/Agentik_Engine/omega_engine/skill_routing.py +388 -0
- package/omega/Agentik_Engine/omega_engine/smoke.py +81 -0
- package/omega/Agentik_Engine/omega_engine/store.py +132 -25
- package/omega/Agentik_Engine/omega_engine/sync.py +445 -0
- package/omega/Agentik_Engine/omega_engine/telegram_history.py +260 -0
- package/omega/Agentik_Engine/omega_engine/tmux.py +526 -0
- package/omega/Agentik_Engine/omega_engine/tools.py +272 -0
- package/omega/Agentik_Engine/omega_engine/understand_anything.py +275 -0
- package/omega/Agentik_Engine/omega_engine/updater.py +70 -0
- package/omega/Agentik_Engine/omega_engine/validate.py +186 -0
- package/omega/Agentik_Engine/omega_engine/vault.py +342 -0
- package/omega/Agentik_Engine/omega_engine/webhooks.py +262 -0
- package/omega/Agentik_Engine/omega_engine/worker.py +526 -0
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/__pycache__/test_account.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_account.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_adversarial.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_adversarial.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_agents_envelope.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_agents_envelope.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audits_pipeline.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audits_pipeline.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_auto_update_and_migrations.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_auto_update_and_migrations.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_autonomous.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_educators.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_educators.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_genesis_and_plan.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_genesis_and_plan.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_graphify.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_graphify.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_handoff.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_handoff.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_bootstrap_and_desktop.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_bootstrap_and_desktop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.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_intelligence.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_intelligence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_llm_clis_and_uninstall.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_llm_clis_and_uninstall.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_managed_agent.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_managed_agent.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_max_provider_and_menu.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_max_provider_and_menu.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_menu_coverage.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_menu_coverage.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_pursue_cadence.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_pursue_cadence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_rag.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_rag.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_role_aliases_and_ssot.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_role_aliases_and_ssot.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_discovery_and_gate.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_discovery_and_gate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_power.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_power.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_routing.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_routing.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_snapshot_partial.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_snapshot_partial.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_and_aisb_chat.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_and_aisb_chat.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tools_and_sync.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tools_and_sync.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v06_features.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v06_features.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_vault.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_vault.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_webhooks_and_readiness.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_webhooks_and_readiness.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_worker_and_cleanup.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_worker_and_cleanup.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_account.py +338 -0
- package/omega/Agentik_Engine/tests/test_adversarial.py +351 -0
- package/omega/Agentik_Engine/tests/test_agents_envelope.py +274 -0
- package/omega/Agentik_Engine/tests/test_audits_pipeline.py +348 -0
- package/omega/Agentik_Engine/tests/test_auto_update_and_migrations.py +394 -0
- package/omega/Agentik_Engine/tests/test_autonomous.py +361 -0
- package/omega/Agentik_Engine/tests/test_educators.py +233 -0
- package/omega/Agentik_Engine/tests/test_genesis_and_plan.py +573 -0
- package/omega/Agentik_Engine/tests/test_graphify.py +190 -0
- package/omega/Agentik_Engine/tests/test_handoff.py +311 -0
- package/omega/Agentik_Engine/tests/test_hermes_and_ua.py +387 -0
- package/omega/Agentik_Engine/tests/test_hermes_bootstrap_and_desktop.py +358 -0
- package/omega/Agentik_Engine/tests/test_install_steps.py +359 -0
- package/omega/Agentik_Engine/tests/test_install_ux.py +151 -0
- package/omega/Agentik_Engine/tests/test_installer_wiring.py +496 -0
- package/omega/Agentik_Engine/tests/test_intelligence.py +285 -0
- package/omega/Agentik_Engine/tests/test_llm_clis_and_uninstall.py +228 -0
- package/omega/Agentik_Engine/tests/test_managed_agent.py +363 -0
- package/omega/Agentik_Engine/tests/test_max_provider_and_menu.py +231 -0
- package/omega/Agentik_Engine/tests/test_menu_coverage.py +72 -0
- package/omega/Agentik_Engine/tests/test_pursue_cadence.py +217 -0
- package/omega/Agentik_Engine/tests/test_rag.py +287 -0
- package/omega/Agentik_Engine/tests/test_role_aliases_and_ssot.py +207 -0
- package/omega/Agentik_Engine/tests/test_skill_discovery_and_gate.py +337 -0
- package/omega/Agentik_Engine/tests/test_skill_power.py +259 -0
- package/omega/Agentik_Engine/tests/test_skill_routing.py +189 -0
- package/omega/Agentik_Engine/tests/test_snapshot_partial.py +172 -0
- package/omega/Agentik_Engine/tests/test_telegram_history.py +209 -0
- package/omega/Agentik_Engine/tests/test_tmux_and_aisb_chat.py +223 -0
- package/omega/Agentik_Engine/tests/test_tools_and_sync.py +312 -0
- package/omega/Agentik_Engine/tests/test_v06_features.py +370 -0
- package/omega/Agentik_Engine/tests/test_vault.py +173 -0
- package/omega/Agentik_Engine/tests/test_webhooks_and_readiness.py +277 -0
- package/omega/Agentik_Engine/tests/test_worker_and_cleanup.py +541 -0
- package/omega/Agentik_Extra/etc/secrets/.vault-key +3 -0
- package/omega/Agentik_Extra/etc/secrets/.vault-pub +1 -0
- package/omega/Agentik_Runtime/audits.db +0 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/omega/Agentik_SSOT/claude-plugins/claude-plugins.yaml +100 -0
- package/omega/Agentik_SSOT/docs/LAYERS.md +90 -0
- package/omega/Agentik_SSOT/docs/USER-JOURNEY.md +283 -0
- package/omega/Agentik_SSOT/marketplaces/design-discipline.yaml +86 -0
- package/omega/Agentik_SSOT/skills/a11yaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/apiaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/automationaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/cadence/SKILL.md +76 -0
- package/omega/Agentik_SSOT/skills/codeaudit/SKILL.md +153 -0
- package/omega/Agentik_SSOT/skills/copyaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/dataaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/debugaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/dispatch/SKILL.md +79 -0
- package/omega/Agentik_SSOT/skills/dxaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/featureaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/flowaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/genesis/SKILL.md +116 -0
- package/omega/Agentik_SSOT/skills/handoff/SKILL.md +117 -0
- package/omega/Agentik_SSOT/skills/logicaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/motionaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/perfaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/plan/SKILL.md +127 -0
- package/omega/Agentik_SSOT/skills/pursue/SKILL.md +68 -0
- package/omega/Agentik_SSOT/skills/rag-route.md +82 -0
- package/omega/Agentik_SSOT/skills/refontaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/retentionaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/secaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/seoaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/skill-auditor/SKILL.md +83 -0
- package/omega/Agentik_SSOT/skills/skill-finder/SKILL.md +116 -0
- package/omega/Agentik_SSOT/skills/uiuxaudit/SKILL.md +165 -0
- package/package.json +2 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Tests for omega_engine.managed_agent — Anthropic Managed Agents wrapper.
|
|
2
|
+
|
|
3
|
+
Strategy: mock urllib.request.urlopen at the boundary so we exercise the
|
|
4
|
+
real serialization, header injection, error handling, SSE stream parser,
|
|
5
|
+
cache, and Provider integration without ever hitting the network.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from unittest import mock
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
HERE = Path(__file__).resolve().parent
|
|
20
|
+
sys.path.insert(0, str(HERE.parent))
|
|
21
|
+
|
|
22
|
+
from omega_engine import managed_agent as MA # noqa: E402
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Fake urllib helpers
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _FakeResponse:
|
|
31
|
+
"""Minimal stand-in for the object returned by urlopen()."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, body: bytes, status: int = 200):
|
|
34
|
+
self._buf = io.BytesIO(body)
|
|
35
|
+
self.status = status
|
|
36
|
+
|
|
37
|
+
def read(self) -> bytes:
|
|
38
|
+
return self._buf.read()
|
|
39
|
+
|
|
40
|
+
def __iter__(self):
|
|
41
|
+
# Yield line by line (with trailing \n) — matches what SSE does.
|
|
42
|
+
self._buf.seek(0)
|
|
43
|
+
for raw in self._buf:
|
|
44
|
+
yield raw
|
|
45
|
+
|
|
46
|
+
def __enter__(self):
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def __exit__(self, *a):
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def close(self):
|
|
53
|
+
self._buf.close()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _json_response(payload: dict) -> _FakeResponse:
|
|
57
|
+
return _FakeResponse(json.dumps(payload).encode("utf-8"))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _sse_response(events: list[dict]) -> _FakeResponse:
|
|
61
|
+
"""Build a fake SSE body from a list of event dicts."""
|
|
62
|
+
body = b""
|
|
63
|
+
for ev in events:
|
|
64
|
+
body += b"data: " + json.dumps(ev).encode("utf-8") + b"\n\n"
|
|
65
|
+
return _FakeResponse(body)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Client — REST shape
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestClientHeaders(unittest.TestCase):
|
|
74
|
+
def _captured_request(self, urlopen_patch):
|
|
75
|
+
# urlopen_patch.call_args[0][0] is the Request object.
|
|
76
|
+
req = urlopen_patch.call_args[0][0]
|
|
77
|
+
return req
|
|
78
|
+
|
|
79
|
+
def test_create_agent_sends_beta_header(self):
|
|
80
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
81
|
+
with mock.patch("urllib.request.urlopen",
|
|
82
|
+
return_value=_json_response({"id": "agent_1", "version": 1})) as uo:
|
|
83
|
+
c.create_agent(name="x", model="claude-opus-4-7", system="hi")
|
|
84
|
+
req = self._captured_request(uo)
|
|
85
|
+
self.assertEqual(req.get_header("X-api-key"), "sk-test")
|
|
86
|
+
self.assertEqual(req.get_header("Anthropic-beta"),
|
|
87
|
+
"managed-agents-2026-04-01")
|
|
88
|
+
self.assertEqual(req.get_header("Anthropic-version"), "2023-06-01")
|
|
89
|
+
# Body is the agent definition
|
|
90
|
+
body = json.loads(req.data.decode("utf-8"))
|
|
91
|
+
self.assertEqual(body["name"], "x")
|
|
92
|
+
self.assertEqual(body["model"], "claude-opus-4-7")
|
|
93
|
+
self.assertEqual(body["system"], "hi")
|
|
94
|
+
self.assertEqual(body["tools"], [{"type": "agent_toolset_20260401"}])
|
|
95
|
+
|
|
96
|
+
def test_create_agent_defaults_tools_to_full_toolset(self):
|
|
97
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
98
|
+
with mock.patch("urllib.request.urlopen",
|
|
99
|
+
return_value=_json_response({"id": "agent_2"})) as uo:
|
|
100
|
+
c.create_agent(name="x")
|
|
101
|
+
body = json.loads(uo.call_args[0][0].data.decode("utf-8"))
|
|
102
|
+
self.assertEqual(body["tools"], [{"type": "agent_toolset_20260401"}])
|
|
103
|
+
|
|
104
|
+
def test_create_environment_defaults_to_cloud_unrestricted(self):
|
|
105
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
106
|
+
with mock.patch("urllib.request.urlopen",
|
|
107
|
+
return_value=_json_response({"id": "env_1"})) as uo:
|
|
108
|
+
c.create_environment(name="quickstart")
|
|
109
|
+
body = json.loads(uo.call_args[0][0].data.decode("utf-8"))
|
|
110
|
+
self.assertEqual(body["name"], "quickstart")
|
|
111
|
+
self.assertEqual(body["config"]["type"], "cloud")
|
|
112
|
+
self.assertEqual(body["config"]["networking"]["type"], "unrestricted")
|
|
113
|
+
|
|
114
|
+
def test_send_user_message_shape(self):
|
|
115
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
116
|
+
with mock.patch("urllib.request.urlopen",
|
|
117
|
+
return_value=_json_response({})) as uo:
|
|
118
|
+
c.send_user_message("sess_1", "hello world")
|
|
119
|
+
body = json.loads(uo.call_args[0][0].data.decode("utf-8"))
|
|
120
|
+
self.assertEqual(body["events"][0]["type"], "user.message")
|
|
121
|
+
self.assertEqual(body["events"][0]["content"][0]["text"], "hello world")
|
|
122
|
+
|
|
123
|
+
def test_http_error_raises_managed_agent_error(self):
|
|
124
|
+
import urllib.error
|
|
125
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
126
|
+
err = urllib.error.HTTPError(
|
|
127
|
+
"http://x", 401,
|
|
128
|
+
"Unauthorized", {}, io.BytesIO(b'{"error":"bad key"}'),
|
|
129
|
+
)
|
|
130
|
+
with mock.patch("urllib.request.urlopen", side_effect=err):
|
|
131
|
+
with self.assertRaises(MA.ManagedAgentError) as ctx:
|
|
132
|
+
c.create_agent(name="x")
|
|
133
|
+
self.assertIn("HTTP 401", str(ctx.exception))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# SSE stream parser
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TestStreamParser(unittest.TestCase):
|
|
142
|
+
def test_stream_yields_each_event(self):
|
|
143
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
144
|
+
events = [
|
|
145
|
+
{"type": "agent.message",
|
|
146
|
+
"content": [{"type": "text", "text": "hi"}]},
|
|
147
|
+
{"type": "agent.tool_use", "name": "bash"},
|
|
148
|
+
{"type": "session.status_idle"},
|
|
149
|
+
]
|
|
150
|
+
with mock.patch("urllib.request.urlopen",
|
|
151
|
+
return_value=_sse_response(events)):
|
|
152
|
+
out = list(c.stream_events("sess_x"))
|
|
153
|
+
self.assertEqual(len(out), 3)
|
|
154
|
+
self.assertEqual(out[0]["type"], "agent.message")
|
|
155
|
+
self.assertEqual(out[2]["type"], "session.status_idle")
|
|
156
|
+
|
|
157
|
+
def test_stream_stops_on_idle_when_stop_on_idle(self):
|
|
158
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
159
|
+
events = [
|
|
160
|
+
{"type": "agent.message",
|
|
161
|
+
"content": [{"type": "text", "text": "a"}]},
|
|
162
|
+
{"type": "session.status_idle"},
|
|
163
|
+
# These MUST be ignored when stop_on_idle=True
|
|
164
|
+
{"type": "agent.message",
|
|
165
|
+
"content": [{"type": "text", "text": "leak"}]},
|
|
166
|
+
]
|
|
167
|
+
with mock.patch("urllib.request.urlopen",
|
|
168
|
+
return_value=_sse_response(events)):
|
|
169
|
+
out = list(c.stream_events("sess_x", stop_on_idle=True))
|
|
170
|
+
self.assertEqual(len(out), 2)
|
|
171
|
+
self.assertEqual(out[-1]["type"], "session.status_idle")
|
|
172
|
+
|
|
173
|
+
def test_stream_ignores_malformed_lines(self):
|
|
174
|
+
c = MA.ManagedAgentClient(api_key="sk-test")
|
|
175
|
+
body = (
|
|
176
|
+
b"\n" # blank
|
|
177
|
+
b": comment\n" # SSE comment
|
|
178
|
+
b"data: not-json\n\n" # malformed payload
|
|
179
|
+
b'data: {"type":"agent.message","content":[{"type":"text","text":"ok"}]}\n\n'
|
|
180
|
+
b'data: {"type":"session.status_idle"}\n\n'
|
|
181
|
+
)
|
|
182
|
+
with mock.patch("urllib.request.urlopen",
|
|
183
|
+
return_value=_FakeResponse(body)):
|
|
184
|
+
out = list(c.stream_events("sess_x"))
|
|
185
|
+
# Two valid events parsed; malformed line silently skipped
|
|
186
|
+
self.assertEqual(len(out), 2)
|
|
187
|
+
self.assertEqual(out[0]["type"], "agent.message")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# Cache
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestCache(unittest.TestCase):
|
|
196
|
+
def test_round_trip(self):
|
|
197
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
198
|
+
cache = MA.ManagedAgentCache(Path(tmp))
|
|
199
|
+
self.assertIsNone(cache.get_agent("a"))
|
|
200
|
+
cache.set_agent("a", "agent_123")
|
|
201
|
+
cache.set_environment("env-a", "env_456")
|
|
202
|
+
self.assertEqual(cache.get_agent("a"), "agent_123")
|
|
203
|
+
self.assertEqual(cache.get_environment("env-a"), "env_456")
|
|
204
|
+
|
|
205
|
+
def test_atomic_write_no_tmp_left_behind(self):
|
|
206
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
207
|
+
cache = MA.ManagedAgentCache(Path(tmp))
|
|
208
|
+
cache.set_agent("x", "agent_1")
|
|
209
|
+
p = MA._cache_path(Path(tmp))
|
|
210
|
+
# No .tmp.* siblings
|
|
211
|
+
leftovers = list(p.parent.glob("*.tmp.*"))
|
|
212
|
+
self.assertEqual(leftovers, [])
|
|
213
|
+
|
|
214
|
+
def test_corrupt_cache_yields_empty(self):
|
|
215
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
216
|
+
p = MA._cache_path(Path(tmp))
|
|
217
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
p.write_text("{not valid")
|
|
219
|
+
cache = MA.ManagedAgentCache(Path(tmp))
|
|
220
|
+
self.assertIsNone(cache.get_agent("x"))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
# Provider — full path
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestProvider(unittest.TestCase):
|
|
229
|
+
def test_run_creates_agent_env_session_and_aggregates_text(self):
|
|
230
|
+
from omega_engine.provider import AgentRequest
|
|
231
|
+
|
|
232
|
+
# Mock the network: 1 POST /agents + 1 POST /environments +
|
|
233
|
+
# 1 POST /sessions + 1 POST /sessions/<id>/events + 1 GET /stream (SSE)
|
|
234
|
+
calls: list[str] = []
|
|
235
|
+
|
|
236
|
+
def fake_urlopen(req, timeout=None):
|
|
237
|
+
url = req.full_url
|
|
238
|
+
calls.append(req.get_method() or "GET")
|
|
239
|
+
if url.endswith("/agents") and req.get_method() == "POST":
|
|
240
|
+
return _json_response({"id": "agent_X", "version": 1})
|
|
241
|
+
if url.endswith("/environments") and req.get_method() == "POST":
|
|
242
|
+
return _json_response({"id": "env_X"})
|
|
243
|
+
if url.endswith("/sessions") and req.get_method() == "POST":
|
|
244
|
+
return _json_response({"id": "sess_X"})
|
|
245
|
+
if "events" in url and req.get_method() == "POST":
|
|
246
|
+
return _json_response({})
|
|
247
|
+
if "stream" in url:
|
|
248
|
+
return _sse_response([
|
|
249
|
+
{"type": "agent.message",
|
|
250
|
+
"content": [{"type": "text", "text": "hello "},
|
|
251
|
+
{"type": "text", "text": "world"}]},
|
|
252
|
+
{"type": "agent.tool_use", "name": "bash"},
|
|
253
|
+
{"type": "session.status_idle"},
|
|
254
|
+
])
|
|
255
|
+
raise AssertionError(f"unmocked: {req.get_method()} {url}")
|
|
256
|
+
|
|
257
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
258
|
+
with mock.patch("urllib.request.urlopen",
|
|
259
|
+
side_effect=fake_urlopen):
|
|
260
|
+
p = MA.ManagedAgentProvider(
|
|
261
|
+
api_key="sk-test", omega_home=Path(tmp),
|
|
262
|
+
)
|
|
263
|
+
result = p.run(AgentRequest(
|
|
264
|
+
role="worker", prompt="do the thing",
|
|
265
|
+
system="", context=None,
|
|
266
|
+
))
|
|
267
|
+
self.assertEqual(result.text, "hello world")
|
|
268
|
+
self.assertEqual(result.artifacts["managed_agent"]["session_id"],
|
|
269
|
+
"sess_X")
|
|
270
|
+
self.assertEqual(result.artifacts["managed_agent"]["tool_uses"],
|
|
271
|
+
["bash"])
|
|
272
|
+
|
|
273
|
+
def test_run_reuses_cached_agent_and_env_on_second_call(self):
|
|
274
|
+
"""Second run should NOT re-POST /agents or /environments."""
|
|
275
|
+
from omega_engine.provider import AgentRequest
|
|
276
|
+
|
|
277
|
+
post_calls: list[str] = []
|
|
278
|
+
|
|
279
|
+
def fake_urlopen(req, timeout=None):
|
|
280
|
+
url = req.full_url
|
|
281
|
+
if req.get_method() == "POST":
|
|
282
|
+
post_calls.append(url)
|
|
283
|
+
if url.endswith("/agents") and req.get_method() == "POST":
|
|
284
|
+
return _json_response({"id": "agent_A"})
|
|
285
|
+
if url.endswith("/environments") and req.get_method() == "POST":
|
|
286
|
+
return _json_response({"id": "env_A"})
|
|
287
|
+
if url.endswith("/sessions") and req.get_method() == "POST":
|
|
288
|
+
return _json_response({"id": "sess_Y"})
|
|
289
|
+
if "events" in url and req.get_method() == "POST":
|
|
290
|
+
return _json_response({})
|
|
291
|
+
if "stream" in url:
|
|
292
|
+
return _sse_response([
|
|
293
|
+
{"type": "agent.message",
|
|
294
|
+
"content": [{"type": "text", "text": "ok"}]},
|
|
295
|
+
{"type": "session.status_idle"},
|
|
296
|
+
])
|
|
297
|
+
raise AssertionError(f"unmocked: {req.get_method()} {url}")
|
|
298
|
+
|
|
299
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
300
|
+
with mock.patch("urllib.request.urlopen",
|
|
301
|
+
side_effect=fake_urlopen):
|
|
302
|
+
p = MA.ManagedAgentProvider(api_key="sk-test", omega_home=Path(tmp))
|
|
303
|
+
p.run(AgentRequest(role="worker", prompt="one", system="", context=None))
|
|
304
|
+
# Reset post_calls — only count the second invocation
|
|
305
|
+
post_calls.clear()
|
|
306
|
+
p2 = MA.ManagedAgentProvider(api_key="sk-test", omega_home=Path(tmp))
|
|
307
|
+
p2.run(AgentRequest(role="worker", prompt="two", system="", context=None))
|
|
308
|
+
# Second run: NO /agents, NO /environments. Only /sessions + /events.
|
|
309
|
+
for url in post_calls:
|
|
310
|
+
self.assertFalse(
|
|
311
|
+
url.endswith("/agents"), f"unexpected re-create: {url}")
|
|
312
|
+
self.assertFalse(
|
|
313
|
+
url.endswith("/environments"), f"unexpected re-create: {url}")
|
|
314
|
+
|
|
315
|
+
def test_constructor_rejects_missing_api_key(self):
|
|
316
|
+
# Patch env to be empty
|
|
317
|
+
with mock.patch.dict(os.environ, {}, clear=True):
|
|
318
|
+
with self.assertRaises(ValueError):
|
|
319
|
+
MA.ManagedAgentProvider(api_key=None)
|
|
320
|
+
|
|
321
|
+
def test_is_available_reflects_env(self):
|
|
322
|
+
with mock.patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-x"}):
|
|
323
|
+
self.assertTrue(MA.ManagedAgentProvider.is_available())
|
|
324
|
+
with mock.patch.dict(os.environ, {}, clear=True):
|
|
325
|
+
self.assertFalse(MA.ManagedAgentProvider.is_available())
|
|
326
|
+
|
|
327
|
+
def test_run_with_system_prompt_prepended(self):
|
|
328
|
+
from omega_engine.provider import AgentRequest
|
|
329
|
+
|
|
330
|
+
captured_event_body = {}
|
|
331
|
+
|
|
332
|
+
def fake_urlopen(req, timeout=None):
|
|
333
|
+
url = req.full_url
|
|
334
|
+
if url.endswith("/agents"):
|
|
335
|
+
return _json_response({"id": "agent_S"})
|
|
336
|
+
if url.endswith("/environments"):
|
|
337
|
+
return _json_response({"id": "env_S"})
|
|
338
|
+
if url.endswith("/sessions"):
|
|
339
|
+
return _json_response({"id": "sess_S"})
|
|
340
|
+
if "events" in url:
|
|
341
|
+
captured_event_body["body"] = json.loads(
|
|
342
|
+
req.data.decode("utf-8")
|
|
343
|
+
)
|
|
344
|
+
return _json_response({})
|
|
345
|
+
if "stream" in url:
|
|
346
|
+
return _sse_response([{"type": "session.status_idle"}])
|
|
347
|
+
raise AssertionError(f"unmocked: {url}")
|
|
348
|
+
|
|
349
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
350
|
+
with mock.patch("urllib.request.urlopen",
|
|
351
|
+
side_effect=fake_urlopen):
|
|
352
|
+
p = MA.ManagedAgentProvider(api_key="sk-test", omega_home=Path(tmp))
|
|
353
|
+
p.run(AgentRequest(
|
|
354
|
+
role="worker", prompt="do X",
|
|
355
|
+
system="be careful", context=None,
|
|
356
|
+
))
|
|
357
|
+
text = captured_event_body["body"]["events"][0]["content"][0]["text"]
|
|
358
|
+
self.assertIn("[system] be careful", text)
|
|
359
|
+
self.assertIn("do X", text)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
unittest.main()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Tests for the ClaudeMaxProvider + the interactive menu wiring.
|
|
2
|
+
|
|
3
|
+
The Max provider subprocesses ``claude -p`` — we don't actually run real
|
|
4
|
+
Claude in tests (cost + dependency). We mock subprocess.run to feed the
|
|
5
|
+
provider a canned JSON response and verify it parses correctly.
|
|
6
|
+
|
|
7
|
+
For the menu, we test the wiring: bare `omega` invocation dispatches to
|
|
8
|
+
``cmd_menu``, and ``run_menu`` refuses cleanly when whiptail is missing.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import unittest
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from unittest import mock
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
HERE = Path(__file__).resolve().parent
|
|
23
|
+
sys.path.insert(0, str(HERE.parent))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# ClaudeMaxProvider
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_CANNED_CLAUDE_JSON = {
|
|
32
|
+
"type": "result", "subtype": "success", "is_error": False,
|
|
33
|
+
"duration_ms": 4344, "duration_api_ms": 1760, "num_turns": 1,
|
|
34
|
+
"result": "Done.",
|
|
35
|
+
"stop_reason": "end_turn",
|
|
36
|
+
"session_id": "fake-session-abc",
|
|
37
|
+
"total_cost_usd": 0.0123,
|
|
38
|
+
"usage": {
|
|
39
|
+
"input_tokens": 12, "output_tokens": 34,
|
|
40
|
+
"cache_read_input_tokens": 0,
|
|
41
|
+
"cache_creation_input_tokens": 100,
|
|
42
|
+
},
|
|
43
|
+
"modelUsage": {"claude-opus-4-7": {"costUSD": 0.0123}},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _fake_proc(stdout: str = "", returncode: int = 0, stderr: str = ""):
|
|
48
|
+
return subprocess.CompletedProcess(
|
|
49
|
+
args=[], returncode=returncode, stdout=stdout, stderr=stderr,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestClaudeMaxAvailability(unittest.TestCase):
|
|
54
|
+
def test_unavailable_when_claude_missing(self):
|
|
55
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
56
|
+
with mock.patch("shutil.which", return_value=None):
|
|
57
|
+
self.assertFalse(ClaudeMaxProvider.is_available())
|
|
58
|
+
|
|
59
|
+
def test_available_when_version_responds(self):
|
|
60
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
61
|
+
with mock.patch("shutil.which", return_value="/usr/bin/claude"), \
|
|
62
|
+
mock.patch("subprocess.run", return_value=_fake_proc("claude 1.0.0", 0)):
|
|
63
|
+
self.assertTrue(ClaudeMaxProvider.is_available())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestClaudeMaxRun(unittest.TestCase):
|
|
67
|
+
def _provider(self):
|
|
68
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
69
|
+
return ClaudeMaxProvider(model="sonnet")
|
|
70
|
+
|
|
71
|
+
def test_parses_canned_response(self):
|
|
72
|
+
from omega_engine.provider import AgentRequest
|
|
73
|
+
p = self._provider()
|
|
74
|
+
with mock.patch(
|
|
75
|
+
"subprocess.run",
|
|
76
|
+
return_value=_fake_proc(json.dumps(_CANNED_CLAUDE_JSON), 0),
|
|
77
|
+
):
|
|
78
|
+
result = p.run(AgentRequest(
|
|
79
|
+
role="worker", prompt="say done",
|
|
80
|
+
))
|
|
81
|
+
self.assertEqual(result.text, "Done.")
|
|
82
|
+
self.assertTrue(result.claimed_done)
|
|
83
|
+
self.assertEqual(result.usage["input_tokens"], 12)
|
|
84
|
+
self.assertEqual(result.usage["output_tokens"], 34)
|
|
85
|
+
self.assertAlmostEqual(result.usage["cost_usd"], 0.0123)
|
|
86
|
+
self.assertEqual(result.artifacts["session_id"], "fake-session-abc")
|
|
87
|
+
self.assertEqual(result.artifacts["num_turns"], 1)
|
|
88
|
+
|
|
89
|
+
def test_subprocess_command_carries_system_prompt(self):
|
|
90
|
+
from omega_engine.provider import AgentRequest
|
|
91
|
+
p = self._provider()
|
|
92
|
+
captured: dict = {}
|
|
93
|
+
|
|
94
|
+
def fake_run(cmd, **kw):
|
|
95
|
+
captured["cmd"] = cmd
|
|
96
|
+
return _fake_proc(json.dumps(_CANNED_CLAUDE_JSON), 0)
|
|
97
|
+
|
|
98
|
+
with mock.patch("subprocess.run", side_effect=fake_run):
|
|
99
|
+
p.run(AgentRequest(
|
|
100
|
+
role="worker", prompt="hi",
|
|
101
|
+
system="You are a focused worker.",
|
|
102
|
+
))
|
|
103
|
+
# --append-system-prompt is on the command line
|
|
104
|
+
self.assertIn("--append-system-prompt", captured["cmd"])
|
|
105
|
+
self.assertIn("You are a focused worker.", captured["cmd"])
|
|
106
|
+
# --dangerously-skip-permissions is set (headless Max)
|
|
107
|
+
self.assertIn("--dangerously-skip-permissions", captured["cmd"])
|
|
108
|
+
# --output-format json is on
|
|
109
|
+
self.assertIn("--output-format", captured["cmd"])
|
|
110
|
+
self.assertIn("json", captured["cmd"])
|
|
111
|
+
|
|
112
|
+
def test_resume_session(self):
|
|
113
|
+
from omega_engine.provider import AgentRequest
|
|
114
|
+
p = self._provider()
|
|
115
|
+
captured: dict = {}
|
|
116
|
+
|
|
117
|
+
def fake_run(cmd, **kw):
|
|
118
|
+
captured["cmd"] = cmd
|
|
119
|
+
return _fake_proc(json.dumps(_CANNED_CLAUDE_JSON), 0)
|
|
120
|
+
|
|
121
|
+
with mock.patch("subprocess.run", side_effect=fake_run):
|
|
122
|
+
p.run(AgentRequest(
|
|
123
|
+
role="oracle", prompt="next step",
|
|
124
|
+
context={"session_id": "prev-abc"},
|
|
125
|
+
))
|
|
126
|
+
self.assertIn("--resume", captured["cmd"])
|
|
127
|
+
self.assertIn("prev-abc", captured["cmd"])
|
|
128
|
+
|
|
129
|
+
def test_failure_surfaces_clean_error(self):
|
|
130
|
+
from omega_engine.provider import AgentRequest
|
|
131
|
+
p = self._provider()
|
|
132
|
+
with mock.patch(
|
|
133
|
+
"subprocess.run",
|
|
134
|
+
return_value=_fake_proc("", 1, "rate limit hit"),
|
|
135
|
+
):
|
|
136
|
+
with self.assertRaises(RuntimeError) as ctx:
|
|
137
|
+
p.run(AgentRequest(role="worker", prompt="hi"))
|
|
138
|
+
self.assertIn("rate limit", str(ctx.exception))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TestRouterAuto(unittest.TestCase):
|
|
142
|
+
def test_auto_picks_claude_max_when_available(self):
|
|
143
|
+
from omega_engine.router import ModelRouter
|
|
144
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
145
|
+
with mock.patch.object(ClaudeMaxProvider, "is_available",
|
|
146
|
+
return_value=True):
|
|
147
|
+
router = ModelRouter.auto()
|
|
148
|
+
self.assertEqual(router.resolve("worker").id, "claude-max")
|
|
149
|
+
|
|
150
|
+
def test_auto_falls_back_to_api_when_max_unavailable(self):
|
|
151
|
+
from omega_engine.router import ModelRouter
|
|
152
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
153
|
+
with mock.patch.object(ClaudeMaxProvider, "is_available",
|
|
154
|
+
return_value=False), \
|
|
155
|
+
mock.patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-x"}):
|
|
156
|
+
router = ModelRouter.auto()
|
|
157
|
+
self.assertEqual(router.resolve("worker").id, "claude")
|
|
158
|
+
|
|
159
|
+
def test_auto_falls_back_to_mock_when_nothing(self):
|
|
160
|
+
from omega_engine.router import ModelRouter
|
|
161
|
+
from omega_engine.provider import ClaudeMaxProvider
|
|
162
|
+
with mock.patch.object(ClaudeMaxProvider, "is_available",
|
|
163
|
+
return_value=False), \
|
|
164
|
+
mock.patch.dict("os.environ", {}, clear=True):
|
|
165
|
+
router = ModelRouter.auto()
|
|
166
|
+
self.assertEqual(router.resolve("worker").id, "mock")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Menu wiring
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestMenuDispatch(unittest.TestCase):
|
|
175
|
+
def test_bare_omega_dispatches_to_menu(self):
|
|
176
|
+
"""parse_args([]) should set fn to cmd_menu — i.e. `omega`
|
|
177
|
+
with no subcommand opens the interactive menu, doesn't error."""
|
|
178
|
+
from omega_engine.cli import _build_parser, cmd_menu
|
|
179
|
+
parser = _build_parser()
|
|
180
|
+
# argparse with optional subparser → no args parses cleanly
|
|
181
|
+
args = parser.parse_args([])
|
|
182
|
+
self.assertEqual(args.fn, cmd_menu)
|
|
183
|
+
|
|
184
|
+
def test_run_menu_falls_back_to_text_when_whiptail_missing(self):
|
|
185
|
+
"""Behaviour changed in v0.19.4: a bare `omega` MUST NEVER exit
|
|
186
|
+
silently when whiptail is missing — it falls back to a stdin/stdout
|
|
187
|
+
text menu. Without this fix, Mac users hit `omega` and got nothing
|
|
188
|
+
("se passe rien" — the v0.19.3 regression)."""
|
|
189
|
+
from omega_engine.menu import run_menu
|
|
190
|
+
import io, contextlib
|
|
191
|
+
# whiptail absent → text fallback. User picks "0" (quit).
|
|
192
|
+
with mock.patch("omega_engine.menu._have", return_value=False), \
|
|
193
|
+
mock.patch("builtins.input", side_effect=["0"]):
|
|
194
|
+
with contextlib.redirect_stdout(io.StringIO()) as buf:
|
|
195
|
+
rc = run_menu()
|
|
196
|
+
self.assertEqual(rc, 0, "text fallback must exit cleanly on user quit")
|
|
197
|
+
# The text menu must actually print SOMETHING — the silent return
|
|
198
|
+
# was the bug we're guarding against.
|
|
199
|
+
self.assertIn("Omega OS", buf.getvalue())
|
|
200
|
+
|
|
201
|
+
def test_run_menu_quits_cleanly(self):
|
|
202
|
+
from omega_engine.menu import run_menu
|
|
203
|
+
# Simulate user choosing "quit" from the main menu.
|
|
204
|
+
with mock.patch("omega_engine.menu._have", return_value=True), \
|
|
205
|
+
mock.patch("omega_engine.menu._whiptail_menu",
|
|
206
|
+
return_value="quit"):
|
|
207
|
+
rc = run_menu()
|
|
208
|
+
self.assertEqual(rc, 0)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestMenuHelpers(unittest.TestCase):
|
|
212
|
+
def test_run_capture_invokes_omega_bin(self):
|
|
213
|
+
from omega_engine.menu import _run_capture
|
|
214
|
+
|
|
215
|
+
captured: dict = {}
|
|
216
|
+
|
|
217
|
+
def fake_run(cmd, **kw):
|
|
218
|
+
captured["cmd"] = cmd
|
|
219
|
+
return _fake_proc("ok\n", 0)
|
|
220
|
+
|
|
221
|
+
with mock.patch("subprocess.run", side_effect=fake_run):
|
|
222
|
+
out = _run_capture("doctor")
|
|
223
|
+
self.assertEqual(out, "ok")
|
|
224
|
+
# The omega args land in cmd[1:], whatever the binary path is.
|
|
225
|
+
self.assertEqual(captured["cmd"][1], "doctor")
|
|
226
|
+
# The first arg is a real path (resolved omega bin or sys.argv[0]).
|
|
227
|
+
self.assertGreater(len(captured["cmd"][0]), 0)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Coverage tests for the interactive menu — make sure the new v0.19.1
|
|
2
|
+
entries (genesis, plan, worker, handoff, hermes, hermes-desktop,
|
|
3
|
+
memory, learn, update, ua, ma) all have:
|
|
4
|
+
|
|
5
|
+
* an entry in _MAIN_ITEMS
|
|
6
|
+
* a corresponding _menu_<name> function
|
|
7
|
+
* a dispatch arm in run_menu
|
|
8
|
+
|
|
9
|
+
So the next time we add a top-level CLI command, the test reminds the
|
|
10
|
+
maintainer to wire it into the menu too.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import unittest
|
|
17
|
+
|
|
18
|
+
HERE = Path(__file__).resolve().parent
|
|
19
|
+
sys.path.insert(0, str(HERE.parent))
|
|
20
|
+
|
|
21
|
+
from omega_engine import menu as M # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Every key here MUST appear in _MAIN_ITEMS AND have a _menu_<key>
|
|
25
|
+
# function (with the hyphen normalised to underscore for the func name).
|
|
26
|
+
_EXPECTED_KEYS_V019 = {
|
|
27
|
+
"mission", "genesis", "plan", "worker", "handoff",
|
|
28
|
+
"hermes", "hermes-desktop", "tmux", "aisb-chat",
|
|
29
|
+
"projects", "accounts", "audit", "skills",
|
|
30
|
+
"memory", "learn", "update", "ua", "ma",
|
|
31
|
+
"maintenance", "settings", "status", "doctor",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestMenuCoverage(unittest.TestCase):
|
|
36
|
+
def test_main_items_count(self):
|
|
37
|
+
"""We added 11 new entries in v0.19.1. Floor: 22 (not counting quit)."""
|
|
38
|
+
non_quit = [k for k, _ in M._MAIN_ITEMS if k != "quit"]
|
|
39
|
+
self.assertGreaterEqual(len(non_quit), 22,
|
|
40
|
+
f"main menu shrank below 22 entries: {non_quit}")
|
|
41
|
+
|
|
42
|
+
def test_all_expected_keys_present(self):
|
|
43
|
+
keys = {k for k, _ in M._MAIN_ITEMS}
|
|
44
|
+
missing = _EXPECTED_KEYS_V019 - keys
|
|
45
|
+
self.assertEqual(missing, set(),
|
|
46
|
+
f"missing menu entries: {missing}")
|
|
47
|
+
|
|
48
|
+
def test_every_main_item_has_handler_or_is_leaf(self):
|
|
49
|
+
"""For every non-leaf entry, _menu_<name> MUST exist."""
|
|
50
|
+
# 'quit', 'status', 'doctor' are leaves (no sub-menu).
|
|
51
|
+
leaves = {"quit", "status", "doctor"}
|
|
52
|
+
for key, _ in M._MAIN_ITEMS:
|
|
53
|
+
if key in leaves:
|
|
54
|
+
continue
|
|
55
|
+
func_name = "_menu_" + key.replace("-", "_")
|
|
56
|
+
self.assertTrue(
|
|
57
|
+
hasattr(M, func_name),
|
|
58
|
+
f"main menu entry {key!r} has no {func_name}() function",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_no_duplicate_keys(self):
|
|
62
|
+
keys = [k for k, _ in M._MAIN_ITEMS]
|
|
63
|
+
self.assertEqual(len(keys), len(set(keys)),
|
|
64
|
+
"duplicate menu keys")
|
|
65
|
+
|
|
66
|
+
def test_quit_is_present(self):
|
|
67
|
+
keys = {k for k, _ in M._MAIN_ITEMS}
|
|
68
|
+
self.assertIn("quit", keys)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
unittest.main()
|