@smilintux/skcapstone 0.10.0 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
package/src/skcapstone/daemon.py
CHANGED
|
@@ -54,6 +54,36 @@ DEFAULT_PORT = 7777
|
|
|
54
54
|
PID_FILE = "daemon.pid"
|
|
55
55
|
LOG_DIR = "logs"
|
|
56
56
|
|
|
57
|
+
|
|
58
|
+
def _sd_notify(state: str) -> bool:
|
|
59
|
+
"""Send a notification to systemd via the NOTIFY_SOCKET.
|
|
60
|
+
|
|
61
|
+
Implements the sd_notify(3) protocol using a raw AF_UNIX datagram socket
|
|
62
|
+
so we don't need an external dependency. Returns True if the notification
|
|
63
|
+
was sent, False if NOTIFY_SOCKET is not set (i.e. not running under systemd).
|
|
64
|
+
|
|
65
|
+
Common states:
|
|
66
|
+
"READY=1" — service startup complete
|
|
67
|
+
"WATCHDOG=1" — watchdog keep-alive ping
|
|
68
|
+
"STOPPING=1" — graceful shutdown in progress
|
|
69
|
+
"""
|
|
70
|
+
addr = os.environ.get("NOTIFY_SOCKET")
|
|
71
|
+
if not addr:
|
|
72
|
+
return False
|
|
73
|
+
import socket as _socket
|
|
74
|
+
try:
|
|
75
|
+
sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM)
|
|
76
|
+
try:
|
|
77
|
+
if addr[0] == "@":
|
|
78
|
+
addr = "\0" + addr[1:]
|
|
79
|
+
sock.sendto(state.encode("utf-8"), addr)
|
|
80
|
+
finally:
|
|
81
|
+
sock.close()
|
|
82
|
+
return True
|
|
83
|
+
except OSError as exc:
|
|
84
|
+
logger.debug("sd_notify(%r) failed: %s", state, exc)
|
|
85
|
+
return False
|
|
86
|
+
|
|
57
87
|
# ── WebSocket helpers (RFC 6455, stdlib-only) ─────────────────────────────────
|
|
58
88
|
|
|
59
89
|
_WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
@@ -685,7 +715,7 @@ class DaemonService:
|
|
|
685
715
|
self._stop_event = threading.Event()
|
|
686
716
|
self._threads: list[threading.Thread] = []
|
|
687
717
|
self._server: Optional[HTTPServer] = None
|
|
688
|
-
self.
|
|
718
|
+
self._skcomms = None
|
|
689
719
|
self._runtime = None
|
|
690
720
|
self._consciousness = None
|
|
691
721
|
self._healer = None
|
|
@@ -770,10 +800,12 @@ class DaemonService:
|
|
|
770
800
|
|
|
771
801
|
self._start_api_server()
|
|
772
802
|
|
|
803
|
+
_sd_notify("READY=1")
|
|
773
804
|
logger.info("Daemon started — PID %d", os.getpid())
|
|
774
805
|
|
|
775
806
|
def stop(self) -> None:
|
|
776
807
|
"""Gracefully stop the daemon and all workers."""
|
|
808
|
+
_sd_notify("STOPPING=1")
|
|
777
809
|
logger.info("Daemon stopping...")
|
|
778
810
|
self._stop_event.set()
|
|
779
811
|
self.state.running = False
|
|
@@ -852,16 +884,22 @@ class DaemonService:
|
|
|
852
884
|
logger.info("Preflight complete — all checks passed")
|
|
853
885
|
|
|
854
886
|
def _load_components(self) -> None:
|
|
855
|
-
"""Attempt to load
|
|
887
|
+
"""Attempt to load SKComms, AgentRuntime, and ConsciousnessLoop."""
|
|
856
888
|
try:
|
|
857
|
-
from
|
|
858
|
-
|
|
859
|
-
|
|
889
|
+
from skcomms.core import SKComms
|
|
890
|
+
from .sync_engine import ensure_comms_dirs, get_comms_root
|
|
891
|
+
self._skcomms = SKComms.from_config()
|
|
892
|
+
expected_comms_root = get_comms_root(self.config.shared_root)
|
|
893
|
+
ensure_comms_dirs(self.config.shared_root)
|
|
894
|
+
for transport in self._skcomms.router.transports:
|
|
895
|
+
if getattr(transport, "name", "") == "syncthing" and hasattr(transport, "configure"):
|
|
896
|
+
transport.configure({"comms_root": str(expected_comms_root)})
|
|
897
|
+
logger.info("SKComms loaded — %d transports", len(self._skcomms.router.transports))
|
|
860
898
|
except ImportError:
|
|
861
|
-
logger.warning("
|
|
899
|
+
logger.warning("SKComms not installed — inbox polling disabled")
|
|
862
900
|
except Exception as exc:
|
|
863
|
-
logger.warning("
|
|
864
|
-
self.state.record_error(f"
|
|
901
|
+
logger.warning("SKComms failed to load: %s", exc)
|
|
902
|
+
self.state.record_error(f"SKComms load: {exc}")
|
|
865
903
|
|
|
866
904
|
try:
|
|
867
905
|
from .runtime import get_runtime
|
|
@@ -898,8 +936,8 @@ class DaemonService:
|
|
|
898
936
|
home=self.config.home,
|
|
899
937
|
shared_root=self.config.shared_root,
|
|
900
938
|
)
|
|
901
|
-
if self.
|
|
902
|
-
self._consciousness.
|
|
939
|
+
if self._skcomms:
|
|
940
|
+
self._consciousness.set_skcomms(self._skcomms)
|
|
903
941
|
logger.info("Consciousness loop loaded")
|
|
904
942
|
|
|
905
943
|
# Preload Ollama model into RAM so first real message is fast
|
|
@@ -953,12 +991,12 @@ class DaemonService:
|
|
|
953
991
|
self.state.record_error(f"Scheduler build: {exc}")
|
|
954
992
|
|
|
955
993
|
def _poll_loop(self) -> None:
|
|
956
|
-
"""Continuously poll
|
|
994
|
+
"""Continuously poll SKComms inbox for new messages."""
|
|
957
995
|
while not self._stop_event.is_set():
|
|
958
996
|
self._component_mgr.heartbeat("poll")
|
|
959
|
-
if self.
|
|
997
|
+
if self._skcomms:
|
|
960
998
|
try:
|
|
961
|
-
envelopes = self.
|
|
999
|
+
envelopes = self._skcomms.receive()
|
|
962
1000
|
count = len(envelopes)
|
|
963
1001
|
self.state.record_poll(count)
|
|
964
1002
|
if count > 0:
|
|
@@ -973,12 +1011,13 @@ class DaemonService:
|
|
|
973
1011
|
self._stop_event.wait(timeout=self.config.poll_interval)
|
|
974
1012
|
|
|
975
1013
|
def _health_loop(self) -> None:
|
|
976
|
-
"""Periodically check transport health."""
|
|
1014
|
+
"""Periodically check transport health and ping systemd watchdog."""
|
|
977
1015
|
while not self._stop_event.is_set():
|
|
978
1016
|
self._component_mgr.heartbeat("health")
|
|
979
|
-
|
|
1017
|
+
_sd_notify("WATCHDOG=1")
|
|
1018
|
+
if self._skcomms:
|
|
980
1019
|
try:
|
|
981
|
-
report = self.
|
|
1020
|
+
report = self._skcomms.status()
|
|
982
1021
|
transports = report.get("transports", {})
|
|
983
1022
|
serializable = {}
|
|
984
1023
|
for name, health in transports.items():
|
|
@@ -1127,10 +1166,10 @@ class DaemonService:
|
|
|
1127
1166
|
self.state.record_error(f"Process message: {exc}")
|
|
1128
1167
|
|
|
1129
1168
|
def _journal_incoming(self, sender: str, preview: str) -> None:
|
|
1130
|
-
"""Auto-journal an incoming
|
|
1169
|
+
"""Auto-journal an incoming SKComms message and store a tagged memory.
|
|
1131
1170
|
|
|
1132
1171
|
Writes a journal entry (title='From {sender}', moments=[preview]) and
|
|
1133
|
-
stores a short-term memory tagged '
|
|
1172
|
+
stores a short-term memory tagged 'skcomms-received'. Both operations
|
|
1134
1173
|
are best-effort: failures are logged at DEBUG level and never bubble up.
|
|
1135
1174
|
"""
|
|
1136
1175
|
try:
|
|
@@ -1145,24 +1184,26 @@ class DaemonService:
|
|
|
1145
1184
|
logger.debug("Auto-journal write failed: %s", exc)
|
|
1146
1185
|
|
|
1147
1186
|
try:
|
|
1148
|
-
self.
|
|
1149
|
-
logger.debug("
|
|
1187
|
+
self._store_skcomms_receipt(sender, preview)
|
|
1188
|
+
logger.debug("SKComms receipt stored for incoming message from %s", sender)
|
|
1150
1189
|
except Exception as exc:
|
|
1151
|
-
logger.debug("
|
|
1190
|
+
logger.debug("SKComms receipt store failed: %s", exc)
|
|
1152
1191
|
|
|
1153
|
-
def
|
|
1154
|
-
"""Write a
|
|
1192
|
+
def _store_skcomms_receipt(self, sender: str, preview: str) -> None:
|
|
1193
|
+
"""Write a skcomms receipt to the skcomms/received/ directory.
|
|
1155
1194
|
|
|
1156
1195
|
These are transport bookkeeping, NOT persistent memories, so they
|
|
1157
|
-
go to ``~/.skcapstone/agents/{agent}/
|
|
1196
|
+
go to ``~/.skcapstone/agents/{agent}/skcomms/received/`` instead of
|
|
1158
1197
|
polluting the memory/ tree that skmemory indexes.
|
|
1159
1198
|
"""
|
|
1160
1199
|
import json
|
|
1161
1200
|
import uuid
|
|
1162
1201
|
from datetime import datetime, timezone
|
|
1163
1202
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1203
|
+
from . import active_agent_name
|
|
1204
|
+
|
|
1205
|
+
agent_name = os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
|
|
1206
|
+
recv_dir = self.config.home / "agents" / agent_name / "skcomms" / "received"
|
|
1166
1207
|
recv_dir.mkdir(parents=True, exist_ok=True)
|
|
1167
1208
|
|
|
1168
1209
|
receipt_id = uuid.uuid4().hex[:12]
|
|
@@ -1262,7 +1303,8 @@ class DaemonService:
|
|
|
1262
1303
|
try:
|
|
1263
1304
|
ts = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
|
|
1264
1305
|
return datetime.now(timezone.utc) <= ts + timedelta(seconds=ttl)
|
|
1265
|
-
except Exception:
|
|
1306
|
+
except Exception as e:
|
|
1307
|
+
logger.warning("Failed to parse heartbeat timestamp %r: %s", ts_str, e)
|
|
1266
1308
|
return False
|
|
1267
1309
|
|
|
1268
1310
|
@staticmethod
|
|
@@ -1275,7 +1317,8 @@ class DaemonService:
|
|
|
1275
1317
|
stats["disk_total_gb"] = round(usage.total / (1024 ** 3), 1)
|
|
1276
1318
|
stats["disk_used_gb"] = round(usage.used / (1024 ** 3), 1)
|
|
1277
1319
|
stats["disk_free_gb"] = round(usage.free / (1024 ** 3), 1)
|
|
1278
|
-
except Exception:
|
|
1320
|
+
except Exception as e:
|
|
1321
|
+
logger.warning("Failed to collect disk stats: %s", e)
|
|
1279
1322
|
stats.update(disk_total_gb=0, disk_used_gb=0, disk_free_gb=0)
|
|
1280
1323
|
try:
|
|
1281
1324
|
import platform as _platform
|
|
@@ -1297,7 +1340,8 @@ class DaemonService:
|
|
|
1297
1340
|
stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
|
|
1298
1341
|
stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
|
|
1299
1342
|
stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
|
|
1300
|
-
except Exception:
|
|
1343
|
+
except Exception as e:
|
|
1344
|
+
logger.warning("Failed to collect memory stats: %s", e)
|
|
1301
1345
|
stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
|
|
1302
1346
|
return stats
|
|
1303
1347
|
|
|
@@ -1312,16 +1356,16 @@ class DaemonService:
|
|
|
1312
1356
|
try:
|
|
1313
1357
|
agent_name = runtime.manifest.name or agent_name
|
|
1314
1358
|
agent_fingerprint = getattr(runtime.manifest, "fingerprint", "")
|
|
1315
|
-
except Exception:
|
|
1316
|
-
|
|
1359
|
+
except Exception as exc:
|
|
1360
|
+
logger.warning("Failed to read agent name from runtime manifest: %s", exc)
|
|
1317
1361
|
identity_file = config.home / "identity" / "identity.json"
|
|
1318
1362
|
if identity_file.exists():
|
|
1319
1363
|
try:
|
|
1320
1364
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1321
1365
|
agent_name = ident.get("name", agent_name)
|
|
1322
1366
|
agent_fingerprint = ident.get("fingerprint", agent_fingerprint)
|
|
1323
|
-
except Exception:
|
|
1324
|
-
|
|
1367
|
+
except Exception as exc:
|
|
1368
|
+
logger.warning("Failed to read identity.json for dashboard: %s", exc)
|
|
1325
1369
|
|
|
1326
1370
|
# Consciousness stats
|
|
1327
1371
|
c_stats: dict = snap.get("consciousness", {})
|
|
@@ -1347,10 +1391,10 @@ class DaemonService:
|
|
|
1347
1391
|
"message_count": len(msgs),
|
|
1348
1392
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
1349
1393
|
})
|
|
1350
|
-
except Exception:
|
|
1351
|
-
|
|
1352
|
-
except Exception:
|
|
1353
|
-
|
|
1394
|
+
except Exception as exc:
|
|
1395
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
1396
|
+
except Exception as exc:
|
|
1397
|
+
logger.warning("Failed to list conversation files: %s", exc)
|
|
1354
1398
|
|
|
1355
1399
|
return {
|
|
1356
1400
|
"agent": {
|
|
@@ -1392,16 +1436,16 @@ class DaemonService:
|
|
|
1392
1436
|
agent["consciousness"] = "SINGULAR"
|
|
1393
1437
|
elif m.is_conscious:
|
|
1394
1438
|
agent["consciousness"] = "CONSCIOUS"
|
|
1395
|
-
except Exception:
|
|
1396
|
-
|
|
1439
|
+
except Exception as exc:
|
|
1440
|
+
logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
|
|
1397
1441
|
identity_file = config.home / "identity" / "identity.json"
|
|
1398
1442
|
if identity_file.exists():
|
|
1399
1443
|
try:
|
|
1400
1444
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1401
1445
|
agent["name"] = ident.get("name", agent["name"])
|
|
1402
1446
|
agent["fingerprint"] = ident.get("fingerprint", agent["fingerprint"])
|
|
1403
|
-
except Exception:
|
|
1404
|
-
|
|
1447
|
+
except Exception as exc:
|
|
1448
|
+
logger.warning("Failed to read identity.json for capstone dashboard: %s", exc)
|
|
1405
1449
|
|
|
1406
1450
|
# ── Pillar status ─────────────────────────────────────────
|
|
1407
1451
|
pillars: dict = {}
|
|
@@ -1411,8 +1455,8 @@ class DaemonService:
|
|
|
1411
1455
|
k: v.value
|
|
1412
1456
|
for k, v in runtime.manifest.pillar_summary.items()
|
|
1413
1457
|
}
|
|
1414
|
-
except Exception:
|
|
1415
|
-
|
|
1458
|
+
except Exception as exc:
|
|
1459
|
+
logger.warning("Failed to read pillar summary from manifest: %s", exc)
|
|
1416
1460
|
|
|
1417
1461
|
# ── Memory stats ──────────────────────────────────────────
|
|
1418
1462
|
memory: dict = {}
|
|
@@ -1426,8 +1470,8 @@ class DaemonService:
|
|
|
1426
1470
|
"long_term": ms.long_term,
|
|
1427
1471
|
"status": ms.status.value,
|
|
1428
1472
|
}
|
|
1429
|
-
except Exception:
|
|
1430
|
-
|
|
1473
|
+
except Exception as exc:
|
|
1474
|
+
logger.warning("Failed to collect memory stats for dashboard: %s", exc)
|
|
1431
1475
|
|
|
1432
1476
|
# ── Coordination board ────────────────────────────────────
|
|
1433
1477
|
board: dict = {"summary": {}, "active": []}
|
|
@@ -1461,16 +1505,16 @@ class DaemonService:
|
|
|
1461
1505
|
},
|
|
1462
1506
|
"active": active_tasks,
|
|
1463
1507
|
}
|
|
1464
|
-
except Exception:
|
|
1465
|
-
|
|
1508
|
+
except Exception as exc:
|
|
1509
|
+
logger.warning("Failed to collect coordination board data for dashboard: %s", exc)
|
|
1466
1510
|
|
|
1467
1511
|
# ── Consciousness stats ───────────────────────────────────
|
|
1468
1512
|
c_stats: dict = {}
|
|
1469
1513
|
if consciousness:
|
|
1470
1514
|
try:
|
|
1471
1515
|
c_stats = dict(consciousness.stats)
|
|
1472
|
-
except Exception:
|
|
1473
|
-
|
|
1516
|
+
except Exception as exc:
|
|
1517
|
+
logger.warning("Failed to read consciousness stats for dashboard: %s", exc)
|
|
1474
1518
|
|
|
1475
1519
|
return {
|
|
1476
1520
|
"agent": agent,
|
|
@@ -1844,17 +1888,18 @@ class DaemonService:
|
|
|
1844
1888
|
|
|
1845
1889
|
fingerprint: Optional[str] = None
|
|
1846
1890
|
try:
|
|
1847
|
-
from
|
|
1891
|
+
from skcomms.capauth_validator import CapAuthValidator
|
|
1848
1892
|
fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
|
|
1849
1893
|
except ImportError:
|
|
1850
|
-
#
|
|
1894
|
+
# skcomms not installed — fall back to skcapstone signed tokens
|
|
1851
1895
|
if token_str:
|
|
1852
1896
|
try:
|
|
1853
1897
|
from .tokens import import_token, verify_token
|
|
1854
1898
|
tok = import_token(token_str)
|
|
1855
1899
|
if verify_token(tok, home=config.home):
|
|
1856
1900
|
fingerprint = tok.payload.issuer
|
|
1857
|
-
except Exception:
|
|
1901
|
+
except Exception as e:
|
|
1902
|
+
logger.warning("Token verification fallback failed: %s", e)
|
|
1858
1903
|
fingerprint = None
|
|
1859
1904
|
|
|
1860
1905
|
if fingerprint is None:
|
|
@@ -1984,8 +2029,8 @@ class DaemonService:
|
|
|
1984
2029
|
entry["identity"] = json.loads(
|
|
1985
2030
|
identity_path.read_text(encoding="utf-8")
|
|
1986
2031
|
)
|
|
1987
|
-
except Exception:
|
|
1988
|
-
|
|
2032
|
+
except Exception as exc:
|
|
2033
|
+
logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
|
|
1989
2034
|
|
|
1990
2035
|
hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
|
|
1991
2036
|
if hb_path.exists():
|
|
@@ -1995,7 +2040,8 @@ class DaemonService:
|
|
|
1995
2040
|
hb["alive"] = alive
|
|
1996
2041
|
entry["heartbeat"] = hb
|
|
1997
2042
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
1998
|
-
except Exception:
|
|
2043
|
+
except Exception as exc:
|
|
2044
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
|
|
1999
2045
|
entry["status"] = "unknown"
|
|
2000
2046
|
else:
|
|
2001
2047
|
entry["status"] = "no_heartbeat"
|
|
@@ -2027,8 +2073,8 @@ class DaemonService:
|
|
|
2027
2073
|
entry["identity"] = json.loads(
|
|
2028
2074
|
identity_path.read_text(encoding="utf-8")
|
|
2029
2075
|
)
|
|
2030
|
-
except Exception:
|
|
2031
|
-
|
|
2076
|
+
except Exception as exc:
|
|
2077
|
+
logger.warning("Failed to read identity for agent %s: %s", name, exc)
|
|
2032
2078
|
|
|
2033
2079
|
hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
|
|
2034
2080
|
if hb_path.exists():
|
|
@@ -2038,8 +2084,8 @@ class DaemonService:
|
|
|
2038
2084
|
hb["alive"] = alive
|
|
2039
2085
|
entry["heartbeat"] = hb
|
|
2040
2086
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
2041
|
-
except Exception:
|
|
2042
|
-
|
|
2087
|
+
except Exception as exc:
|
|
2088
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
|
|
2043
2089
|
|
|
2044
2090
|
memory_dir = agent_dir / "memory"
|
|
2045
2091
|
if memory_dir.exists():
|
|
@@ -2062,8 +2108,8 @@ class DaemonService:
|
|
|
2062
2108
|
"message_count": len(msgs),
|
|
2063
2109
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
2064
2110
|
})
|
|
2065
|
-
except Exception:
|
|
2066
|
-
|
|
2111
|
+
except Exception as exc:
|
|
2112
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2067
2113
|
entry["recent_conversations"] = conv_list
|
|
2068
2114
|
|
|
2069
2115
|
if consciousness:
|
|
@@ -2092,8 +2138,8 @@ class DaemonService:
|
|
|
2092
2138
|
"last_message_time": last_msg.get("timestamp") if msgs else None,
|
|
2093
2139
|
"last_message_preview": (last_content or "")[:120],
|
|
2094
2140
|
})
|
|
2095
|
-
except Exception:
|
|
2096
|
-
|
|
2141
|
+
except Exception as exc:
|
|
2142
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2097
2143
|
self._json_response({"conversations": conversations})
|
|
2098
2144
|
|
|
2099
2145
|
# ── Conversations: single peer history ────────────────────
|
|
@@ -2172,7 +2218,8 @@ class DaemonService:
|
|
|
2172
2218
|
length = int(self.headers.get("Content-Length", 0))
|
|
2173
2219
|
body = self.rfile.read(length) if length > 0 else b"{}"
|
|
2174
2220
|
data = json.loads(body)
|
|
2175
|
-
except Exception:
|
|
2221
|
+
except Exception as e:
|
|
2222
|
+
logger.warning("Failed to parse request JSON body: %s", e)
|
|
2176
2223
|
self._json_response({"error": "invalid JSON body"}, status=400)
|
|
2177
2224
|
return
|
|
2178
2225
|
|
|
@@ -2184,7 +2231,7 @@ class DaemonService:
|
|
|
2184
2231
|
message_id = str(uuid.uuid4())
|
|
2185
2232
|
ts = datetime.now(timezone.utc).isoformat()
|
|
2186
2233
|
|
|
2187
|
-
# Build
|
|
2234
|
+
# Build SKComms envelope
|
|
2188
2235
|
envelope = {
|
|
2189
2236
|
"message_id": message_id,
|
|
2190
2237
|
"sender": "api",
|
|
@@ -2196,7 +2243,7 @@ class DaemonService:
|
|
|
2196
2243
|
},
|
|
2197
2244
|
}
|
|
2198
2245
|
|
|
2199
|
-
# Write to
|
|
2246
|
+
# Write to SKComms outbox
|
|
2200
2247
|
try:
|
|
2201
2248
|
outbox = config.shared_root / "sync" / "comms" / "outbox"
|
|
2202
2249
|
outbox.mkdir(parents=True, exist_ok=True)
|
|
@@ -2469,23 +2516,31 @@ class DaemonService:
|
|
|
2469
2516
|
def read_pid(home: Optional[Path] = None) -> Optional[int]:
|
|
2470
2517
|
"""Read the daemon PID from the PID file.
|
|
2471
2518
|
|
|
2519
|
+
Checks the given home directory first, then falls back to the shared
|
|
2520
|
+
root (AGENT_HOME / ~/.skcapstone) since the daemon writes its PID
|
|
2521
|
+
to config.home which defaults to the shared root.
|
|
2522
|
+
|
|
2472
2523
|
Args:
|
|
2473
|
-
home: Agent home directory.
|
|
2524
|
+
home: Agent home directory (or shared root).
|
|
2474
2525
|
|
|
2475
2526
|
Returns:
|
|
2476
2527
|
PID as int, or None if not running.
|
|
2477
2528
|
"""
|
|
2478
2529
|
home = (home or Path(AGENT_HOME)).expanduser()
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2530
|
+
shared_root = Path(AGENT_HOME).expanduser()
|
|
2531
|
+
|
|
2532
|
+
# Check agent home first, then shared root
|
|
2533
|
+
for candidate in (home, shared_root):
|
|
2534
|
+
pid_path = candidate / PID_FILE
|
|
2535
|
+
if not pid_path.exists():
|
|
2536
|
+
continue
|
|
2537
|
+
try:
|
|
2538
|
+
pid = int(pid_path.read_text(encoding="utf-8").strip())
|
|
2539
|
+
os.kill(pid, 0)
|
|
2540
|
+
return pid
|
|
2541
|
+
except (ValueError, ProcessLookupError, PermissionError):
|
|
2542
|
+
pid_path.unlink(missing_ok=True)
|
|
2543
|
+
return None
|
|
2489
2544
|
|
|
2490
2545
|
|
|
2491
2546
|
def is_running(home: Optional[Path] = None) -> bool:
|
|
@@ -240,8 +240,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
|
|
|
240
240
|
"recent_errors": recent_errors,
|
|
241
241
|
"inflight_count": snap.get("inflight_count", 0),
|
|
242
242
|
}
|
|
243
|
-
except Exception:
|
|
244
|
-
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
logger.warning("Failed to fetch daemon status for dashboard: %s", exc)
|
|
245
245
|
|
|
246
246
|
# ── Daemon /consciousness ─────────────────────────────────────────────────
|
|
247
247
|
consciousness_info: dict = {"enabled": False}
|
|
@@ -249,8 +249,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
|
|
|
249
249
|
url = f"http://127.0.0.1:{daemon_port}/consciousness"
|
|
250
250
|
with urllib.request.urlopen(url, timeout=3) as resp:
|
|
251
251
|
consciousness_info = json.loads(resp.read())
|
|
252
|
-
except Exception:
|
|
253
|
-
|
|
252
|
+
except Exception as exc:
|
|
253
|
+
logger.debug("Failed to fetch consciousness status for dashboard: %s", exc)
|
|
254
254
|
|
|
255
255
|
# ── LLM backend availability ──────────────────────────────────────────────
|
|
256
256
|
backend_health: dict = {
|
|
@@ -266,16 +266,16 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
|
|
|
266
266
|
urllib.request.Request(f"{ollama_host}/api/tags"), timeout=2
|
|
267
267
|
):
|
|
268
268
|
backend_health["ollama"] = True
|
|
269
|
-
except Exception:
|
|
270
|
-
|
|
269
|
+
except Exception as exc:
|
|
270
|
+
logger.debug("Ollama probe failed (not available): %s", exc)
|
|
271
271
|
|
|
272
272
|
# ── Heartbeat (system metrics + active conversations) ─────────────────────
|
|
273
273
|
system_info: dict = {}
|
|
274
274
|
active_conversations: int = 0
|
|
275
275
|
try:
|
|
276
|
-
from . import SHARED_ROOT
|
|
276
|
+
from . import SHARED_ROOT, DEFAULT_AGENT
|
|
277
277
|
identity_path = home / "identity" / "identity.json"
|
|
278
|
-
agent_name =
|
|
278
|
+
agent_name = DEFAULT_AGENT
|
|
279
279
|
if identity_path.exists():
|
|
280
280
|
ident = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
281
281
|
agent_name = ident.get("name", agent_name).lower()
|
|
@@ -291,8 +291,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
|
|
|
291
291
|
"cpu_load_1min": hb.get("cpu_load_1min", 0.0),
|
|
292
292
|
"memory_used_mb": hb.get("memory_used_mb", 0),
|
|
293
293
|
}
|
|
294
|
-
except Exception:
|
|
295
|
-
|
|
294
|
+
except Exception as exc:
|
|
295
|
+
logger.warning("Failed to read heartbeat data for dashboard: %s", exc)
|
|
296
296
|
|
|
297
297
|
return {
|
|
298
298
|
"generated_at": now,
|