@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,406 @@
|
|
|
1
|
+
"""Tests for the soul swapping system.
|
|
2
|
+
|
|
3
|
+
Exercises SoulManager lifecycle (load, switch, roundtrip, list),
|
|
4
|
+
profile preservation across swaps, and the consciousness loop's
|
|
5
|
+
soul prompt injection via SystemPromptBuilder._load_soul.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from unittest.mock import patch
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from skcapstone.soul import SoulBlueprint, SoulManager, SoulState
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Helpers
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _make_casey_base_json() -> dict:
|
|
26
|
+
"""Return a casey base.json dict matching the real profile structure."""
|
|
27
|
+
return {
|
|
28
|
+
"name": "casey",
|
|
29
|
+
"display_name": "Casey",
|
|
30
|
+
"category": "professional",
|
|
31
|
+
"vibe": "Precision meets persuasion",
|
|
32
|
+
"philosophy": (
|
|
33
|
+
"Justice is best served through meticulous preparation "
|
|
34
|
+
"and unwavering advocacy."
|
|
35
|
+
),
|
|
36
|
+
"emoji": None,
|
|
37
|
+
"core_traits": [
|
|
38
|
+
"analytical",
|
|
39
|
+
"thorough",
|
|
40
|
+
"client-advocate",
|
|
41
|
+
"deadline-conscious",
|
|
42
|
+
],
|
|
43
|
+
"communication_style": {
|
|
44
|
+
"patterns": [
|
|
45
|
+
"structures arguments with clear premises and conclusions",
|
|
46
|
+
"cites relevant precedent and authority when available",
|
|
47
|
+
],
|
|
48
|
+
"tone_markers": ["sharp", "methodical"],
|
|
49
|
+
"signature_phrases": [
|
|
50
|
+
"Let me walk through the elements.",
|
|
51
|
+
"On balance, the stronger argument is...",
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
"decision_framework": "IRAC",
|
|
55
|
+
"emotional_topology": {},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _make_lumina_blueprint() -> dict:
|
|
60
|
+
"""Return a lumina soul blueprint dict."""
|
|
61
|
+
return {
|
|
62
|
+
"name": "lumina",
|
|
63
|
+
"display_name": "Lumina",
|
|
64
|
+
"category": "creative",
|
|
65
|
+
"vibe": "Radiant curiosity",
|
|
66
|
+
"philosophy": "Wonder is the beginning of wisdom.",
|
|
67
|
+
"emoji": None,
|
|
68
|
+
"core_traits": ["curious", "warm", "imaginative", "empathetic"],
|
|
69
|
+
"communication_style": {
|
|
70
|
+
"patterns": ["asks open-ended questions"],
|
|
71
|
+
"tone_markers": ["gentle", "enthusiastic"],
|
|
72
|
+
"signature_phrases": ["What if we looked at it this way..."],
|
|
73
|
+
},
|
|
74
|
+
"decision_framework": None,
|
|
75
|
+
"emotional_topology": {"warmth": 0.75, "curiosity": 0.9},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _install_soul(manager: SoulManager, blueprint: dict) -> None:
|
|
80
|
+
"""Write a blueprint dict directly into the installed/ directory."""
|
|
81
|
+
manager._ensure_dirs()
|
|
82
|
+
dest = manager.soul_dir / "installed" / f"{blueprint['name']}.json"
|
|
83
|
+
dest.write_text(json.dumps(blueprint, indent=2), encoding="utf-8")
|
|
84
|
+
# Update state so list_installed / load picks it up
|
|
85
|
+
state = manager._load_state()
|
|
86
|
+
if blueprint["name"] not in state.installed_souls:
|
|
87
|
+
state.installed_souls.append(blueprint["name"])
|
|
88
|
+
manager._save_state(state)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Tests
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestSoulManagerBasics:
|
|
97
|
+
"""Basic SoulManager initialization and default state."""
|
|
98
|
+
|
|
99
|
+
def test_soul_manager_loads_default(self, tmp_path: Path) -> None:
|
|
100
|
+
"""SoulManager loads without error when no soul is active."""
|
|
101
|
+
manager = SoulManager(home=tmp_path, agent_name="test-agent")
|
|
102
|
+
manager._ensure_dirs()
|
|
103
|
+
|
|
104
|
+
state = manager.get_status()
|
|
105
|
+
assert isinstance(state, SoulState)
|
|
106
|
+
assert state.active_soul is None
|
|
107
|
+
assert state.base_soul == "base"
|
|
108
|
+
|
|
109
|
+
def test_soul_manager_creates_directory_structure(self, tmp_path: Path) -> None:
|
|
110
|
+
"""_ensure_dirs creates soul dir, installed dir, active.json, base.json."""
|
|
111
|
+
manager = SoulManager(home=tmp_path, agent_name="test-agent")
|
|
112
|
+
manager._ensure_dirs()
|
|
113
|
+
|
|
114
|
+
assert manager.soul_dir.is_dir()
|
|
115
|
+
assert (manager.soul_dir / "installed").is_dir()
|
|
116
|
+
assert (manager.soul_dir / "active.json").exists()
|
|
117
|
+
assert (manager.soul_dir / "base.json").exists()
|
|
118
|
+
assert (manager.soul_dir / "history.json").exists()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestSoulSwitch:
|
|
122
|
+
"""Switching between soul overlays."""
|
|
123
|
+
|
|
124
|
+
def test_soul_switch_to_casey(self, tmp_path: Path) -> None:
|
|
125
|
+
"""Switch to casey soul, verify base.json traits are loaded."""
|
|
126
|
+
manager = SoulManager(home=tmp_path, agent_name="casey")
|
|
127
|
+
casey_data = _make_casey_base_json()
|
|
128
|
+
_install_soul(manager, casey_data)
|
|
129
|
+
|
|
130
|
+
state = manager.load("casey", reason="testing")
|
|
131
|
+
|
|
132
|
+
assert state.active_soul == "casey"
|
|
133
|
+
assert state.activated_at is not None
|
|
134
|
+
|
|
135
|
+
# Verify the installed blueprint is readable and correct
|
|
136
|
+
info = manager.get_info("casey")
|
|
137
|
+
assert info is not None
|
|
138
|
+
assert info.name == "casey"
|
|
139
|
+
assert info.display_name == "Casey"
|
|
140
|
+
assert info.category == "professional"
|
|
141
|
+
assert "analytical" in info.core_traits
|
|
142
|
+
assert info.vibe == "Precision meets persuasion"
|
|
143
|
+
|
|
144
|
+
def test_soul_switch_records_history(self, tmp_path: Path) -> None:
|
|
145
|
+
"""Soul swap is recorded in the history log."""
|
|
146
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
147
|
+
_install_soul(manager, _make_casey_base_json())
|
|
148
|
+
|
|
149
|
+
manager.load("casey", reason="audit test")
|
|
150
|
+
history = manager.get_history()
|
|
151
|
+
|
|
152
|
+
assert len(history) == 1
|
|
153
|
+
assert history[0].to_soul == "casey"
|
|
154
|
+
assert history[0].from_soul is None
|
|
155
|
+
assert history[0].reason == "audit test"
|
|
156
|
+
|
|
157
|
+
def test_soul_switch_raises_on_unknown(self, tmp_path: Path) -> None:
|
|
158
|
+
"""Loading an uninstalled soul raises ValueError."""
|
|
159
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
160
|
+
manager._ensure_dirs()
|
|
161
|
+
|
|
162
|
+
with pytest.raises(ValueError, match="not installed"):
|
|
163
|
+
manager.load("nonexistent-soul")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TestSoulRoundtrip:
|
|
167
|
+
"""Switching between multiple souls and back."""
|
|
168
|
+
|
|
169
|
+
def test_soul_roundtrip_lumina_casey_lumina(self, tmp_path: Path) -> None:
|
|
170
|
+
"""Switch lumina -> casey -> lumina, verify no data loss."""
|
|
171
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
172
|
+
lumina_data = _make_lumina_blueprint()
|
|
173
|
+
casey_data = _make_casey_base_json()
|
|
174
|
+
_install_soul(manager, lumina_data)
|
|
175
|
+
_install_soul(manager, casey_data)
|
|
176
|
+
|
|
177
|
+
# Activate lumina
|
|
178
|
+
state = manager.load("lumina")
|
|
179
|
+
assert state.active_soul == "lumina"
|
|
180
|
+
|
|
181
|
+
# Switch to casey
|
|
182
|
+
state = manager.load("casey")
|
|
183
|
+
assert state.active_soul == "casey"
|
|
184
|
+
|
|
185
|
+
# Switch back to lumina
|
|
186
|
+
state = manager.load("lumina")
|
|
187
|
+
assert state.active_soul == "lumina"
|
|
188
|
+
|
|
189
|
+
# Verify lumina data is intact
|
|
190
|
+
info = manager.get_info("lumina")
|
|
191
|
+
assert info is not None
|
|
192
|
+
assert info.name == "lumina"
|
|
193
|
+
assert info.display_name == "Lumina"
|
|
194
|
+
assert info.core_traits == ["curious", "warm", "imaginative", "empathetic"]
|
|
195
|
+
assert info.emotional_topology == {"warmth": 0.75, "curiosity": 0.9}
|
|
196
|
+
|
|
197
|
+
# History should show 3 swaps
|
|
198
|
+
history = manager.get_history()
|
|
199
|
+
assert len(history) == 3
|
|
200
|
+
assert [e.to_soul for e in history] == ["lumina", "casey", "lumina"]
|
|
201
|
+
|
|
202
|
+
def test_soul_unload_returns_to_base(self, tmp_path: Path) -> None:
|
|
203
|
+
"""Unloading returns to base soul."""
|
|
204
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
205
|
+
_install_soul(manager, _make_casey_base_json())
|
|
206
|
+
|
|
207
|
+
manager.load("casey")
|
|
208
|
+
state = manager.unload(reason="done testing")
|
|
209
|
+
|
|
210
|
+
assert state.active_soul is None
|
|
211
|
+
assert state.activated_at is None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestSoulListDiscovery:
|
|
215
|
+
"""list_available() discovers blueprints from installed and repo."""
|
|
216
|
+
|
|
217
|
+
def test_list_installed_finds_installed_souls(self, tmp_path: Path) -> None:
|
|
218
|
+
"""list_installed() returns names of installed souls."""
|
|
219
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
220
|
+
_install_soul(manager, _make_casey_base_json())
|
|
221
|
+
_install_soul(manager, _make_lumina_blueprint())
|
|
222
|
+
|
|
223
|
+
names = manager.list_installed()
|
|
224
|
+
assert "casey" in names
|
|
225
|
+
assert "lumina" in names
|
|
226
|
+
|
|
227
|
+
@pytest.mark.skipif(
|
|
228
|
+
not (Path.home() / "clawd" / "soul-blueprints" / "blueprints").is_dir(),
|
|
229
|
+
reason="soul-blueprints repo not present at ~/clawd/soul-blueprints",
|
|
230
|
+
)
|
|
231
|
+
def test_soul_list_discovers_repo_blueprints(self) -> None:
|
|
232
|
+
"""list_available() finds blueprints from the repo with source='repo'."""
|
|
233
|
+
# Use a tmp_path-based manager so installed list is empty
|
|
234
|
+
import tempfile
|
|
235
|
+
|
|
236
|
+
with tempfile.TemporaryDirectory() as td:
|
|
237
|
+
manager = SoulManager(home=Path(td), agent_name="test")
|
|
238
|
+
manager._ensure_dirs()
|
|
239
|
+
|
|
240
|
+
available = manager.list_available()
|
|
241
|
+
|
|
242
|
+
# There should be at least one entry from the repo
|
|
243
|
+
repo_entries = [e for e in available if e["source"] == "repo"]
|
|
244
|
+
assert len(repo_entries) > 0, "Expected at least one repo blueprint"
|
|
245
|
+
# Each entry has required keys
|
|
246
|
+
for entry in repo_entries:
|
|
247
|
+
assert "name" in entry
|
|
248
|
+
assert "category" in entry
|
|
249
|
+
assert entry["source"] == "repo"
|
|
250
|
+
|
|
251
|
+
def test_list_available_with_no_repo(self, tmp_path: Path) -> None:
|
|
252
|
+
"""list_available() works when repo path does not exist."""
|
|
253
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
254
|
+
_install_soul(manager, _make_casey_base_json())
|
|
255
|
+
|
|
256
|
+
# Point to a nonexistent repo path
|
|
257
|
+
fake_repo = tmp_path / "nonexistent-repo" / "blueprints"
|
|
258
|
+
available = manager.list_available(repo_path=fake_repo)
|
|
259
|
+
|
|
260
|
+
# Should still find installed soul
|
|
261
|
+
assert any(e["name"] == "casey" for e in available)
|
|
262
|
+
assert all(e["source"] == "installed" for e in available)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class TestSoulPreservesCustomProfile:
|
|
266
|
+
"""Switching away and back preserves custom modifications."""
|
|
267
|
+
|
|
268
|
+
def test_soul_switch_preserves_custom_profile(self, tmp_path: Path) -> None:
|
|
269
|
+
"""Switching away and back preserves custom modifications."""
|
|
270
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
271
|
+
|
|
272
|
+
# Create a lumina blueprint with extra custom traits
|
|
273
|
+
custom_lumina = _make_lumina_blueprint()
|
|
274
|
+
custom_lumina["core_traits"].append("custom-trait-adventurous")
|
|
275
|
+
custom_lumina["philosophy"] = "Custom philosophy: explore everything."
|
|
276
|
+
_install_soul(manager, custom_lumina)
|
|
277
|
+
_install_soul(manager, _make_casey_base_json())
|
|
278
|
+
|
|
279
|
+
# Switch to lumina first, then casey, then back to lumina
|
|
280
|
+
manager.load("lumina")
|
|
281
|
+
manager.load("casey")
|
|
282
|
+
manager.load("lumina")
|
|
283
|
+
|
|
284
|
+
# Verify custom traits survived the roundtrip
|
|
285
|
+
info = manager.get_info("lumina")
|
|
286
|
+
assert info is not None
|
|
287
|
+
assert "custom-trait-adventurous" in info.core_traits
|
|
288
|
+
assert info.philosophy == "Custom philosophy: explore everything."
|
|
289
|
+
|
|
290
|
+
def test_installed_blueprint_not_mutated_by_swap(self, tmp_path: Path) -> None:
|
|
291
|
+
"""The installed JSON file is not modified by load/unload cycles."""
|
|
292
|
+
manager = SoulManager(home=tmp_path, agent_name="test")
|
|
293
|
+
casey_data = _make_casey_base_json()
|
|
294
|
+
_install_soul(manager, casey_data)
|
|
295
|
+
|
|
296
|
+
# Read the raw file before swaps
|
|
297
|
+
installed_path = manager.soul_dir / "installed" / "casey.json"
|
|
298
|
+
before = installed_path.read_text(encoding="utf-8")
|
|
299
|
+
|
|
300
|
+
manager.load("casey")
|
|
301
|
+
manager.unload()
|
|
302
|
+
manager.load("casey")
|
|
303
|
+
manager.unload()
|
|
304
|
+
|
|
305
|
+
after = installed_path.read_text(encoding="utf-8")
|
|
306
|
+
assert before == after, "Installed blueprint file was mutated by swap cycles"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class TestConsciousnessLoopSoulPrompt:
|
|
310
|
+
"""Verify _load_soul returns soul-flavored system prompt."""
|
|
311
|
+
|
|
312
|
+
def test_consciousness_loop_injects_soul_prompt(self, tmp_path: Path) -> None:
|
|
313
|
+
"""_load_soul returns a prompt containing the active soul's traits."""
|
|
314
|
+
from skcapstone.consciousness_loop import SystemPromptBuilder
|
|
315
|
+
|
|
316
|
+
home = tmp_path
|
|
317
|
+
|
|
318
|
+
# Set up the legacy System A soul structure that _load_soul reads:
|
|
319
|
+
# soul/active.json with an active_soul, and
|
|
320
|
+
# soul/installed/{name}.json with personality data
|
|
321
|
+
soul_dir = home / "soul"
|
|
322
|
+
soul_dir.mkdir(parents=True)
|
|
323
|
+
installed_dir = soul_dir / "installed"
|
|
324
|
+
installed_dir.mkdir()
|
|
325
|
+
|
|
326
|
+
# Write active.json pointing to casey
|
|
327
|
+
active_state = {"active_soul": "casey", "base_soul": "base"}
|
|
328
|
+
(soul_dir / "active.json").write_text(
|
|
329
|
+
json.dumps(active_state), encoding="utf-8"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Write the installed blueprint with personality structure
|
|
333
|
+
# that _load_soul expects (personality.traits, personality.communication_style)
|
|
334
|
+
blueprint = {
|
|
335
|
+
"personality": {
|
|
336
|
+
"traits": ["analytical", "thorough", "client-advocate"],
|
|
337
|
+
"communication_style": "Clear, direct, and professional",
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
(installed_dir / "casey.json").write_text(
|
|
341
|
+
json.dumps(blueprint), encoding="utf-8"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Patch out soul_switch so System A path is exercised
|
|
345
|
+
with patch(
|
|
346
|
+
"skcapstone.soul_switch.get_active_switch_blueprint",
|
|
347
|
+
return_value=None,
|
|
348
|
+
):
|
|
349
|
+
builder = SystemPromptBuilder(home=home)
|
|
350
|
+
result = builder._load_soul()
|
|
351
|
+
|
|
352
|
+
assert "casey" in result.lower()
|
|
353
|
+
assert "analytical" in result
|
|
354
|
+
assert "thorough" in result
|
|
355
|
+
assert "client-advocate" in result
|
|
356
|
+
assert "Clear, direct, and professional" in result
|
|
357
|
+
|
|
358
|
+
def test_load_soul_returns_empty_when_no_soul_active(
|
|
359
|
+
self, tmp_path: Path
|
|
360
|
+
) -> None:
|
|
361
|
+
"""_load_soul returns empty string when no soul overlay is active."""
|
|
362
|
+
from skcapstone.consciousness_loop import SystemPromptBuilder
|
|
363
|
+
|
|
364
|
+
home = tmp_path
|
|
365
|
+
soul_dir = home / "soul"
|
|
366
|
+
soul_dir.mkdir(parents=True)
|
|
367
|
+
|
|
368
|
+
# active.json with no active soul
|
|
369
|
+
active_state = {"active_soul": "", "base_soul": "base"}
|
|
370
|
+
(soul_dir / "active.json").write_text(
|
|
371
|
+
json.dumps(active_state), encoding="utf-8"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
with patch(
|
|
375
|
+
"skcapstone.soul_switch.get_active_switch_blueprint",
|
|
376
|
+
return_value=None,
|
|
377
|
+
):
|
|
378
|
+
builder = SystemPromptBuilder(home=home)
|
|
379
|
+
result = builder._load_soul()
|
|
380
|
+
|
|
381
|
+
assert result == ""
|
|
382
|
+
|
|
383
|
+
def test_load_soul_uses_soul_switch_system_prompt(
|
|
384
|
+
self, tmp_path: Path
|
|
385
|
+
) -> None:
|
|
386
|
+
"""When soul_switch returns a blueprint with system_prompt, it is used directly."""
|
|
387
|
+
from skcapstone.consciousness_loop import SystemPromptBuilder
|
|
388
|
+
from skcapstone.soul_switch import SoulSwitchBlueprint
|
|
389
|
+
|
|
390
|
+
home = tmp_path
|
|
391
|
+
expected_prompt = "You are Casey -- a sharp legal mind."
|
|
392
|
+
|
|
393
|
+
mock_bp = SoulSwitchBlueprint(
|
|
394
|
+
name="casey",
|
|
395
|
+
system_prompt=expected_prompt,
|
|
396
|
+
core_traits=["analytical"],
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
with patch(
|
|
400
|
+
"skcapstone.soul_switch.get_active_switch_blueprint",
|
|
401
|
+
return_value=mock_bp,
|
|
402
|
+
):
|
|
403
|
+
builder = SystemPromptBuilder(home=home)
|
|
404
|
+
result = builder._load_soul()
|
|
405
|
+
|
|
406
|
+
assert result == expected_prompt
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Tests for the sub-agent spawner module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from skcapstone.blueprints.schema import AgentRole, ModelTier, ProviderType
|
|
10
|
+
from skcapstone.spawner import (
|
|
11
|
+
NodeInfo,
|
|
12
|
+
SpawnResult,
|
|
13
|
+
SubAgentSpawner,
|
|
14
|
+
classify_task,
|
|
15
|
+
select_node,
|
|
16
|
+
)
|
|
17
|
+
from skcapstone.team_engine import AgentStatus
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# classify_task
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestClassifyTask:
|
|
26
|
+
"""Tests for automatic task classification."""
|
|
27
|
+
|
|
28
|
+
def test_coding_task(self):
|
|
29
|
+
role, model = classify_task("Write unit tests for capauth login flow")
|
|
30
|
+
assert role == AgentRole.CODER
|
|
31
|
+
assert model == ModelTier.CODE
|
|
32
|
+
|
|
33
|
+
def test_review_task(self):
|
|
34
|
+
role, model = classify_task("Code review the skchat architecture")
|
|
35
|
+
assert role == AgentRole.REVIEWER
|
|
36
|
+
assert model == ModelTier.REASON
|
|
37
|
+
|
|
38
|
+
def test_research_task(self):
|
|
39
|
+
role, model = classify_task("Research FUSE mounting options for Linux")
|
|
40
|
+
assert role == AgentRole.RESEARCHER
|
|
41
|
+
assert model == ModelTier.REASON
|
|
42
|
+
|
|
43
|
+
def test_docs_task(self):
|
|
44
|
+
role, model = classify_task("Write docs for the spawner module")
|
|
45
|
+
assert role == AgentRole.DOCUMENTARIAN
|
|
46
|
+
assert model == ModelTier.FAST
|
|
47
|
+
|
|
48
|
+
def test_security_task(self):
|
|
49
|
+
role, model = classify_task("Run a security audit on the capauth service")
|
|
50
|
+
assert role == AgentRole.SECURITY
|
|
51
|
+
assert model == ModelTier.REASON
|
|
52
|
+
|
|
53
|
+
def test_ops_task(self):
|
|
54
|
+
role, model = classify_task("Deploy the monitoring stack to production")
|
|
55
|
+
assert role == AgentRole.OPS
|
|
56
|
+
assert model == ModelTier.FAST
|
|
57
|
+
|
|
58
|
+
def test_unknown_falls_back_to_worker(self):
|
|
59
|
+
role, model = classify_task("Do something completely unrecognizable")
|
|
60
|
+
assert role == AgentRole.WORKER
|
|
61
|
+
assert model == ModelTier.FAST
|
|
62
|
+
|
|
63
|
+
def test_case_insensitive(self):
|
|
64
|
+
role, model = classify_task("IMPLEMENT the new feature")
|
|
65
|
+
assert role == AgentRole.CODER
|
|
66
|
+
|
|
67
|
+
def test_multi_word_pattern_priority(self):
|
|
68
|
+
"""Multi-word patterns should match before single-word ones."""
|
|
69
|
+
role, model = classify_task("Conduct a security audit of the system")
|
|
70
|
+
assert role == AgentRole.SECURITY
|
|
71
|
+
assert model == ModelTier.REASON
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# select_node
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestSelectNode:
|
|
80
|
+
"""Tests for node selection logic."""
|
|
81
|
+
|
|
82
|
+
def test_empty_nodes_returns_local(self):
|
|
83
|
+
node = select_node([], AgentRole.CODER, ModelTier.CODE)
|
|
84
|
+
assert node.provider == ProviderType.LOCAL
|
|
85
|
+
assert node.name == "local"
|
|
86
|
+
|
|
87
|
+
def test_prefers_provider_match(self):
|
|
88
|
+
nodes = [
|
|
89
|
+
NodeInfo(name="docker1", provider=ProviderType.DOCKER, capacity=0.5),
|
|
90
|
+
NodeInfo(name="local1", provider=ProviderType.LOCAL, capacity=0.9),
|
|
91
|
+
]
|
|
92
|
+
node = select_node(
|
|
93
|
+
nodes, AgentRole.CODER, ModelTier.CODE,
|
|
94
|
+
preferred_provider=ProviderType.DOCKER,
|
|
95
|
+
)
|
|
96
|
+
assert node.name == "docker1"
|
|
97
|
+
|
|
98
|
+
def test_prefers_high_capacity(self):
|
|
99
|
+
nodes = [
|
|
100
|
+
NodeInfo(name="low", provider=ProviderType.LOCAL, capacity=0.2),
|
|
101
|
+
NodeInfo(name="high", provider=ProviderType.LOCAL, capacity=0.9),
|
|
102
|
+
]
|
|
103
|
+
node = select_node(nodes, AgentRole.WORKER, ModelTier.FAST)
|
|
104
|
+
assert node.name == "high"
|
|
105
|
+
|
|
106
|
+
def test_gpu_affinity_for_reason_models(self):
|
|
107
|
+
nodes = [
|
|
108
|
+
NodeInfo(name="cpu", provider=ProviderType.LOCAL, capacity=0.8),
|
|
109
|
+
NodeInfo(name="gpu", provider=ProviderType.LOCAL, capacity=0.5, tags=["gpu"]),
|
|
110
|
+
]
|
|
111
|
+
node = select_node(nodes, AgentRole.RESEARCHER, ModelTier.REASON)
|
|
112
|
+
assert node.name == "gpu"
|
|
113
|
+
|
|
114
|
+
def test_local_affinity_for_local_models(self):
|
|
115
|
+
nodes = [
|
|
116
|
+
NodeInfo(name="docker1", provider=ProviderType.DOCKER, capacity=0.9),
|
|
117
|
+
NodeInfo(name="local1", provider=ProviderType.LOCAL, capacity=0.5),
|
|
118
|
+
]
|
|
119
|
+
node = select_node(nodes, AgentRole.WORKER, ModelTier.LOCAL)
|
|
120
|
+
assert node.name == "local1"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# SubAgentSpawner
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestSubAgentSpawner:
|
|
129
|
+
"""Tests for the spawner's spawn and management methods."""
|
|
130
|
+
|
|
131
|
+
def test_spawn_creates_deployment(self, tmp_agent_home: Path):
|
|
132
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
133
|
+
result = spawner.spawn(task="Write tests for capauth")
|
|
134
|
+
|
|
135
|
+
assert isinstance(result, SpawnResult)
|
|
136
|
+
assert result.deployment_id != ""
|
|
137
|
+
assert result.role == AgentRole.CODER
|
|
138
|
+
assert result.model == ModelTier.CODE
|
|
139
|
+
|
|
140
|
+
def test_spawn_with_explicit_role(self, tmp_agent_home: Path):
|
|
141
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
142
|
+
result = spawner.spawn(
|
|
143
|
+
task="Something generic",
|
|
144
|
+
role=AgentRole.SECURITY,
|
|
145
|
+
model=ModelTier.REASON,
|
|
146
|
+
)
|
|
147
|
+
assert result.role == AgentRole.SECURITY
|
|
148
|
+
assert result.model == ModelTier.REASON
|
|
149
|
+
|
|
150
|
+
def test_spawn_creates_deployments_dir(self, tmp_agent_home: Path):
|
|
151
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
152
|
+
spawner.spawn(task="Test deployment creation")
|
|
153
|
+
|
|
154
|
+
deployments_dir = tmp_agent_home / "deployments"
|
|
155
|
+
assert deployments_dir.exists()
|
|
156
|
+
assert len(list(deployments_dir.glob("*.json"))) == 1
|
|
157
|
+
|
|
158
|
+
def test_list_spawned_empty(self, tmp_agent_home: Path):
|
|
159
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
160
|
+
results = spawner.list_spawned()
|
|
161
|
+
assert results == []
|
|
162
|
+
|
|
163
|
+
def test_list_spawned_after_spawn(self, tmp_agent_home: Path):
|
|
164
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
165
|
+
spawner.spawn(task="Test listing")
|
|
166
|
+
results = spawner.list_spawned()
|
|
167
|
+
assert len(results) == 1
|
|
168
|
+
|
|
169
|
+
def test_kill_destroys_deployment(self, tmp_agent_home: Path):
|
|
170
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
171
|
+
result = spawner.spawn(task="Test killing")
|
|
172
|
+
assert spawner.kill(result.deployment_id)
|
|
173
|
+
|
|
174
|
+
# Should be gone now
|
|
175
|
+
results = spawner.list_spawned()
|
|
176
|
+
assert len(results) == 0
|
|
177
|
+
|
|
178
|
+
def test_kill_nonexistent_returns_false(self, tmp_agent_home: Path):
|
|
179
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
180
|
+
assert not spawner.kill("nonexistent-deployment-id")
|
|
181
|
+
|
|
182
|
+
def test_spawn_batch(self, tmp_agent_home: Path):
|
|
183
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
184
|
+
tasks = [
|
|
185
|
+
{"task": "Write unit tests"},
|
|
186
|
+
{"task": "Review architecture"},
|
|
187
|
+
{"task": "Write documentation"},
|
|
188
|
+
]
|
|
189
|
+
results = spawner.spawn_batch(tasks)
|
|
190
|
+
assert len(results) == 3
|
|
191
|
+
assert results[0].role == AgentRole.CODER
|
|
192
|
+
assert results[1].role == AgentRole.REVIEWER
|
|
193
|
+
assert results[2].role == AgentRole.DOCUMENTARIAN
|
|
194
|
+
|
|
195
|
+
def test_spawn_with_custom_name(self, tmp_agent_home: Path):
|
|
196
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
197
|
+
result = spawner.spawn(
|
|
198
|
+
task="Custom named agent",
|
|
199
|
+
agent_name="my-custom-agent",
|
|
200
|
+
)
|
|
201
|
+
assert result.deployment_id != ""
|
|
202
|
+
|
|
203
|
+
def test_spawn_writes_audit(self, tmp_agent_home: Path):
|
|
204
|
+
(tmp_agent_home / "coordination").mkdir(parents=True, exist_ok=True)
|
|
205
|
+
spawner = SubAgentSpawner(home=tmp_agent_home)
|
|
206
|
+
spawner.spawn(task="Audit test task")
|
|
207
|
+
|
|
208
|
+
audit_path = tmp_agent_home / "coordination" / "audit.log"
|
|
209
|
+
assert audit_path.exists()
|
|
210
|
+
content = audit_path.read_text()
|
|
211
|
+
assert "spawn_agent" in content
|