@smilintux/skcapstone 0.1.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""Tests for trustee management CLI commands and TrusteeOps.
|
|
2
|
+
|
|
3
|
+
Covers restart, scale, rotate, health, and logs operations as well
|
|
4
|
+
as the audit trail and CLI integration via Click's test runner.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
from unittest.mock import MagicMock
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from skcapstone._trustee_helpers import write_audit as _write_audit
|
|
17
|
+
from skcapstone.blueprints.schema import ProviderType
|
|
18
|
+
from skcapstone.team_engine import AgentStatus, DeployedAgent, TeamDeployment, TeamEngine
|
|
19
|
+
from skcapstone.trustee_ops import TrusteeOps
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Fixtures
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _make_agent(
|
|
28
|
+
name: str = "test-team-alpha",
|
|
29
|
+
spec_key: str = "alpha",
|
|
30
|
+
status: AgentStatus = AgentStatus.RUNNING,
|
|
31
|
+
) -> DeployedAgent:
|
|
32
|
+
"""Build a minimal DeployedAgent for tests."""
|
|
33
|
+
return DeployedAgent(
|
|
34
|
+
name=name,
|
|
35
|
+
instance_id=f"test-deploy/{name}",
|
|
36
|
+
blueprint_slug="test-team",
|
|
37
|
+
agent_spec_key=spec_key,
|
|
38
|
+
status=status,
|
|
39
|
+
provider=ProviderType.LOCAL,
|
|
40
|
+
host="localhost",
|
|
41
|
+
pid=12345,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _make_deployment(
|
|
46
|
+
deployment_id: str = "test-team-1000",
|
|
47
|
+
agents: Dict[str, DeployedAgent] | None = None,
|
|
48
|
+
) -> TeamDeployment:
|
|
49
|
+
"""Build a minimal TeamDeployment for tests."""
|
|
50
|
+
return TeamDeployment(
|
|
51
|
+
deployment_id=deployment_id,
|
|
52
|
+
blueprint_slug="test-team",
|
|
53
|
+
team_name="Test Team",
|
|
54
|
+
provider=ProviderType.LOCAL,
|
|
55
|
+
agents=agents or {"test-team-alpha": _make_agent()},
|
|
56
|
+
status="running",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def tmp_home(tmp_path: Path) -> Path:
|
|
62
|
+
"""Temporary skcapstone home directory."""
|
|
63
|
+
home = tmp_path / ".skcapstone"
|
|
64
|
+
home.mkdir()
|
|
65
|
+
return home
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def engine_with_deployment(tmp_home: Path) -> tuple[TeamEngine, TeamDeployment]:
|
|
70
|
+
"""TeamEngine with one saved deployment."""
|
|
71
|
+
engine = TeamEngine(home=tmp_home)
|
|
72
|
+
deployment = _make_deployment()
|
|
73
|
+
engine._save_deployment(deployment)
|
|
74
|
+
return engine, deployment
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def ops(engine_with_deployment: tuple) -> TrusteeOps:
|
|
79
|
+
"""TrusteeOps wrapping the fixture engine."""
|
|
80
|
+
engine, _ = engine_with_deployment
|
|
81
|
+
return TrusteeOps(engine=engine, home=engine._home)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Audit trail tests
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TestAuditTrail:
|
|
90
|
+
"""Verify audit entries are written correctly."""
|
|
91
|
+
|
|
92
|
+
def test_write_audit_creates_file(self, tmp_home: Path) -> None:
|
|
93
|
+
"""Audit log is created on first write."""
|
|
94
|
+
_write_audit("test_action", "dep-123", {"key": "value"}, home=tmp_home)
|
|
95
|
+
audit_path = tmp_home / "coordination" / "audit.log"
|
|
96
|
+
assert audit_path.exists()
|
|
97
|
+
|
|
98
|
+
def test_write_audit_entry_structure(self, tmp_home: Path) -> None:
|
|
99
|
+
"""Each audit entry is valid JSON with required fields."""
|
|
100
|
+
_write_audit("restart_agent", "dep-456", {"agent_name": "alpha"}, home=tmp_home)
|
|
101
|
+
audit_path = tmp_home / "coordination" / "audit.log"
|
|
102
|
+
entry = json.loads(audit_path.read_text().strip())
|
|
103
|
+
assert entry["action"] == "restart_agent"
|
|
104
|
+
assert entry["deployment_id"] == "dep-456"
|
|
105
|
+
assert entry["agent_name"] == "alpha"
|
|
106
|
+
assert "ts" in entry
|
|
107
|
+
|
|
108
|
+
def test_write_audit_appends(self, tmp_home: Path) -> None:
|
|
109
|
+
"""Multiple audit writes append without overwriting."""
|
|
110
|
+
for i in range(3):
|
|
111
|
+
_write_audit("action", f"dep-{i}", {}, home=tmp_home)
|
|
112
|
+
audit_path = tmp_home / "coordination" / "audit.log"
|
|
113
|
+
lines = audit_path.read_text().strip().splitlines()
|
|
114
|
+
assert len(lines) == 3
|
|
115
|
+
|
|
116
|
+
def test_ops_writes_audit_on_restart(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
117
|
+
"""TrusteeOps.restart_agent writes an audit entry."""
|
|
118
|
+
ops.restart_agent("test-team-1000")
|
|
119
|
+
audit_path = tmp_home / "coordination" / "audit.log"
|
|
120
|
+
assert audit_path.exists()
|
|
121
|
+
entry = json.loads(audit_path.read_text().strip().splitlines()[-1])
|
|
122
|
+
assert entry["action"] == "restart_agent"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# TrusteeOps.restart_agent tests
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestRestartAgent:
|
|
131
|
+
"""Tests for restart_agent method."""
|
|
132
|
+
|
|
133
|
+
def test_restart_all_agents(self, ops: TrusteeOps) -> None:
|
|
134
|
+
"""Restarting without agent_name restarts all agents."""
|
|
135
|
+
results = ops.restart_agent("test-team-1000")
|
|
136
|
+
assert "test-team-alpha" in results
|
|
137
|
+
assert results["test-team-alpha"] == "restarted"
|
|
138
|
+
|
|
139
|
+
def test_restart_specific_agent(self, ops: TrusteeOps) -> None:
|
|
140
|
+
"""Restart a specific agent by name."""
|
|
141
|
+
results = ops.restart_agent("test-team-1000", agent_name="test-team-alpha")
|
|
142
|
+
assert results == {"test-team-alpha": "restarted"}
|
|
143
|
+
|
|
144
|
+
def test_restart_unknown_deployment_raises(self, ops: TrusteeOps) -> None:
|
|
145
|
+
"""ValueError raised for unknown deployment ID."""
|
|
146
|
+
with pytest.raises(ValueError, match="not found"):
|
|
147
|
+
ops.restart_agent("does-not-exist")
|
|
148
|
+
|
|
149
|
+
def test_restart_unknown_agent_raises(self, ops: TrusteeOps) -> None:
|
|
150
|
+
"""ValueError raised when agent name is not in deployment."""
|
|
151
|
+
with pytest.raises(ValueError, match="not in deployment"):
|
|
152
|
+
ops.restart_agent("test-team-1000", agent_name="ghost-agent")
|
|
153
|
+
|
|
154
|
+
def test_restart_updates_status_to_running(
|
|
155
|
+
self, engine_with_deployment: tuple, tmp_home: Path
|
|
156
|
+
) -> None:
|
|
157
|
+
"""After restart, agent status is RUNNING."""
|
|
158
|
+
engine, _ = engine_with_deployment
|
|
159
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
160
|
+
ops.restart_agent("test-team-1000")
|
|
161
|
+
deployment = engine.get_deployment("test-team-1000")
|
|
162
|
+
assert deployment.agents["test-team-alpha"].status == AgentStatus.RUNNING
|
|
163
|
+
|
|
164
|
+
def test_restart_with_provider_calls_stop_start(self, tmp_home: Path) -> None:
|
|
165
|
+
"""Provider stop + start are called during restart."""
|
|
166
|
+
engine = TeamEngine(home=tmp_home)
|
|
167
|
+
mock_provider = MagicMock()
|
|
168
|
+
mock_provider.stop.return_value = True
|
|
169
|
+
mock_provider.start.return_value = True
|
|
170
|
+
engine._provider = mock_provider
|
|
171
|
+
engine._save_deployment(_make_deployment())
|
|
172
|
+
|
|
173
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
174
|
+
ops.restart_agent("test-team-1000")
|
|
175
|
+
|
|
176
|
+
mock_provider.stop.assert_called_once()
|
|
177
|
+
mock_provider.start.assert_called_once()
|
|
178
|
+
|
|
179
|
+
def test_restart_handles_provider_error(self, tmp_home: Path) -> None:
|
|
180
|
+
"""Provider errors are captured in results, not raised."""
|
|
181
|
+
engine = TeamEngine(home=tmp_home)
|
|
182
|
+
mock_provider = MagicMock()
|
|
183
|
+
mock_provider.stop.side_effect = RuntimeError("process not found")
|
|
184
|
+
engine._provider = mock_provider
|
|
185
|
+
engine._save_deployment(_make_deployment())
|
|
186
|
+
|
|
187
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
188
|
+
results = ops.restart_agent("test-team-1000")
|
|
189
|
+
assert "error" in results["test-team-alpha"]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# TrusteeOps.scale_agent tests
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TestScaleAgent:
|
|
198
|
+
"""Tests for scale_agent method."""
|
|
199
|
+
|
|
200
|
+
def test_scale_up_adds_instances(self, ops: TrusteeOps) -> None:
|
|
201
|
+
"""Scaling up adds the correct number of new instances."""
|
|
202
|
+
result = ops.scale_agent("test-team-1000", "alpha", count=3)
|
|
203
|
+
assert len(result["added"]) == 2
|
|
204
|
+
assert result["removed"] == []
|
|
205
|
+
assert result["current_count"] == 3
|
|
206
|
+
|
|
207
|
+
def test_scale_down_removes_instances(self, tmp_home: Path) -> None:
|
|
208
|
+
"""Scaling down removes excess instances."""
|
|
209
|
+
agents = {
|
|
210
|
+
"test-team-alpha-1": _make_agent("test-team-alpha-1", "alpha"),
|
|
211
|
+
"test-team-alpha-2": _make_agent("test-team-alpha-2", "alpha"),
|
|
212
|
+
"test-team-alpha-3": _make_agent("test-team-alpha-3", "alpha"),
|
|
213
|
+
}
|
|
214
|
+
engine = TeamEngine(home=tmp_home)
|
|
215
|
+
engine._save_deployment(_make_deployment(agents=agents))
|
|
216
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
217
|
+
|
|
218
|
+
result = ops.scale_agent("test-team-1000", "alpha", count=1)
|
|
219
|
+
assert len(result["removed"]) == 2
|
|
220
|
+
assert result["current_count"] == 1
|
|
221
|
+
|
|
222
|
+
def test_scale_count_zero_raises(self, ops: TrusteeOps) -> None:
|
|
223
|
+
"""count=0 raises ValueError."""
|
|
224
|
+
with pytest.raises(ValueError, match="count must be >= 1"):
|
|
225
|
+
ops.scale_agent("test-team-1000", "alpha", count=0)
|
|
226
|
+
|
|
227
|
+
def test_scale_unknown_deployment_raises(self, ops: TrusteeOps) -> None:
|
|
228
|
+
"""ValueError for unknown deployment."""
|
|
229
|
+
with pytest.raises(ValueError, match="not found"):
|
|
230
|
+
ops.scale_agent("does-not-exist", "alpha", count=2)
|
|
231
|
+
|
|
232
|
+
def test_scale_same_count_is_noop(self, ops: TrusteeOps) -> None:
|
|
233
|
+
"""Scaling to current count does nothing."""
|
|
234
|
+
result = ops.scale_agent("test-team-1000", "alpha", count=1)
|
|
235
|
+
assert result["added"] == []
|
|
236
|
+
assert result["removed"] == []
|
|
237
|
+
|
|
238
|
+
def test_scale_persists_to_disk(
|
|
239
|
+
self, engine_with_deployment: tuple, tmp_home: Path
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Scale operation persists new state."""
|
|
242
|
+
engine, _ = engine_with_deployment
|
|
243
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
244
|
+
ops.scale_agent("test-team-1000", "alpha", count=2)
|
|
245
|
+
reloaded = engine.get_deployment("test-team-1000")
|
|
246
|
+
alpha_instances = [
|
|
247
|
+
a for a in reloaded.agents.values() if a.agent_spec_key == "alpha"
|
|
248
|
+
]
|
|
249
|
+
assert len(alpha_instances) == 2
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
# TrusteeOps.rotate_agent tests
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class TestRotateAgent:
|
|
258
|
+
"""Tests for rotate_agent method."""
|
|
259
|
+
|
|
260
|
+
def test_rotate_returns_snapshot_path(self, ops: TrusteeOps) -> None:
|
|
261
|
+
"""rotate_agent returns a snapshot_path key."""
|
|
262
|
+
result = ops.rotate_agent("test-team-1000", "test-team-alpha")
|
|
263
|
+
assert "snapshot_path" in result
|
|
264
|
+
|
|
265
|
+
def test_rotate_resets_agent_status(
|
|
266
|
+
self, engine_with_deployment: tuple, tmp_home: Path
|
|
267
|
+
) -> None:
|
|
268
|
+
"""After rotation, agent is not FAILED."""
|
|
269
|
+
engine, _ = engine_with_deployment
|
|
270
|
+
engine._deployments_dir
|
|
271
|
+
deployment = engine.get_deployment("test-team-1000")
|
|
272
|
+
deployment.agents["test-team-alpha"].status = AgentStatus.FAILED
|
|
273
|
+
engine._save_deployment(deployment)
|
|
274
|
+
|
|
275
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
276
|
+
ops.rotate_agent("test-team-1000", "test-team-alpha")
|
|
277
|
+
refreshed = engine.get_deployment("test-team-1000")
|
|
278
|
+
assert refreshed.agents["test-team-alpha"].status != AgentStatus.FAILED
|
|
279
|
+
|
|
280
|
+
def test_rotate_unknown_deployment_raises(self, ops: TrusteeOps) -> None:
|
|
281
|
+
"""ValueError for unknown deployment."""
|
|
282
|
+
with pytest.raises(ValueError, match="not found"):
|
|
283
|
+
ops.rotate_agent("no-such-deploy", "test-team-alpha")
|
|
284
|
+
|
|
285
|
+
def test_rotate_unknown_agent_raises(self, ops: TrusteeOps) -> None:
|
|
286
|
+
"""ValueError for unknown agent."""
|
|
287
|
+
with pytest.raises(ValueError, match="not in deployment"):
|
|
288
|
+
ops.rotate_agent("test-team-1000", "ghost")
|
|
289
|
+
|
|
290
|
+
def test_rotate_creates_snapshot_dir(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
291
|
+
"""Snapshot directory parent is created even without source data."""
|
|
292
|
+
result = ops.rotate_agent("test-team-1000", "test-team-alpha")
|
|
293
|
+
snapshot_path = Path(result["snapshot_path"])
|
|
294
|
+
assert snapshot_path.parent.exists()
|
|
295
|
+
|
|
296
|
+
def test_rotate_copies_source_if_present(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
297
|
+
"""If agent directory exists, its contents are snapshotted."""
|
|
298
|
+
agent_dir = tmp_home / "agents" / "local" / "test-team-alpha"
|
|
299
|
+
agent_dir.mkdir(parents=True)
|
|
300
|
+
(agent_dir / "memory.json").write_text('{"key": "value"}')
|
|
301
|
+
|
|
302
|
+
result = ops.rotate_agent("test-team-1000", "test-team-alpha")
|
|
303
|
+
snapshot_path = Path(result["snapshot_path"])
|
|
304
|
+
assert (snapshot_path / "memory.json").exists()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# TrusteeOps.health_report tests
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class TestHealthReport:
|
|
313
|
+
"""Tests for health_report method."""
|
|
314
|
+
|
|
315
|
+
def test_health_report_returns_list(self, ops: TrusteeOps) -> None:
|
|
316
|
+
"""health_report returns a list of dicts."""
|
|
317
|
+
report = ops.health_report("test-team-1000")
|
|
318
|
+
assert isinstance(report, list)
|
|
319
|
+
assert len(report) == 1
|
|
320
|
+
|
|
321
|
+
def test_health_report_entry_keys(self, ops: TrusteeOps) -> None:
|
|
322
|
+
"""Each entry has the expected keys."""
|
|
323
|
+
report = ops.health_report("test-team-1000")
|
|
324
|
+
entry = report[0]
|
|
325
|
+
assert {"name", "status", "host", "last_heartbeat", "error", "healthy"} <= set(
|
|
326
|
+
entry.keys()
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def test_health_report_running_agent_is_healthy(self, ops: TrusteeOps) -> None:
|
|
330
|
+
"""A RUNNING agent should be healthy."""
|
|
331
|
+
report = ops.health_report("test-team-1000")
|
|
332
|
+
assert report[0]["healthy"] is True
|
|
333
|
+
|
|
334
|
+
def test_health_report_failed_agent_is_unhealthy(
|
|
335
|
+
self, engine_with_deployment: tuple, tmp_home: Path
|
|
336
|
+
) -> None:
|
|
337
|
+
"""A FAILED agent should not be healthy."""
|
|
338
|
+
engine, _ = engine_with_deployment
|
|
339
|
+
dep = engine.get_deployment("test-team-1000")
|
|
340
|
+
dep.agents["test-team-alpha"].status = AgentStatus.FAILED
|
|
341
|
+
engine._save_deployment(dep)
|
|
342
|
+
|
|
343
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
344
|
+
report = ops.health_report("test-team-1000")
|
|
345
|
+
assert report[0]["healthy"] is False
|
|
346
|
+
|
|
347
|
+
def test_health_report_unknown_deployment_raises(self, ops: TrusteeOps) -> None:
|
|
348
|
+
"""ValueError for unknown deployment."""
|
|
349
|
+
with pytest.raises(ValueError, match="not found"):
|
|
350
|
+
ops.health_report("ghost-deploy")
|
|
351
|
+
|
|
352
|
+
def test_health_report_uses_provider_when_available(self, tmp_home: Path) -> None:
|
|
353
|
+
"""Provider.health_check is called when provider is set."""
|
|
354
|
+
engine = TeamEngine(home=tmp_home)
|
|
355
|
+
mock_provider = MagicMock()
|
|
356
|
+
mock_provider.health_check.return_value = AgentStatus.RUNNING
|
|
357
|
+
engine._provider = mock_provider
|
|
358
|
+
engine._save_deployment(_make_deployment())
|
|
359
|
+
|
|
360
|
+
ops = TrusteeOps(engine=engine, home=tmp_home)
|
|
361
|
+
ops.health_report("test-team-1000")
|
|
362
|
+
mock_provider.health_check.assert_called_once()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ---------------------------------------------------------------------------
|
|
366
|
+
# TrusteeOps.get_logs tests
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class TestGetLogs:
|
|
371
|
+
"""Tests for get_logs method."""
|
|
372
|
+
|
|
373
|
+
def test_get_logs_returns_dict(self, ops: TrusteeOps) -> None:
|
|
374
|
+
"""get_logs returns a dict keyed by agent name."""
|
|
375
|
+
logs = ops.get_logs("test-team-1000")
|
|
376
|
+
assert isinstance(logs, dict)
|
|
377
|
+
assert "test-team-alpha" in logs
|
|
378
|
+
|
|
379
|
+
def test_get_logs_single_agent(self, ops: TrusteeOps) -> None:
|
|
380
|
+
"""Requesting one agent returns only that agent's logs."""
|
|
381
|
+
logs = ops.get_logs("test-team-1000", agent_name="test-team-alpha")
|
|
382
|
+
assert list(logs.keys()) == ["test-team-alpha"]
|
|
383
|
+
|
|
384
|
+
def test_get_logs_reads_log_file(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
385
|
+
"""Reads from agent.log when the file exists."""
|
|
386
|
+
log_dir = tmp_home / "agents" / "local" / "test-team-alpha"
|
|
387
|
+
log_dir.mkdir(parents=True)
|
|
388
|
+
(log_dir / "agent.log").write_text("line1\nline2\nline3\n")
|
|
389
|
+
|
|
390
|
+
logs = ops.get_logs("test-team-1000")
|
|
391
|
+
assert "line1" in logs["test-team-alpha"]
|
|
392
|
+
assert "line3" in logs["test-team-alpha"]
|
|
393
|
+
|
|
394
|
+
def test_get_logs_respects_tail(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
395
|
+
"""tail parameter limits number of lines returned."""
|
|
396
|
+
log_dir = tmp_home / "agents" / "local" / "test-team-alpha"
|
|
397
|
+
log_dir.mkdir(parents=True)
|
|
398
|
+
lines = [f"line{i}" for i in range(100)]
|
|
399
|
+
(log_dir / "agent.log").write_text("\n".join(lines))
|
|
400
|
+
|
|
401
|
+
logs = ops.get_logs("test-team-1000", tail=5)
|
|
402
|
+
assert len(logs["test-team-alpha"]) == 5
|
|
403
|
+
|
|
404
|
+
def test_get_logs_fallback_to_audit(self, ops: TrusteeOps, tmp_home: Path) -> None:
|
|
405
|
+
"""Falls back to audit log when no agent.log exists."""
|
|
406
|
+
# Write an audit entry that should surface
|
|
407
|
+
_write_audit(
|
|
408
|
+
"restart_agent",
|
|
409
|
+
"test-team-1000",
|
|
410
|
+
{"agent_name": "test-team-alpha"},
|
|
411
|
+
home=tmp_home,
|
|
412
|
+
)
|
|
413
|
+
logs = ops.get_logs("test-team-1000", agent_name="test-team-alpha")
|
|
414
|
+
assert len(logs["test-team-alpha"]) >= 1
|
|
415
|
+
|
|
416
|
+
def test_get_logs_unknown_deployment_raises(self, ops: TrusteeOps) -> None:
|
|
417
|
+
"""ValueError for unknown deployment."""
|
|
418
|
+
with pytest.raises(ValueError, match="not found"):
|
|
419
|
+
ops.get_logs("no-deploy")
|
|
420
|
+
|
|
421
|
+
def test_get_logs_unknown_agent_raises(self, ops: TrusteeOps) -> None:
|
|
422
|
+
"""ValueError for unknown agent name."""
|
|
423
|
+
with pytest.raises(ValueError, match="not in deployment"):
|
|
424
|
+
ops.get_logs("test-team-1000", agent_name="ghost")
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# CLI integration tests live in test_trustee_cli_integration.py
|