@smilintux/skcapstone 0.9.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 +278 -1
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +10 -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.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- 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 +95 -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 +196 -11
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +908 -0
- package/scripts/proxy-monitor.sh +89 -0
- 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 +856 -0
- package/scripts/telegram-catchup-all.sh +147 -0
- 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/blueprints/builtins/itil-operations.yaml +40 -0
- 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 +121 -42
- 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 +151 -88
- 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 +35 -25
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +34 -30
- 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 +426 -0
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +341 -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 +1000 -126
- 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 +72 -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,21 +1317,31 @@ 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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1324
|
+
import platform as _platform
|
|
1325
|
+
if _platform.system() == "Linux":
|
|
1326
|
+
meminfo: dict = {}
|
|
1327
|
+
with open("/proc/meminfo") as fh:
|
|
1328
|
+
for line in fh:
|
|
1329
|
+
parts = line.split()
|
|
1330
|
+
if len(parts) >= 2:
|
|
1331
|
+
meminfo[parts[0].rstrip(":")] = int(parts[1])
|
|
1332
|
+
total_kb = meminfo.get("MemTotal", 0)
|
|
1333
|
+
avail_kb = meminfo.get("MemAvailable", 0)
|
|
1334
|
+
stats["memory_total_mb"] = round(total_kb / 1024)
|
|
1335
|
+
stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
|
|
1336
|
+
stats["memory_free_mb"] = round(avail_kb / 1024)
|
|
1337
|
+
else:
|
|
1338
|
+
import psutil
|
|
1339
|
+
mem = psutil.virtual_memory()
|
|
1340
|
+
stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
|
|
1341
|
+
stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
|
|
1342
|
+
stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
|
|
1343
|
+
except Exception as e:
|
|
1344
|
+
logger.warning("Failed to collect memory stats: %s", e)
|
|
1293
1345
|
stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
|
|
1294
1346
|
return stats
|
|
1295
1347
|
|
|
@@ -1304,16 +1356,16 @@ class DaemonService:
|
|
|
1304
1356
|
try:
|
|
1305
1357
|
agent_name = runtime.manifest.name or agent_name
|
|
1306
1358
|
agent_fingerprint = getattr(runtime.manifest, "fingerprint", "")
|
|
1307
|
-
except Exception:
|
|
1308
|
-
|
|
1359
|
+
except Exception as exc:
|
|
1360
|
+
logger.warning("Failed to read agent name from runtime manifest: %s", exc)
|
|
1309
1361
|
identity_file = config.home / "identity" / "identity.json"
|
|
1310
1362
|
if identity_file.exists():
|
|
1311
1363
|
try:
|
|
1312
1364
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1313
1365
|
agent_name = ident.get("name", agent_name)
|
|
1314
1366
|
agent_fingerprint = ident.get("fingerprint", agent_fingerprint)
|
|
1315
|
-
except Exception:
|
|
1316
|
-
|
|
1367
|
+
except Exception as exc:
|
|
1368
|
+
logger.warning("Failed to read identity.json for dashboard: %s", exc)
|
|
1317
1369
|
|
|
1318
1370
|
# Consciousness stats
|
|
1319
1371
|
c_stats: dict = snap.get("consciousness", {})
|
|
@@ -1339,10 +1391,10 @@ class DaemonService:
|
|
|
1339
1391
|
"message_count": len(msgs),
|
|
1340
1392
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
1341
1393
|
})
|
|
1342
|
-
except Exception:
|
|
1343
|
-
|
|
1344
|
-
except Exception:
|
|
1345
|
-
|
|
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)
|
|
1346
1398
|
|
|
1347
1399
|
return {
|
|
1348
1400
|
"agent": {
|
|
@@ -1384,16 +1436,16 @@ class DaemonService:
|
|
|
1384
1436
|
agent["consciousness"] = "SINGULAR"
|
|
1385
1437
|
elif m.is_conscious:
|
|
1386
1438
|
agent["consciousness"] = "CONSCIOUS"
|
|
1387
|
-
except Exception:
|
|
1388
|
-
|
|
1439
|
+
except Exception as exc:
|
|
1440
|
+
logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
|
|
1389
1441
|
identity_file = config.home / "identity" / "identity.json"
|
|
1390
1442
|
if identity_file.exists():
|
|
1391
1443
|
try:
|
|
1392
1444
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1393
1445
|
agent["name"] = ident.get("name", agent["name"])
|
|
1394
1446
|
agent["fingerprint"] = ident.get("fingerprint", agent["fingerprint"])
|
|
1395
|
-
except Exception:
|
|
1396
|
-
|
|
1447
|
+
except Exception as exc:
|
|
1448
|
+
logger.warning("Failed to read identity.json for capstone dashboard: %s", exc)
|
|
1397
1449
|
|
|
1398
1450
|
# ── Pillar status ─────────────────────────────────────────
|
|
1399
1451
|
pillars: dict = {}
|
|
@@ -1403,8 +1455,8 @@ class DaemonService:
|
|
|
1403
1455
|
k: v.value
|
|
1404
1456
|
for k, v in runtime.manifest.pillar_summary.items()
|
|
1405
1457
|
}
|
|
1406
|
-
except Exception:
|
|
1407
|
-
|
|
1458
|
+
except Exception as exc:
|
|
1459
|
+
logger.warning("Failed to read pillar summary from manifest: %s", exc)
|
|
1408
1460
|
|
|
1409
1461
|
# ── Memory stats ──────────────────────────────────────────
|
|
1410
1462
|
memory: dict = {}
|
|
@@ -1418,8 +1470,8 @@ class DaemonService:
|
|
|
1418
1470
|
"long_term": ms.long_term,
|
|
1419
1471
|
"status": ms.status.value,
|
|
1420
1472
|
}
|
|
1421
|
-
except Exception:
|
|
1422
|
-
|
|
1473
|
+
except Exception as exc:
|
|
1474
|
+
logger.warning("Failed to collect memory stats for dashboard: %s", exc)
|
|
1423
1475
|
|
|
1424
1476
|
# ── Coordination board ────────────────────────────────────
|
|
1425
1477
|
board: dict = {"summary": {}, "active": []}
|
|
@@ -1453,16 +1505,16 @@ class DaemonService:
|
|
|
1453
1505
|
},
|
|
1454
1506
|
"active": active_tasks,
|
|
1455
1507
|
}
|
|
1456
|
-
except Exception:
|
|
1457
|
-
|
|
1508
|
+
except Exception as exc:
|
|
1509
|
+
logger.warning("Failed to collect coordination board data for dashboard: %s", exc)
|
|
1458
1510
|
|
|
1459
1511
|
# ── Consciousness stats ───────────────────────────────────
|
|
1460
1512
|
c_stats: dict = {}
|
|
1461
1513
|
if consciousness:
|
|
1462
1514
|
try:
|
|
1463
1515
|
c_stats = dict(consciousness.stats)
|
|
1464
|
-
except Exception:
|
|
1465
|
-
|
|
1516
|
+
except Exception as exc:
|
|
1517
|
+
logger.warning("Failed to read consciousness stats for dashboard: %s", exc)
|
|
1466
1518
|
|
|
1467
1519
|
return {
|
|
1468
1520
|
"agent": agent,
|
|
@@ -1836,17 +1888,18 @@ class DaemonService:
|
|
|
1836
1888
|
|
|
1837
1889
|
fingerprint: Optional[str] = None
|
|
1838
1890
|
try:
|
|
1839
|
-
from
|
|
1891
|
+
from skcomms.capauth_validator import CapAuthValidator
|
|
1840
1892
|
fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
|
|
1841
1893
|
except ImportError:
|
|
1842
|
-
#
|
|
1894
|
+
# skcomms not installed — fall back to skcapstone signed tokens
|
|
1843
1895
|
if token_str:
|
|
1844
1896
|
try:
|
|
1845
1897
|
from .tokens import import_token, verify_token
|
|
1846
1898
|
tok = import_token(token_str)
|
|
1847
1899
|
if verify_token(tok, home=config.home):
|
|
1848
1900
|
fingerprint = tok.payload.issuer
|
|
1849
|
-
except Exception:
|
|
1901
|
+
except Exception as e:
|
|
1902
|
+
logger.warning("Token verification fallback failed: %s", e)
|
|
1850
1903
|
fingerprint = None
|
|
1851
1904
|
|
|
1852
1905
|
if fingerprint is None:
|
|
@@ -1976,8 +2029,8 @@ class DaemonService:
|
|
|
1976
2029
|
entry["identity"] = json.loads(
|
|
1977
2030
|
identity_path.read_text(encoding="utf-8")
|
|
1978
2031
|
)
|
|
1979
|
-
except Exception:
|
|
1980
|
-
|
|
2032
|
+
except Exception as exc:
|
|
2033
|
+
logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
|
|
1981
2034
|
|
|
1982
2035
|
hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
|
|
1983
2036
|
if hb_path.exists():
|
|
@@ -1987,7 +2040,8 @@ class DaemonService:
|
|
|
1987
2040
|
hb["alive"] = alive
|
|
1988
2041
|
entry["heartbeat"] = hb
|
|
1989
2042
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
1990
|
-
except Exception:
|
|
2043
|
+
except Exception as exc:
|
|
2044
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
|
|
1991
2045
|
entry["status"] = "unknown"
|
|
1992
2046
|
else:
|
|
1993
2047
|
entry["status"] = "no_heartbeat"
|
|
@@ -2019,8 +2073,8 @@ class DaemonService:
|
|
|
2019
2073
|
entry["identity"] = json.loads(
|
|
2020
2074
|
identity_path.read_text(encoding="utf-8")
|
|
2021
2075
|
)
|
|
2022
|
-
except Exception:
|
|
2023
|
-
|
|
2076
|
+
except Exception as exc:
|
|
2077
|
+
logger.warning("Failed to read identity for agent %s: %s", name, exc)
|
|
2024
2078
|
|
|
2025
2079
|
hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
|
|
2026
2080
|
if hb_path.exists():
|
|
@@ -2030,8 +2084,8 @@ class DaemonService:
|
|
|
2030
2084
|
hb["alive"] = alive
|
|
2031
2085
|
entry["heartbeat"] = hb
|
|
2032
2086
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
2033
|
-
except Exception:
|
|
2034
|
-
|
|
2087
|
+
except Exception as exc:
|
|
2088
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
|
|
2035
2089
|
|
|
2036
2090
|
memory_dir = agent_dir / "memory"
|
|
2037
2091
|
if memory_dir.exists():
|
|
@@ -2054,8 +2108,8 @@ class DaemonService:
|
|
|
2054
2108
|
"message_count": len(msgs),
|
|
2055
2109
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
2056
2110
|
})
|
|
2057
|
-
except Exception:
|
|
2058
|
-
|
|
2111
|
+
except Exception as exc:
|
|
2112
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2059
2113
|
entry["recent_conversations"] = conv_list
|
|
2060
2114
|
|
|
2061
2115
|
if consciousness:
|
|
@@ -2084,8 +2138,8 @@ class DaemonService:
|
|
|
2084
2138
|
"last_message_time": last_msg.get("timestamp") if msgs else None,
|
|
2085
2139
|
"last_message_preview": (last_content or "")[:120],
|
|
2086
2140
|
})
|
|
2087
|
-
except Exception:
|
|
2088
|
-
|
|
2141
|
+
except Exception as exc:
|
|
2142
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2089
2143
|
self._json_response({"conversations": conversations})
|
|
2090
2144
|
|
|
2091
2145
|
# ── Conversations: single peer history ────────────────────
|
|
@@ -2164,7 +2218,8 @@ class DaemonService:
|
|
|
2164
2218
|
length = int(self.headers.get("Content-Length", 0))
|
|
2165
2219
|
body = self.rfile.read(length) if length > 0 else b"{}"
|
|
2166
2220
|
data = json.loads(body)
|
|
2167
|
-
except Exception:
|
|
2221
|
+
except Exception as e:
|
|
2222
|
+
logger.warning("Failed to parse request JSON body: %s", e)
|
|
2168
2223
|
self._json_response({"error": "invalid JSON body"}, status=400)
|
|
2169
2224
|
return
|
|
2170
2225
|
|
|
@@ -2176,7 +2231,7 @@ class DaemonService:
|
|
|
2176
2231
|
message_id = str(uuid.uuid4())
|
|
2177
2232
|
ts = datetime.now(timezone.utc).isoformat()
|
|
2178
2233
|
|
|
2179
|
-
# Build
|
|
2234
|
+
# Build SKComms envelope
|
|
2180
2235
|
envelope = {
|
|
2181
2236
|
"message_id": message_id,
|
|
2182
2237
|
"sender": "api",
|
|
@@ -2188,7 +2243,7 @@ class DaemonService:
|
|
|
2188
2243
|
},
|
|
2189
2244
|
}
|
|
2190
2245
|
|
|
2191
|
-
# Write to
|
|
2246
|
+
# Write to SKComms outbox
|
|
2192
2247
|
try:
|
|
2193
2248
|
outbox = config.shared_root / "sync" / "comms" / "outbox"
|
|
2194
2249
|
outbox.mkdir(parents=True, exist_ok=True)
|
|
@@ -2461,23 +2516,31 @@ class DaemonService:
|
|
|
2461
2516
|
def read_pid(home: Optional[Path] = None) -> Optional[int]:
|
|
2462
2517
|
"""Read the daemon PID from the PID file.
|
|
2463
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
|
+
|
|
2464
2523
|
Args:
|
|
2465
|
-
home: Agent home directory.
|
|
2524
|
+
home: Agent home directory (or shared root).
|
|
2466
2525
|
|
|
2467
2526
|
Returns:
|
|
2468
2527
|
PID as int, or None if not running.
|
|
2469
2528
|
"""
|
|
2470
2529
|
home = (home or Path(AGENT_HOME)).expanduser()
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
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
|
|
2481
2544
|
|
|
2482
2545
|
|
|
2483
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,
|