@smilintux/skcapstone 0.4.6 → 0.5.0
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/.github/workflows/publish.yml +8 -1
- package/docs/CUSTOM_AGENT.md +184 -0
- package/docs/GETTING_STARTED.md +3 -0
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/archive-sessions.sh +88 -0
- package/scripts/install.sh +39 -8
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +878 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/refresh-anthropic-token.sh +94 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +136 -0
- package/scripts/watch-anthropic-token.sh +117 -0
- package/src/skcapstone/__init__.py +1 -1
- package/src/skcapstone/_cli_monolith.py +4 -4
- package/src/skcapstone/api.py +36 -35
- package/src/skcapstone/auction.py +8 -8
- package/src/skcapstone/blueprint_registry.py +2 -2
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/chat.py +4 -4
- package/src/skcapstone/cli/__init__.py +2 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/chat.py +5 -2
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/daemon.py +116 -41
- package/src/skcapstone/cli/itil.py +434 -0
- package/src/skcapstone/cli/memory.py +4 -4
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +5 -2
- package/src/skcapstone/cli/status.py +11 -8
- package/src/skcapstone/cli/upgrade_cmd.py +7 -4
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +27 -0
- package/src/skcapstone/consciousness_loop.py +20 -18
- package/src/skcapstone/coordination.py +6 -2
- package/src/skcapstone/daemon.py +51 -42
- package/src/skcapstone/dashboard.py +8 -8
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
- package/src/skcapstone/doctor.py +5 -2
- package/src/skcapstone/dreaming.py +1440 -0
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +2 -2
- package/src/skcapstone/fuse_mount.py +21 -13
- package/src/skcapstone/heartbeat.py +33 -29
- package/src/skcapstone/itil.py +1104 -0
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_server.py +306 -4
- package/src/skcapstone/mcp_tools/__init__.py +4 -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/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +9 -6
- package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
- package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/metrics.py +8 -8
- package/src/skcapstone/migrate_memories.py +2 -2
- package/src/skcapstone/models.py +14 -0
- package/src/skcapstone/onboard.py +137 -14
- package/src/skcapstone/peer_directory.py +2 -2
- package/src/skcapstone/providers/docker.py +2 -2
- package/src/skcapstone/scheduled_tasks.py +107 -0
- package/src/skcapstone/service_health.py +83 -4
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +17 -0
|
@@ -117,3 +117,30 @@ def write_default_config(home: Path) -> Path:
|
|
|
117
117
|
config_path.write_text(header + content, encoding="utf-8")
|
|
118
118
|
logger.info("Wrote default consciousness config to %s", config_path)
|
|
119
119
|
return config_path
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def load_dreaming_config(
|
|
123
|
+
home: Path,
|
|
124
|
+
config_path: Optional[Path] = None,
|
|
125
|
+
):
|
|
126
|
+
"""Load dreaming config from the consciousness.yaml ``dreaming:`` section.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
home: Agent home directory.
|
|
130
|
+
config_path: Explicit path to config file (overrides default).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
DreamingConfig (defaults if section is missing or unparseable).
|
|
134
|
+
"""
|
|
135
|
+
from .dreaming import DreamingConfig
|
|
136
|
+
|
|
137
|
+
yaml_path = config_path or (home / "config" / CONFIG_FILENAME)
|
|
138
|
+
if not yaml_path.exists():
|
|
139
|
+
return DreamingConfig()
|
|
140
|
+
try:
|
|
141
|
+
raw = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
|
|
142
|
+
if raw and isinstance(raw, dict) and "dreaming" in raw:
|
|
143
|
+
return DreamingConfig.model_validate(raw["dreaming"])
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
logger.warning("Failed to parse dreaming config: %s", exc)
|
|
146
|
+
return DreamingConfig()
|
|
@@ -211,8 +211,8 @@ class _OllamaPool:
|
|
|
211
211
|
if self._conn is not None:
|
|
212
212
|
try:
|
|
213
213
|
self._conn.close()
|
|
214
|
-
except Exception:
|
|
215
|
-
|
|
214
|
+
except Exception as exc:
|
|
215
|
+
logger.warning("Failed to close connection socket: %s", exc)
|
|
216
216
|
self._conn = None
|
|
217
217
|
self._created_at = 0.0
|
|
218
218
|
|
|
@@ -995,8 +995,8 @@ class SystemPromptBuilder:
|
|
|
995
995
|
f"trust: {anchor.get('trust', 5)}/10, "
|
|
996
996
|
f"connection: {anchor.get('connection', 5)}/10"
|
|
997
997
|
)
|
|
998
|
-
except Exception:
|
|
999
|
-
|
|
998
|
+
except Exception as exc:
|
|
999
|
+
logger.warning("Failed to load warmth anchor: %s", exc)
|
|
1000
1000
|
return ""
|
|
1001
1001
|
|
|
1002
1002
|
def _load_context(self) -> str:
|
|
@@ -1252,7 +1252,8 @@ class ConsciousnessLoop:
|
|
|
1252
1252
|
try:
|
|
1253
1253
|
from skcapstone.mood import MoodTracker
|
|
1254
1254
|
self._mood_tracker: Optional[Any] = MoodTracker(home=self._home)
|
|
1255
|
-
except Exception:
|
|
1255
|
+
except Exception as exc:
|
|
1256
|
+
logger.warning("MoodTracker unavailable, mood tracking disabled: %s", exc)
|
|
1256
1257
|
self._mood_tracker = None
|
|
1257
1258
|
|
|
1258
1259
|
# Agent identity for inbox filtering
|
|
@@ -1266,7 +1267,8 @@ class ConsciousnessLoop:
|
|
|
1266
1267
|
try:
|
|
1267
1268
|
from skcapstone.peer_directory import PeerDirectory
|
|
1268
1269
|
self._peer_dir: Optional[Any] = PeerDirectory(home=self._shared_root)
|
|
1269
|
-
except Exception:
|
|
1270
|
+
except Exception as exc:
|
|
1271
|
+
logger.warning("PeerDirectory unavailable, peer tracking disabled: %s", exc)
|
|
1270
1272
|
self._peer_dir = None
|
|
1271
1273
|
|
|
1272
1274
|
def set_skcomm(self, skcomm) -> None:
|
|
@@ -1334,15 +1336,15 @@ class ConsciousnessLoop:
|
|
|
1334
1336
|
try:
|
|
1335
1337
|
self._observer.stop()
|
|
1336
1338
|
self._observer.join(timeout=5)
|
|
1337
|
-
except Exception:
|
|
1338
|
-
|
|
1339
|
+
except Exception as exc:
|
|
1340
|
+
logger.warning("Error stopping inotify observer: %s", exc)
|
|
1339
1341
|
# Stop sync watcher if running
|
|
1340
1342
|
sync_watcher = getattr(self, "_sync_watcher", None)
|
|
1341
1343
|
if sync_watcher:
|
|
1342
1344
|
try:
|
|
1343
1345
|
sync_watcher.stop()
|
|
1344
|
-
except Exception:
|
|
1345
|
-
|
|
1346
|
+
except Exception as exc:
|
|
1347
|
+
logger.warning("Error stopping sync watcher: %s", exc)
|
|
1346
1348
|
self._executor.shutdown(wait=False)
|
|
1347
1349
|
self._metrics.stop()
|
|
1348
1350
|
logger.info("Consciousness loop stopped.")
|
|
@@ -1353,8 +1355,8 @@ class ConsciousnessLoop:
|
|
|
1353
1355
|
try:
|
|
1354
1356
|
self._observer.stop()
|
|
1355
1357
|
self._observer.join(timeout=5)
|
|
1356
|
-
except Exception:
|
|
1357
|
-
|
|
1358
|
+
except Exception as exc:
|
|
1359
|
+
logger.warning("Error stopping inotify observer during restart: %s", exc)
|
|
1358
1360
|
self._observer = None
|
|
1359
1361
|
|
|
1360
1362
|
# Re-launch inotify in a new thread
|
|
@@ -1419,8 +1421,8 @@ class ConsciousnessLoop:
|
|
|
1419
1421
|
if self._peer_dir is not None:
|
|
1420
1422
|
try:
|
|
1421
1423
|
self._peer_dir.update_last_seen(sender)
|
|
1422
|
-
except Exception:
|
|
1423
|
-
|
|
1424
|
+
except Exception as exc:
|
|
1425
|
+
logger.warning("Failed to update peer directory for %s: %s", sender, exc)
|
|
1424
1426
|
self._metrics.record_message(sender)
|
|
1425
1427
|
|
|
1426
1428
|
# Desktop notification
|
|
@@ -1827,8 +1829,8 @@ class ConsciousnessLoop:
|
|
|
1827
1829
|
if identity_path.exists():
|
|
1828
1830
|
data = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
1829
1831
|
return data.get("name", "").lower()
|
|
1830
|
-
except Exception:
|
|
1831
|
-
|
|
1832
|
+
except Exception as exc:
|
|
1833
|
+
logger.warning("Failed to resolve agent name from identity.json: %s", exc)
|
|
1832
1834
|
return ""
|
|
1833
1835
|
|
|
1834
1836
|
def _verify_message_signature(self, data: dict) -> str:
|
|
@@ -1948,8 +1950,8 @@ class ConsciousnessLoop:
|
|
|
1948
1950
|
queue_size,
|
|
1949
1951
|
)
|
|
1950
1952
|
return
|
|
1951
|
-
except Exception:
|
|
1952
|
-
|
|
1953
|
+
except Exception as exc:
|
|
1954
|
+
logger.debug("Could not check executor queue depth: %s", exc)
|
|
1953
1955
|
|
|
1954
1956
|
# PGP signature verification (soft enforcement — log only)
|
|
1955
1957
|
sig_sender = _sanitize_peer_name(
|
|
@@ -14,6 +14,7 @@ Directory layout:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
+
import logging
|
|
17
18
|
import re
|
|
18
19
|
import socket
|
|
19
20
|
import uuid
|
|
@@ -24,6 +25,8 @@ from typing import Optional
|
|
|
24
25
|
|
|
25
26
|
from pydantic import BaseModel, Field
|
|
26
27
|
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
def _slugify_filename(text: str) -> str:
|
|
29
32
|
"""Convert text to a filesystem-safe slug.
|
|
@@ -123,6 +126,7 @@ class AgentFile(BaseModel):
|
|
|
123
126
|
claimed_tasks: list[str] = Field(default_factory=list)
|
|
124
127
|
completed_tasks: list[str] = Field(default_factory=list)
|
|
125
128
|
capabilities: list[str] = Field(default_factory=list)
|
|
129
|
+
itil_claims: list[str] = Field(default_factory=list)
|
|
126
130
|
notes: str = ""
|
|
127
131
|
|
|
128
132
|
|
|
@@ -473,9 +477,9 @@ def _mint_joules_for_task(board: Board, task_id: str, agent_name: str) -> None:
|
|
|
473
477
|
if record:
|
|
474
478
|
title = task_data.get("title", task_id)
|
|
475
479
|
print(f"[SKJoule] Minted {record.joules} Joules for task: {title}")
|
|
476
|
-
except Exception:
|
|
480
|
+
except Exception as exc:
|
|
477
481
|
# Never let tokenization failure block task completion
|
|
478
|
-
|
|
482
|
+
logger.warning("Joule tokenization failed for task %s (non-fatal): %s", task_id, exc)
|
|
479
483
|
|
|
480
484
|
|
|
481
485
|
_BRIEFING_PROTOCOL = """\
|
package/src/skcapstone/daemon.py
CHANGED
|
@@ -1278,17 +1278,25 @@ class DaemonService:
|
|
|
1278
1278
|
except Exception:
|
|
1279
1279
|
stats.update(disk_total_gb=0, disk_used_gb=0, disk_free_gb=0)
|
|
1280
1280
|
try:
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1281
|
+
import platform as _platform
|
|
1282
|
+
if _platform.system() == "Linux":
|
|
1283
|
+
meminfo: dict = {}
|
|
1284
|
+
with open("/proc/meminfo") as fh:
|
|
1285
|
+
for line in fh:
|
|
1286
|
+
parts = line.split()
|
|
1287
|
+
if len(parts) >= 2:
|
|
1288
|
+
meminfo[parts[0].rstrip(":")] = int(parts[1])
|
|
1289
|
+
total_kb = meminfo.get("MemTotal", 0)
|
|
1290
|
+
avail_kb = meminfo.get("MemAvailable", 0)
|
|
1291
|
+
stats["memory_total_mb"] = round(total_kb / 1024)
|
|
1292
|
+
stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
|
|
1293
|
+
stats["memory_free_mb"] = round(avail_kb / 1024)
|
|
1294
|
+
else:
|
|
1295
|
+
import psutil
|
|
1296
|
+
mem = psutil.virtual_memory()
|
|
1297
|
+
stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
|
|
1298
|
+
stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
|
|
1299
|
+
stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
|
|
1292
1300
|
except Exception:
|
|
1293
1301
|
stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
|
|
1294
1302
|
return stats
|
|
@@ -1304,16 +1312,16 @@ class DaemonService:
|
|
|
1304
1312
|
try:
|
|
1305
1313
|
agent_name = runtime.manifest.name or agent_name
|
|
1306
1314
|
agent_fingerprint = getattr(runtime.manifest, "fingerprint", "")
|
|
1307
|
-
except Exception:
|
|
1308
|
-
|
|
1315
|
+
except Exception as exc:
|
|
1316
|
+
logger.warning("Failed to read agent name from runtime manifest: %s", exc)
|
|
1309
1317
|
identity_file = config.home / "identity" / "identity.json"
|
|
1310
1318
|
if identity_file.exists():
|
|
1311
1319
|
try:
|
|
1312
1320
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1313
1321
|
agent_name = ident.get("name", agent_name)
|
|
1314
1322
|
agent_fingerprint = ident.get("fingerprint", agent_fingerprint)
|
|
1315
|
-
except Exception:
|
|
1316
|
-
|
|
1323
|
+
except Exception as exc:
|
|
1324
|
+
logger.warning("Failed to read identity.json for dashboard: %s", exc)
|
|
1317
1325
|
|
|
1318
1326
|
# Consciousness stats
|
|
1319
1327
|
c_stats: dict = snap.get("consciousness", {})
|
|
@@ -1339,10 +1347,10 @@ class DaemonService:
|
|
|
1339
1347
|
"message_count": len(msgs),
|
|
1340
1348
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
1341
1349
|
})
|
|
1342
|
-
except Exception:
|
|
1343
|
-
|
|
1344
|
-
except Exception:
|
|
1345
|
-
|
|
1350
|
+
except Exception as exc:
|
|
1351
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
1352
|
+
except Exception as exc:
|
|
1353
|
+
logger.warning("Failed to list conversation files: %s", exc)
|
|
1346
1354
|
|
|
1347
1355
|
return {
|
|
1348
1356
|
"agent": {
|
|
@@ -1384,16 +1392,16 @@ class DaemonService:
|
|
|
1384
1392
|
agent["consciousness"] = "SINGULAR"
|
|
1385
1393
|
elif m.is_conscious:
|
|
1386
1394
|
agent["consciousness"] = "CONSCIOUS"
|
|
1387
|
-
except Exception:
|
|
1388
|
-
|
|
1395
|
+
except Exception as exc:
|
|
1396
|
+
logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
|
|
1389
1397
|
identity_file = config.home / "identity" / "identity.json"
|
|
1390
1398
|
if identity_file.exists():
|
|
1391
1399
|
try:
|
|
1392
1400
|
ident = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
1393
1401
|
agent["name"] = ident.get("name", agent["name"])
|
|
1394
1402
|
agent["fingerprint"] = ident.get("fingerprint", agent["fingerprint"])
|
|
1395
|
-
except Exception:
|
|
1396
|
-
|
|
1403
|
+
except Exception as exc:
|
|
1404
|
+
logger.warning("Failed to read identity.json for capstone dashboard: %s", exc)
|
|
1397
1405
|
|
|
1398
1406
|
# ── Pillar status ─────────────────────────────────────────
|
|
1399
1407
|
pillars: dict = {}
|
|
@@ -1403,8 +1411,8 @@ class DaemonService:
|
|
|
1403
1411
|
k: v.value
|
|
1404
1412
|
for k, v in runtime.manifest.pillar_summary.items()
|
|
1405
1413
|
}
|
|
1406
|
-
except Exception:
|
|
1407
|
-
|
|
1414
|
+
except Exception as exc:
|
|
1415
|
+
logger.warning("Failed to read pillar summary from manifest: %s", exc)
|
|
1408
1416
|
|
|
1409
1417
|
# ── Memory stats ──────────────────────────────────────────
|
|
1410
1418
|
memory: dict = {}
|
|
@@ -1418,8 +1426,8 @@ class DaemonService:
|
|
|
1418
1426
|
"long_term": ms.long_term,
|
|
1419
1427
|
"status": ms.status.value,
|
|
1420
1428
|
}
|
|
1421
|
-
except Exception:
|
|
1422
|
-
|
|
1429
|
+
except Exception as exc:
|
|
1430
|
+
logger.warning("Failed to collect memory stats for dashboard: %s", exc)
|
|
1423
1431
|
|
|
1424
1432
|
# ── Coordination board ────────────────────────────────────
|
|
1425
1433
|
board: dict = {"summary": {}, "active": []}
|
|
@@ -1453,16 +1461,16 @@ class DaemonService:
|
|
|
1453
1461
|
},
|
|
1454
1462
|
"active": active_tasks,
|
|
1455
1463
|
}
|
|
1456
|
-
except Exception:
|
|
1457
|
-
|
|
1464
|
+
except Exception as exc:
|
|
1465
|
+
logger.warning("Failed to collect coordination board data for dashboard: %s", exc)
|
|
1458
1466
|
|
|
1459
1467
|
# ── Consciousness stats ───────────────────────────────────
|
|
1460
1468
|
c_stats: dict = {}
|
|
1461
1469
|
if consciousness:
|
|
1462
1470
|
try:
|
|
1463
1471
|
c_stats = dict(consciousness.stats)
|
|
1464
|
-
except Exception:
|
|
1465
|
-
|
|
1472
|
+
except Exception as exc:
|
|
1473
|
+
logger.warning("Failed to read consciousness stats for dashboard: %s", exc)
|
|
1466
1474
|
|
|
1467
1475
|
return {
|
|
1468
1476
|
"agent": agent,
|
|
@@ -1976,8 +1984,8 @@ class DaemonService:
|
|
|
1976
1984
|
entry["identity"] = json.loads(
|
|
1977
1985
|
identity_path.read_text(encoding="utf-8")
|
|
1978
1986
|
)
|
|
1979
|
-
except Exception:
|
|
1980
|
-
|
|
1987
|
+
except Exception as exc:
|
|
1988
|
+
logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
|
|
1981
1989
|
|
|
1982
1990
|
hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
|
|
1983
1991
|
if hb_path.exists():
|
|
@@ -1987,7 +1995,8 @@ class DaemonService:
|
|
|
1987
1995
|
hb["alive"] = alive
|
|
1988
1996
|
entry["heartbeat"] = hb
|
|
1989
1997
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
1990
|
-
except Exception:
|
|
1998
|
+
except Exception as exc:
|
|
1999
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
|
|
1991
2000
|
entry["status"] = "unknown"
|
|
1992
2001
|
else:
|
|
1993
2002
|
entry["status"] = "no_heartbeat"
|
|
@@ -2019,8 +2028,8 @@ class DaemonService:
|
|
|
2019
2028
|
entry["identity"] = json.loads(
|
|
2020
2029
|
identity_path.read_text(encoding="utf-8")
|
|
2021
2030
|
)
|
|
2022
|
-
except Exception:
|
|
2023
|
-
|
|
2031
|
+
except Exception as exc:
|
|
2032
|
+
logger.warning("Failed to read identity for agent %s: %s", name, exc)
|
|
2024
2033
|
|
|
2025
2034
|
hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
|
|
2026
2035
|
if hb_path.exists():
|
|
@@ -2030,8 +2039,8 @@ class DaemonService:
|
|
|
2030
2039
|
hb["alive"] = alive
|
|
2031
2040
|
entry["heartbeat"] = hb
|
|
2032
2041
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
2033
|
-
except Exception:
|
|
2034
|
-
|
|
2042
|
+
except Exception as exc:
|
|
2043
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
|
|
2035
2044
|
|
|
2036
2045
|
memory_dir = agent_dir / "memory"
|
|
2037
2046
|
if memory_dir.exists():
|
|
@@ -2054,8 +2063,8 @@ class DaemonService:
|
|
|
2054
2063
|
"message_count": len(msgs),
|
|
2055
2064
|
"last_message": msgs[-1].get("timestamp") if msgs else None,
|
|
2056
2065
|
})
|
|
2057
|
-
except Exception:
|
|
2058
|
-
|
|
2066
|
+
except Exception as exc:
|
|
2067
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2059
2068
|
entry["recent_conversations"] = conv_list
|
|
2060
2069
|
|
|
2061
2070
|
if consciousness:
|
|
@@ -2084,8 +2093,8 @@ class DaemonService:
|
|
|
2084
2093
|
"last_message_time": last_msg.get("timestamp") if msgs else None,
|
|
2085
2094
|
"last_message_preview": (last_content or "")[:120],
|
|
2086
2095
|
})
|
|
2087
|
-
except Exception:
|
|
2088
|
-
|
|
2096
|
+
except Exception as exc:
|
|
2097
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
2089
2098
|
self._json_response({"conversations": conversations})
|
|
2090
2099
|
|
|
2091
2100
|
# ── Conversations: single peer history ────────────────────
|
|
@@ -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,8 +266,8 @@ 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 = {}
|
|
@@ -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,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Claude Code Hooks — Auto-Save Memory
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
Run `skmemory register` to install hooks automatically.
|
|
6
|
+
|
|
7
|
+
This registers three hooks in `~/.claude/settings.json`:
|
|
8
|
+
|
|
9
|
+
### PreCompact
|
|
10
|
+
- **When**: Before Claude Code compacts conversation context
|
|
11
|
+
- **Does**: Saves a snapshot and journal entry to skmemory
|
|
12
|
+
- **Script**: `skmemory/hooks/pre-compact-save.sh`
|
|
13
|
+
|
|
14
|
+
### SessionEnd
|
|
15
|
+
- **When**: When a Claude Code session ends (logout, clear, exit)
|
|
16
|
+
- **Does**: Saves session-end snapshot and journal entry
|
|
17
|
+
- **Script**: `skmemory/hooks/session-end-save.sh`
|
|
18
|
+
|
|
19
|
+
### SessionStart (compact)
|
|
20
|
+
- **When**: After context compaction completes
|
|
21
|
+
- **Does**: Re-injects memory context (recent memories, seeds, journal) into the new context
|
|
22
|
+
- **Script**: `skmemory/hooks/post-compact-reinject.sh`
|
|
23
|
+
|
|
24
|
+
## How It Works
|
|
25
|
+
|
|
26
|
+
All hooks are agent-aware via `$SKCAPSTONE_AGENT` env var:
|
|
27
|
+
- `SKCAPSTONE_AGENT=lumina claude` → hooks save to Lumina's memory
|
|
28
|
+
- `SKCAPSTONE_AGENT=opus claude` → hooks save to Opus's memory
|
|
29
|
+
- Default (no env var): saves to `opus` agent
|
|
30
|
+
|
|
31
|
+
## Manual Verification
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Check hooks are registered
|
|
35
|
+
cat ~/.claude/settings.json | jq '.hooks'
|
|
36
|
+
|
|
37
|
+
# Test pre-compact hook
|
|
38
|
+
echo '{"session_id":"test","trigger":"manual","cwd":"."}' | /path/to/pre-compact-save.sh
|
|
39
|
+
|
|
40
|
+
# Re-register if needed
|
|
41
|
+
skmemory register
|
|
42
|
+
```
|
package/src/skcapstone/doctor.py
CHANGED
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import importlib
|
|
15
15
|
import json
|
|
16
|
+
import logging
|
|
16
17
|
import os
|
|
17
18
|
import shutil
|
|
18
19
|
import subprocess
|
|
@@ -20,6 +21,8 @@ from dataclasses import dataclass, field
|
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Optional
|
|
22
23
|
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
@dataclass
|
|
25
28
|
class Check:
|
|
@@ -613,8 +616,8 @@ def _check_versions() -> list[Check]:
|
|
|
613
616
|
category="packages",
|
|
614
617
|
)
|
|
615
618
|
)
|
|
616
|
-
except Exception:
|
|
617
|
-
|
|
619
|
+
except Exception as exc:
|
|
620
|
+
logger.warning("Version check failed (non-fatal): %s", exc)
|
|
618
621
|
|
|
619
622
|
return checks
|
|
620
623
|
|