@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.
Files changed (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. 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