@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,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Self-Healing Doctor — auto-diagnosing, auto-remediating agent health.
|
|
3
|
+
|
|
4
|
+
Extends the existing doctor.py diagnostics with auto-fix capabilities.
|
|
5
|
+
Follows the TrusteeMonitor escalation pattern:
|
|
6
|
+
diagnose → auto-fix → re-check → escalate if still broken.
|
|
7
|
+
|
|
8
|
+
Registered auto-fixes:
|
|
9
|
+
- Missing home dirs → mkdir -p
|
|
10
|
+
- Missing memory index → rebuild from memory/**/*.json
|
|
11
|
+
- Missing sync backends → write default sync-manifest.json
|
|
12
|
+
- LLM unreachable → re-probe backends, switch fallback
|
|
13
|
+
- Config corrupt → reset to defaults
|
|
14
|
+
- Dead worker thread → restart
|
|
15
|
+
- Dead inotify thread → restart observer
|
|
16
|
+
- Stale model profiles → flag for human review
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("skcapstone.self_healing")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SelfHealingDoctor:
|
|
31
|
+
"""Auto-diagnosing, auto-remediating health monitor.
|
|
32
|
+
|
|
33
|
+
Runs diagnostics from doctor.py plus consciousness-specific checks.
|
|
34
|
+
Auto-fixes what it can, escalates what it can't.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
home: Agent home directory.
|
|
38
|
+
consciousness_loop: Optional reference to the running loop.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
home: Path,
|
|
44
|
+
consciousness_loop: Any = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
self._home = home
|
|
47
|
+
self._consciousness = consciousness_loop
|
|
48
|
+
self._last_report: dict[str, Any] = {}
|
|
49
|
+
|
|
50
|
+
def diagnose_and_heal(self) -> dict[str, Any]:
|
|
51
|
+
"""Run diagnostics, auto-fix what we can, escalate the rest.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict with checks_run, checks_passed, auto_fixed,
|
|
55
|
+
still_broken, escalated counts and details.
|
|
56
|
+
"""
|
|
57
|
+
checks_run = 0
|
|
58
|
+
checks_passed = 0
|
|
59
|
+
auto_fixed = 0
|
|
60
|
+
still_broken = 0
|
|
61
|
+
escalated_items: list[str] = []
|
|
62
|
+
details: list[dict[str, Any]] = []
|
|
63
|
+
|
|
64
|
+
# Run all check groups
|
|
65
|
+
check_methods = [
|
|
66
|
+
self._check_home_dirs,
|
|
67
|
+
self._check_memory_index,
|
|
68
|
+
self._check_sync_manifest,
|
|
69
|
+
self._check_consciousness_health,
|
|
70
|
+
self._check_profile_freshness,
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for check_fn in check_methods:
|
|
74
|
+
try:
|
|
75
|
+
result = check_fn()
|
|
76
|
+
checks_run += 1
|
|
77
|
+
if result["status"] == "ok":
|
|
78
|
+
checks_passed += 1
|
|
79
|
+
elif result["status"] == "fixed":
|
|
80
|
+
checks_passed += 1
|
|
81
|
+
auto_fixed += 1
|
|
82
|
+
elif result["status"] == "broken":
|
|
83
|
+
still_broken += 1
|
|
84
|
+
escalated_items.append(result.get("name", "unknown"))
|
|
85
|
+
details.append(result)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
checks_run += 1
|
|
88
|
+
still_broken += 1
|
|
89
|
+
details.append({
|
|
90
|
+
"name": check_fn.__name__,
|
|
91
|
+
"status": "error",
|
|
92
|
+
"error": str(exc),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
report = {
|
|
96
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
97
|
+
"checks_run": checks_run,
|
|
98
|
+
"checks_passed": checks_passed,
|
|
99
|
+
"auto_fixed": auto_fixed,
|
|
100
|
+
"still_broken": still_broken,
|
|
101
|
+
"escalated": escalated_items,
|
|
102
|
+
"details": details,
|
|
103
|
+
}
|
|
104
|
+
self._last_report = report
|
|
105
|
+
|
|
106
|
+
# Escalate if needed
|
|
107
|
+
if escalated_items:
|
|
108
|
+
self._escalate(escalated_items)
|
|
109
|
+
|
|
110
|
+
return report
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def last_report(self) -> dict[str, Any]:
|
|
114
|
+
"""Most recent diagnostic report."""
|
|
115
|
+
return self._last_report
|
|
116
|
+
|
|
117
|
+
# -------------------------------------------------------------------
|
|
118
|
+
# Check methods — each returns {"name", "status", "message"}
|
|
119
|
+
# -------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
def _check_home_dirs(self) -> dict[str, Any]:
|
|
122
|
+
"""Ensure all required home subdirectories exist."""
|
|
123
|
+
required = [
|
|
124
|
+
"identity", "memory", "trust", "security", "sync", "config",
|
|
125
|
+
"soul", "logs",
|
|
126
|
+
]
|
|
127
|
+
missing = []
|
|
128
|
+
for subdir in required:
|
|
129
|
+
path = self._home / subdir
|
|
130
|
+
if not path.exists():
|
|
131
|
+
missing.append(subdir)
|
|
132
|
+
|
|
133
|
+
if not missing:
|
|
134
|
+
return {"name": "home_dirs", "status": "ok", "message": "All dirs present"}
|
|
135
|
+
|
|
136
|
+
# Auto-fix: create missing dirs
|
|
137
|
+
for subdir in missing:
|
|
138
|
+
(self._home / subdir).mkdir(parents=True, exist_ok=True)
|
|
139
|
+
logger.info("Auto-created missing dir: %s", subdir)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"name": "home_dirs",
|
|
143
|
+
"status": "fixed",
|
|
144
|
+
"message": f"Created {len(missing)} missing dirs: {missing}",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
def _check_memory_index(self) -> dict[str, Any]:
|
|
148
|
+
"""Check if memory index exists and is valid."""
|
|
149
|
+
memory_dir = self._home / "memory"
|
|
150
|
+
index_path = memory_dir / "index.json"
|
|
151
|
+
|
|
152
|
+
if not memory_dir.exists():
|
|
153
|
+
return {"name": "memory_index", "status": "ok", "message": "No memory dir"}
|
|
154
|
+
|
|
155
|
+
if index_path.exists():
|
|
156
|
+
try:
|
|
157
|
+
data = json.loads(index_path.read_text(encoding="utf-8"))
|
|
158
|
+
if isinstance(data, (list, dict)):
|
|
159
|
+
return {"name": "memory_index", "status": "ok", "message": "Index valid"}
|
|
160
|
+
except (json.JSONDecodeError, OSError):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# Auto-fix: rebuild index from memory files
|
|
164
|
+
entries = []
|
|
165
|
+
for layer_dir in ("short-term", "mid-term", "long-term"):
|
|
166
|
+
layer_path = memory_dir / layer_dir
|
|
167
|
+
if layer_path.exists():
|
|
168
|
+
for f in layer_path.glob("*.json"):
|
|
169
|
+
try:
|
|
170
|
+
entry = json.loads(f.read_text(encoding="utf-8"))
|
|
171
|
+
entries.append({
|
|
172
|
+
"memory_id": entry.get("memory_id", f.stem),
|
|
173
|
+
"layer": layer_dir,
|
|
174
|
+
"tags": entry.get("tags", []),
|
|
175
|
+
})
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
logger.debug("Skipping malformed memory file %s: %s", f, exc)
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
index_path.write_text(
|
|
181
|
+
json.dumps(entries, indent=2), encoding="utf-8"
|
|
182
|
+
)
|
|
183
|
+
logger.info("Rebuilt memory index with %d entries", len(entries))
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
"name": "memory_index",
|
|
187
|
+
"status": "fixed",
|
|
188
|
+
"message": f"Rebuilt index with {len(entries)} entries",
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def _check_sync_manifest(self) -> dict[str, Any]:
|
|
192
|
+
"""Check sync-manifest.json exists."""
|
|
193
|
+
sync_dir = self._home / "sync"
|
|
194
|
+
manifest_path = sync_dir / "sync-manifest.json"
|
|
195
|
+
|
|
196
|
+
if manifest_path.exists():
|
|
197
|
+
return {"name": "sync_manifest", "status": "ok", "message": "Manifest present"}
|
|
198
|
+
|
|
199
|
+
if not sync_dir.exists():
|
|
200
|
+
return {"name": "sync_manifest", "status": "ok", "message": "No sync dir"}
|
|
201
|
+
|
|
202
|
+
# Auto-fix: write default manifest
|
|
203
|
+
default_manifest = {
|
|
204
|
+
"version": 1,
|
|
205
|
+
"backends": ["syncthing"],
|
|
206
|
+
"auto_push": True,
|
|
207
|
+
"auto_pull": True,
|
|
208
|
+
}
|
|
209
|
+
sync_dir.mkdir(parents=True, exist_ok=True)
|
|
210
|
+
manifest_path.write_text(
|
|
211
|
+
json.dumps(default_manifest, indent=2), encoding="utf-8"
|
|
212
|
+
)
|
|
213
|
+
logger.info("Wrote default sync-manifest.json")
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"name": "sync_manifest",
|
|
217
|
+
"status": "fixed",
|
|
218
|
+
"message": "Created default sync manifest",
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
def _check_consciousness_health(self) -> dict[str, Any]:
|
|
222
|
+
"""Check consciousness loop health."""
|
|
223
|
+
if not self._consciousness:
|
|
224
|
+
return {
|
|
225
|
+
"name": "consciousness",
|
|
226
|
+
"status": "ok",
|
|
227
|
+
"message": "Consciousness loop not loaded (disabled)",
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
issues: list[str] = []
|
|
231
|
+
|
|
232
|
+
# Check at least one backend is available
|
|
233
|
+
backends = self._consciousness._bridge.available_backends
|
|
234
|
+
if not any(backends.values()):
|
|
235
|
+
# Auto-fix: re-probe
|
|
236
|
+
self._consciousness._bridge._probe_available_backends()
|
|
237
|
+
backends = self._consciousness._bridge.available_backends
|
|
238
|
+
if any(backends.values()):
|
|
239
|
+
logger.info("Re-probed backends — found available: %s",
|
|
240
|
+
[k for k, v in backends.items() if v])
|
|
241
|
+
else:
|
|
242
|
+
issues.append("No LLM backends reachable")
|
|
243
|
+
|
|
244
|
+
# Check inotify thread
|
|
245
|
+
observer = self._consciousness._observer
|
|
246
|
+
if observer and hasattr(observer, "is_alive") and not observer.is_alive():
|
|
247
|
+
# Auto-fix: restart observer
|
|
248
|
+
try:
|
|
249
|
+
self._consciousness._run_inotify_restart()
|
|
250
|
+
logger.info("Restarted inotify observer")
|
|
251
|
+
except Exception as exc:
|
|
252
|
+
logger.debug("Inotify restart failed: %s", exc)
|
|
253
|
+
issues.append("Inotify thread dead — restart failed")
|
|
254
|
+
|
|
255
|
+
if issues:
|
|
256
|
+
return {
|
|
257
|
+
"name": "consciousness",
|
|
258
|
+
"status": "broken",
|
|
259
|
+
"message": "; ".join(issues),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
"name": "consciousness",
|
|
264
|
+
"status": "ok",
|
|
265
|
+
"message": "Consciousness loop healthy",
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
def _check_profile_freshness(self) -> dict[str, Any]:
|
|
269
|
+
"""Flag model profiles older than 90 days as needing review."""
|
|
270
|
+
try:
|
|
271
|
+
from skcapstone.prompt_adapter import PromptAdapter
|
|
272
|
+
adapter = PromptAdapter()
|
|
273
|
+
stale: list[str] = []
|
|
274
|
+
now = datetime.now(timezone.utc)
|
|
275
|
+
|
|
276
|
+
for profile in adapter.profiles:
|
|
277
|
+
if not profile.last_updated:
|
|
278
|
+
stale.append(profile.family)
|
|
279
|
+
continue
|
|
280
|
+
try:
|
|
281
|
+
updated = datetime.fromisoformat(profile.last_updated)
|
|
282
|
+
if hasattr(updated, "tzinfo") and updated.tzinfo is None:
|
|
283
|
+
updated = updated.replace(tzinfo=timezone.utc)
|
|
284
|
+
age_days = (now - updated).days
|
|
285
|
+
if age_days > 90:
|
|
286
|
+
stale.append(f"{profile.family} ({age_days}d)")
|
|
287
|
+
except (ValueError, TypeError):
|
|
288
|
+
stale.append(profile.family)
|
|
289
|
+
|
|
290
|
+
if stale:
|
|
291
|
+
logger.warning(
|
|
292
|
+
"Model profiles older than 90 days (manual update recommended): %s",
|
|
293
|
+
", ".join(stale),
|
|
294
|
+
)
|
|
295
|
+
return {
|
|
296
|
+
"name": "profile_freshness",
|
|
297
|
+
"status": "ok", # Informational, not broken
|
|
298
|
+
"message": f"Stale profiles (>90d): {', '.join(stale)}",
|
|
299
|
+
"stale_profiles": stale,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
"name": "profile_freshness",
|
|
304
|
+
"status": "ok",
|
|
305
|
+
"message": "All profiles fresh",
|
|
306
|
+
}
|
|
307
|
+
except Exception as exc:
|
|
308
|
+
return {
|
|
309
|
+
"name": "profile_freshness",
|
|
310
|
+
"status": "ok",
|
|
311
|
+
"message": f"Could not check profiles: {exc}",
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# -------------------------------------------------------------------
|
|
315
|
+
# Escalation
|
|
316
|
+
# -------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
def _escalate(self, items: list[str]) -> None:
|
|
319
|
+
"""Escalate unresolved issues via SKChat.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
items: List of check names that are still broken.
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
from skcapstone.mcp_tools._helpers import _get_agent_name
|
|
326
|
+
agent_name = _get_agent_name(self._home)
|
|
327
|
+
message = (
|
|
328
|
+
f"Self-healing alert from {agent_name}: "
|
|
329
|
+
f"{len(items)} issue(s) could not be auto-fixed: {', '.join(items)}"
|
|
330
|
+
)
|
|
331
|
+
logger.warning("Escalating: %s", message)
|
|
332
|
+
|
|
333
|
+
# Try to send via SKChat if available
|
|
334
|
+
try:
|
|
335
|
+
from skchat.messenger import AgentMessenger
|
|
336
|
+
messenger = AgentMessenger.from_config()
|
|
337
|
+
messenger.send("chef", message)
|
|
338
|
+
except Exception as exc:
|
|
339
|
+
logger.debug("SKChat escalation failed (best-effort): %s", exc)
|
|
340
|
+
except Exception as exc:
|
|
341
|
+
logger.debug("Escalation failed: %s", exc)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Service URL health check mechanism.
|
|
2
|
+
|
|
3
|
+
Pings all known services in the sovereign stack and returns structured
|
|
4
|
+
status reports. Each check uses urllib.request with a 3-second timeout
|
|
5
|
+
so the full sweep completes in bounded time even when services are down.
|
|
6
|
+
|
|
7
|
+
Usage (library):
|
|
8
|
+
from skcapstone.service_health import check_all_services
|
|
9
|
+
results = check_all_services()
|
|
10
|
+
for svc in results:
|
|
11
|
+
print(f"{svc['name']}: {svc['status']} ({svc['latency_ms']}ms)")
|
|
12
|
+
|
|
13
|
+
Usage (scheduled task):
|
|
14
|
+
from skcapstone.service_health import make_service_health_task
|
|
15
|
+
callback = make_service_health_task()
|
|
16
|
+
scheduler.register("service_health_check", 300, callback)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import socket
|
|
25
|
+
import time
|
|
26
|
+
import urllib.error
|
|
27
|
+
import urllib.request
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("skcapstone.service_health")
|
|
31
|
+
|
|
32
|
+
# Default timeout per service check (seconds).
|
|
33
|
+
CHECK_TIMEOUT = 3
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Individual service checks
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _http_check(
|
|
42
|
+
name: str,
|
|
43
|
+
url: str,
|
|
44
|
+
*,
|
|
45
|
+
headers: dict[str, str] | None = None,
|
|
46
|
+
version_key: str | None = None,
|
|
47
|
+
) -> dict[str, Any]:
|
|
48
|
+
"""Perform an HTTP GET health check against *url*.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
name: Human-readable service name.
|
|
52
|
+
url: Full URL to probe (e.g. ``http://localhost:6333/healthz``).
|
|
53
|
+
headers: Optional extra HTTP headers (e.g. API keys).
|
|
54
|
+
version_key: If set, extract this key from the JSON response as version.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Status dict with name, url, status, latency_ms, version, error.
|
|
58
|
+
"""
|
|
59
|
+
result: dict[str, Any] = {
|
|
60
|
+
"name": name,
|
|
61
|
+
"url": url,
|
|
62
|
+
"status": "unknown",
|
|
63
|
+
"latency_ms": 0,
|
|
64
|
+
"version": None,
|
|
65
|
+
"error": None,
|
|
66
|
+
}
|
|
67
|
+
req = urllib.request.Request(url, headers=headers or {})
|
|
68
|
+
t0 = time.monotonic()
|
|
69
|
+
try:
|
|
70
|
+
with urllib.request.urlopen(req, timeout=CHECK_TIMEOUT) as resp:
|
|
71
|
+
latency = (time.monotonic() - t0) * 1000
|
|
72
|
+
result["latency_ms"] = round(latency, 1)
|
|
73
|
+
result["status"] = "up"
|
|
74
|
+
|
|
75
|
+
if version_key:
|
|
76
|
+
try:
|
|
77
|
+
body = json.loads(resp.read().decode("utf-8"))
|
|
78
|
+
result["version"] = body.get(version_key)
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
except urllib.error.HTTPError as exc:
|
|
82
|
+
latency = (time.monotonic() - t0) * 1000
|
|
83
|
+
result["latency_ms"] = round(latency, 1)
|
|
84
|
+
# A non-2xx response still means the service is reachable.
|
|
85
|
+
if exc.code < 500:
|
|
86
|
+
result["status"] = "up"
|
|
87
|
+
else:
|
|
88
|
+
result["status"] = "down"
|
|
89
|
+
result["error"] = f"HTTP {exc.code}"
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
latency = (time.monotonic() - t0) * 1000
|
|
92
|
+
result["latency_ms"] = round(latency, 1)
|
|
93
|
+
result["status"] = "down"
|
|
94
|
+
result["error"] = str(exc)[:200]
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _tcp_check(name: str, host: str, port: int) -> dict[str, Any]:
|
|
99
|
+
"""Perform a raw TCP connect check.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
name: Human-readable service name.
|
|
103
|
+
host: Hostname or IP to connect to.
|
|
104
|
+
port: TCP port number.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Status dict with name, url, status, latency_ms, version, error.
|
|
108
|
+
"""
|
|
109
|
+
url = f"tcp://{host}:{port}"
|
|
110
|
+
result: dict[str, Any] = {
|
|
111
|
+
"name": name,
|
|
112
|
+
"url": url,
|
|
113
|
+
"status": "unknown",
|
|
114
|
+
"latency_ms": 0,
|
|
115
|
+
"version": None,
|
|
116
|
+
"error": None,
|
|
117
|
+
}
|
|
118
|
+
t0 = time.monotonic()
|
|
119
|
+
try:
|
|
120
|
+
sock = socket.create_connection((host, port), timeout=CHECK_TIMEOUT)
|
|
121
|
+
latency = (time.monotonic() - t0) * 1000
|
|
122
|
+
sock.close()
|
|
123
|
+
result["latency_ms"] = round(latency, 1)
|
|
124
|
+
result["status"] = "up"
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
latency = (time.monotonic() - t0) * 1000
|
|
127
|
+
result["latency_ms"] = round(latency, 1)
|
|
128
|
+
result["status"] = "down"
|
|
129
|
+
result["error"] = str(exc)[:200]
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# Aggregate check
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def check_all_services() -> list[dict[str, Any]]:
|
|
139
|
+
"""Ping every known service and return a list of status dicts.
|
|
140
|
+
|
|
141
|
+
Environment variables override default URLs:
|
|
142
|
+
SKMEMORY_SKVECTOR_URL — Qdrant REST base (default http://localhost:6333)
|
|
143
|
+
SKMEMORY_SKGRAPH_HOST — FalkorDB host (default localhost)
|
|
144
|
+
SKMEMORY_SKGRAPH_PORT — FalkorDB port (default 6379)
|
|
145
|
+
SYNCTHING_API_URL — Syncthing REST (default http://localhost:8384)
|
|
146
|
+
SYNCTHING_API_KEY — Syncthing API key (optional)
|
|
147
|
+
SKCAPSTONE_DAEMON_URL — Daemon HTTP base (default http://localhost:9383)
|
|
148
|
+
SKCHAT_DAEMON_URL — SKChat daemon (default http://localhost:9385)
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of dicts, each containing: name, url, status ("up"|"down"|"unknown"),
|
|
152
|
+
latency_ms, version, error.
|
|
153
|
+
"""
|
|
154
|
+
results: list[dict[str, Any]] = []
|
|
155
|
+
|
|
156
|
+
# -- SKVector (Qdrant) --------------------------------------------------
|
|
157
|
+
qdrant_base = os.environ.get("SKMEMORY_SKVECTOR_URL", "http://localhost:6333")
|
|
158
|
+
qdrant_url = qdrant_base.rstrip("/") + "/healthz"
|
|
159
|
+
results.append(_http_check("skvector (Qdrant)", qdrant_url))
|
|
160
|
+
|
|
161
|
+
# -- SKGraph (FalkorDB) — TCP check on Redis protocol port ---------------
|
|
162
|
+
graph_host = os.environ.get("SKMEMORY_SKGRAPH_HOST", "localhost")
|
|
163
|
+
graph_port = int(os.environ.get("SKMEMORY_SKGRAPH_PORT", "6379"))
|
|
164
|
+
results.append(_tcp_check("skgraph (FalkorDB)", graph_host, graph_port))
|
|
165
|
+
|
|
166
|
+
# -- Syncthing -----------------------------------------------------------
|
|
167
|
+
syncthing_base = os.environ.get("SYNCTHING_API_URL", "http://localhost:8384")
|
|
168
|
+
syncthing_url = syncthing_base.rstrip("/") + "/rest/system/status"
|
|
169
|
+
syncthing_headers: dict[str, str] = {}
|
|
170
|
+
api_key = os.environ.get("SYNCTHING_API_KEY", "")
|
|
171
|
+
if api_key:
|
|
172
|
+
syncthing_headers["X-API-Key"] = api_key
|
|
173
|
+
results.append(
|
|
174
|
+
_http_check(
|
|
175
|
+
"syncthing",
|
|
176
|
+
syncthing_url,
|
|
177
|
+
headers=syncthing_headers,
|
|
178
|
+
version_key="version",
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# -- skcapstone daemon ---------------------------------------------------
|
|
183
|
+
daemon_base = os.environ.get("SKCAPSTONE_DAEMON_URL", "http://localhost:9383")
|
|
184
|
+
daemon_url = daemon_base.rstrip("/") + "/health"
|
|
185
|
+
results.append(_http_check("skcapstone daemon", daemon_url))
|
|
186
|
+
|
|
187
|
+
# -- skchat daemon -------------------------------------------------------
|
|
188
|
+
chat_base = os.environ.get("SKCHAT_DAEMON_URL", "http://localhost:9385")
|
|
189
|
+
chat_url = chat_base.rstrip("/") + "/health"
|
|
190
|
+
results.append(_http_check("skchat daemon", chat_url))
|
|
191
|
+
|
|
192
|
+
return results
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Scheduled-task factory
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def make_service_health_task() -> callable:
|
|
201
|
+
"""Return a zero-arg callback suitable for TaskScheduler.register().
|
|
202
|
+
|
|
203
|
+
Runs check_all_services() and logs results. Down services are logged
|
|
204
|
+
at WARNING level; all-up is logged at DEBUG level.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def _run() -> None:
|
|
208
|
+
results = check_all_services()
|
|
209
|
+
down = [r for r in results if r["status"] == "down"]
|
|
210
|
+
if down:
|
|
211
|
+
names = ", ".join(r["name"] for r in down)
|
|
212
|
+
logger.warning(
|
|
213
|
+
"Service health: %d/%d down — %s", len(down), len(results), names
|
|
214
|
+
)
|
|
215
|
+
for r in down:
|
|
216
|
+
logger.warning(
|
|
217
|
+
" %s (%s): %s", r["name"], r["url"], r["error"] or "unreachable"
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
up_count = sum(1 for r in results if r["status"] == "up")
|
|
221
|
+
logger.debug(
|
|
222
|
+
"Service health: %d/%d up, %d unknown",
|
|
223
|
+
up_count,
|
|
224
|
+
len(results),
|
|
225
|
+
len(results) - up_count,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return _run
|