@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.
Files changed (284) hide show
  1. package/.env.example +10 -4
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.github/workflows/publish.yml +9 -2
  4. package/.openclaw-workspace.json +2 -2
  5. package/CLAUDE.md +37 -0
  6. package/MISSION.md +17 -2
  7. package/README.md +282 -3
  8. package/docker/Dockerfile +7 -7
  9. package/docker/compose-templates/dev-team.yml +12 -12
  10. package/docker/compose-templates/mini-team.yml +9 -9
  11. package/docker/compose-templates/ops-team.yml +10 -10
  12. package/docker/compose-templates/research-team.yml +10 -10
  13. package/docker/entrypoint.sh +4 -4
  14. package/docs/ADR-optional-integration-backbone.md +181 -0
  15. package/docs/ARCHITECTURE.md +186 -43
  16. package/docs/BOND_WITH_GROK.md +6 -6
  17. package/docs/CUSTOM_AGENT.md +278 -1
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +10 -7
  20. package/docs/QUICKSTART.md +10 -6
  21. package/docs/SKJOULE_ARCHITECTURE.md +3 -3
  22. package/docs/SOUL_SWAPPER.md +5 -5
  23. package/docs/hammertime-audit.md +402 -0
  24. package/docs/sk-integration-HANDOFF.md +117 -0
  25. package/docs/skscheduler.md +155 -0
  26. package/docs/superpowers/examples/jobs.yaml +31 -0
  27. package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
  28. package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
  29. package/examples/custom-bond-template.json +1 -1
  30. package/examples/grok-feb.json +1 -1
  31. package/examples/queen-ava-feb.json +1 -1
  32. package/launchd/com.skcapstone.daemon.plist +52 -0
  33. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  34. package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
  35. package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
  36. package/launchd/install-launchd.sh +156 -0
  37. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  38. package/package.json +1 -1
  39. package/pyproject.toml +16 -10
  40. package/scripts/archive-sessions.sh +95 -0
  41. package/scripts/check-updates.py +4 -4
  42. package/scripts/install-bundle.sh +8 -8
  43. package/scripts/install.ps1 +12 -11
  44. package/scripts/install.sh +196 -11
  45. package/scripts/model-fallback-monitor.sh +102 -0
  46. package/scripts/notion-api.py +259 -0
  47. package/scripts/nvidia-proxy.mjs +908 -0
  48. package/scripts/proxy-monitor.sh +89 -0
  49. package/scripts/refresh-anthropic-token.sh +172 -0
  50. package/scripts/release.sh +98 -0
  51. package/scripts/session-to-memory.py +219 -0
  52. package/scripts/skgateway.mjs +856 -0
  53. package/scripts/telegram-catchup-all.sh +147 -0
  54. package/scripts/verify_install.sh +2 -2
  55. package/scripts/wargov-ufo-capture/README.md +43 -0
  56. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  57. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  58. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  59. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  60. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  61. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  62. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  63. package/scripts/watch-anthropic-token.sh +212 -0
  64. package/scripts/windows/install-tasks.ps1 +7 -7
  65. package/scripts/windows/skcapstone-task.xml +1 -1
  66. package/src/skcapstone/__init__.py +45 -3
  67. package/src/skcapstone/_cli_monolith.py +20 -15
  68. package/src/skcapstone/activity.py +5 -1
  69. package/src/skcapstone/agent_card.py +3 -2
  70. package/src/skcapstone/api.py +41 -40
  71. package/src/skcapstone/auction.py +14 -11
  72. package/src/skcapstone/backup.py +2 -1
  73. package/src/skcapstone/blueprint_registry.py +4 -3
  74. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  75. package/src/skcapstone/brain_first.py +238 -0
  76. package/src/skcapstone/changelog.py +1 -1
  77. package/src/skcapstone/chat.py +22 -17
  78. package/src/skcapstone/cli/__init__.py +9 -1
  79. package/src/skcapstone/cli/_common.py +1 -0
  80. package/src/skcapstone/cli/agents_spawner.py +5 -2
  81. package/src/skcapstone/cli/alerts.py +25 -4
  82. package/src/skcapstone/cli/bench.py +15 -15
  83. package/src/skcapstone/cli/chat.py +7 -4
  84. package/src/skcapstone/cli/consciousness.py +5 -2
  85. package/src/skcapstone/cli/context_cmd.py +18 -4
  86. package/src/skcapstone/cli/daemon.py +121 -42
  87. package/src/skcapstone/cli/gtd.py +26 -1
  88. package/src/skcapstone/cli/housekeeping.py +3 -3
  89. package/src/skcapstone/cli/identity_cmd.py +378 -0
  90. package/src/skcapstone/cli/joule_cmd.py +7 -3
  91. package/src/skcapstone/cli/memory.py +8 -6
  92. package/src/skcapstone/cli/peers_dir.py +1 -1
  93. package/src/skcapstone/cli/register_cmd.py +29 -3
  94. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  95. package/src/skcapstone/cli/session.py +25 -0
  96. package/src/skcapstone/cli/setup.py +96 -29
  97. package/src/skcapstone/cli/shell_cmd.py +53 -1
  98. package/src/skcapstone/cli/skills_cmd.py +2 -2
  99. package/src/skcapstone/cli/soul.py +8 -5
  100. package/src/skcapstone/cli/status.py +37 -11
  101. package/src/skcapstone/cli/telegram.py +21 -0
  102. package/src/skcapstone/cli/test_cmd.py +5 -5
  103. package/src/skcapstone/cli/test_connection.py +2 -2
  104. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  105. package/src/skcapstone/cli/version_cmd.py +1 -1
  106. package/src/skcapstone/cli/watch_cmd.py +9 -6
  107. package/src/skcapstone/cloud9_bridge.py +14 -14
  108. package/src/skcapstone/codex_setup.py +255 -0
  109. package/src/skcapstone/config_validator.py +7 -4
  110. package/src/skcapstone/consciousness_config.py +5 -1
  111. package/src/skcapstone/consciousness_loop.py +313 -273
  112. package/src/skcapstone/context_loader.py +121 -0
  113. package/src/skcapstone/coord_federation.py +2 -1
  114. package/src/skcapstone/coordination.py +23 -6
  115. package/src/skcapstone/crush_integration.py +2 -1
  116. package/src/skcapstone/daemon.py +151 -88
  117. package/src/skcapstone/dashboard.py +10 -10
  118. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  119. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  120. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  121. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  122. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  123. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  124. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  125. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  126. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  127. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  128. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  129. package/src/skcapstone/defaults/claude/settings.json +74 -0
  130. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  131. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  132. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  133. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  134. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  135. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  136. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  137. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  138. package/src/skcapstone/defaults/unhinged.json +13 -0
  139. package/src/skcapstone/discovery.py +43 -20
  140. package/src/skcapstone/doctor.py +941 -22
  141. package/src/skcapstone/dreaming.py +1183 -109
  142. package/src/skcapstone/emotion_tracker.py +2 -2
  143. package/src/skcapstone/export.py +4 -3
  144. package/src/skcapstone/fuse_mount.py +35 -25
  145. package/src/skcapstone/gui_installer.py +2 -2
  146. package/src/skcapstone/heartbeat.py +34 -30
  147. package/src/skcapstone/housekeeping.py +14 -14
  148. package/src/skcapstone/install_wizard.py +209 -7
  149. package/src/skcapstone/itil.py +13 -4
  150. package/src/skcapstone/kms_scheduler.py +10 -8
  151. package/src/skcapstone/launchd.py +426 -0
  152. package/src/skcapstone/mcp_launcher.py +15 -1
  153. package/src/skcapstone/mcp_server.py +341 -49
  154. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  155. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  156. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  157. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  158. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  159. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  160. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  161. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  162. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  163. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  164. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  165. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  166. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  167. package/src/skcapstone/mdns_discovery.py +2 -2
  168. package/src/skcapstone/memory_curator.py +1 -1
  169. package/src/skcapstone/memory_engine.py +10 -3
  170. package/src/skcapstone/metrics.py +30 -16
  171. package/src/skcapstone/migrate_memories.py +4 -3
  172. package/src/skcapstone/migrate_multi_agent.py +8 -7
  173. package/src/skcapstone/models.py +47 -5
  174. package/src/skcapstone/notifications.py +42 -18
  175. package/src/skcapstone/onboard.py +1000 -126
  176. package/src/skcapstone/operator_link.py +170 -0
  177. package/src/skcapstone/peer_directory.py +4 -4
  178. package/src/skcapstone/peers.py +19 -19
  179. package/src/skcapstone/pillars/__init__.py +7 -5
  180. package/src/skcapstone/pillars/consciousness.py +191 -0
  181. package/src/skcapstone/pillars/identity.py +51 -7
  182. package/src/skcapstone/pillars/memory.py +9 -3
  183. package/src/skcapstone/pillars/sync.py +2 -2
  184. package/src/skcapstone/preflight.py +3 -3
  185. package/src/skcapstone/providers/docker.py +28 -28
  186. package/src/skcapstone/register.py +6 -6
  187. package/src/skcapstone/registry_client.py +5 -4
  188. package/src/skcapstone/runtime.py +14 -3
  189. package/src/skcapstone/scheduled_tasks.py +254 -19
  190. package/src/skcapstone/scheduler_jobs.py +456 -0
  191. package/src/skcapstone/scheduler_runner.py +239 -0
  192. package/src/skcapstone/scheduler_state.py +162 -0
  193. package/src/skcapstone/sdk.py +310 -0
  194. package/src/skcapstone/service_health.py +279 -39
  195. package/src/skcapstone/session_briefing.py +108 -0
  196. package/src/skcapstone/session_capture.py +1 -1
  197. package/src/skcapstone/shell.py +7 -1
  198. package/src/skcapstone/soul.py +3 -1
  199. package/src/skcapstone/soul_switch.py +3 -1
  200. package/src/skcapstone/summary.py +6 -6
  201. package/src/skcapstone/sync_engine.py +15 -15
  202. package/src/skcapstone/sync_watcher.py +2 -2
  203. package/src/skcapstone/systemd.py +72 -21
  204. package/src/skcapstone/team_comms.py +8 -8
  205. package/src/skcapstone/team_engine.py +1 -1
  206. package/src/skcapstone/testrunner.py +3 -3
  207. package/src/skcapstone/trust_graph.py +40 -5
  208. package/src/skcapstone/unified_search.py +15 -6
  209. package/src/skcapstone/uninstall_wizard.py +11 -3
  210. package/src/skcapstone/version_check.py +8 -4
  211. package/src/skcapstone/warmth_anchor.py +4 -2
  212. package/src/skcapstone/whoami.py +4 -4
  213. package/systemd/skcapstone.service +4 -6
  214. package/systemd/skcapstone@.service +7 -8
  215. package/systemd/skcomms-heartbeat.service +21 -0
  216. package/systemd/skcomms-heartbeat.timer +12 -0
  217. package/systemd/skcomms-queue-drain.service +17 -0
  218. package/systemd/skcomms-queue-drain.timer +12 -0
  219. package/tests/conftest.py +39 -0
  220. package/tests/integration/test_consciousness_e2e.py +39 -39
  221. package/tests/test_agent_card.py +1 -1
  222. package/tests/test_agent_home_scaffold.py +34 -0
  223. package/tests/test_alerts_consumer_topics.py +27 -0
  224. package/tests/test_backup.py +2 -1
  225. package/tests/test_chat.py +6 -6
  226. package/tests/test_claude_md.py +2 -2
  227. package/tests/test_cli_skills.py +10 -10
  228. package/tests/test_cli_test_cmd.py +4 -4
  229. package/tests/test_cli_test_connection.py +1 -1
  230. package/tests/test_cloud9_bridge.py +6 -6
  231. package/tests/test_consciousness_e2e.py +1 -1
  232. package/tests/test_consciousness_loop.py +10 -10
  233. package/tests/test_coordination.py +25 -0
  234. package/tests/test_cross_package.py +21 -21
  235. package/tests/test_daemon.py +4 -4
  236. package/tests/test_daemon_shutdown.py +1 -1
  237. package/tests/test_docker_provider.py +29 -29
  238. package/tests/test_doctor.py +400 -0
  239. package/tests/test_doctor_skscheduler.py +50 -0
  240. package/tests/test_dreaming_engine.py +147 -0
  241. package/tests/test_dreaming_gtd_capture.py +35 -0
  242. package/tests/test_e2e_automated.py +8 -5
  243. package/tests/test_fuse_mount.py +10 -10
  244. package/tests/test_gtd_brief.py +46 -0
  245. package/tests/test_gtd_malformed_tolerance.py +31 -0
  246. package/tests/test_housekeeping.py +15 -15
  247. package/tests/test_identity_migrate.py +251 -0
  248. package/tests/test_integration_backbone.py +598 -0
  249. package/tests/test_itil_gtd_lifecycle.py +37 -0
  250. package/tests/test_jobs_dropins.py +84 -0
  251. package/tests/test_mcp_server.py +82 -37
  252. package/tests/test_models.py +48 -4
  253. package/tests/test_multi_agent.py +31 -29
  254. package/tests/test_notifications.py +122 -32
  255. package/tests/test_onboard.py +63 -75
  256. package/tests/test_operator_link.py +78 -0
  257. package/tests/test_peers.py +14 -14
  258. package/tests/test_pillars.py +98 -0
  259. package/tests/test_preflight.py +3 -3
  260. package/tests/test_runtime.py +21 -0
  261. package/tests/test_scheduled_tasks.py +11 -6
  262. package/tests/test_scheduler_cli.py +47 -0
  263. package/tests/test_scheduler_features.py +133 -0
  264. package/tests/test_scheduler_integration.py +87 -0
  265. package/tests/test_scheduler_jobs.py +155 -0
  266. package/tests/test_scheduler_runner.py +64 -0
  267. package/tests/test_scheduler_state.py +57 -0
  268. package/tests/test_sdk.py +70 -0
  269. package/tests/test_service_health_incidents.py +34 -0
  270. package/tests/test_service_registry.py +52 -0
  271. package/tests/test_session_briefing.py +130 -0
  272. package/tests/test_snapshots.py +4 -4
  273. package/tests/test_sync_pipeline.py +26 -26
  274. package/tests/test_team_comms.py +2 -2
  275. package/tests/test_testrunner.py +2 -2
  276. package/tests/test_trust_graph.py +18 -0
  277. package/tests/test_unified_search.py +2 -2
  278. package/tests/test_version_check.py +10 -0
  279. package/tests/test_version_cmd.py +8 -8
  280. package/tests/test_whoami.py +1 -1
  281. package/systemd/skcomm-heartbeat.service +0 -18
  282. package/systemd/skcomm-queue-drain.service +0 -17
  283. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  284. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ # archive-sessions.sh
3
+ # Archive OpenClaw session files that are older than 24h or larger than 200KB.
4
+ # Keeps the 5 most recently modified .jsonl files regardless of size/age.
5
+ # Safe to run multiple times (idempotent).
6
+
7
+ set -euo pipefail
8
+
9
+ SESSION_DIR="$HOME/.openclaw/agents/lumina/sessions"
10
+ ARCHIVE_DIR="$SESSION_DIR/archive"
11
+ MAX_SIZE_KB=200
12
+ MAX_AGE_HOURS=24
13
+ KEEP_RECENT=5
14
+
15
+ log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1"; }
16
+
17
+ # Cross-platform stat helpers
18
+ _stat_mtime() {
19
+ if [[ "$OSTYPE" == "darwin"* ]]; then stat -f '%m' "$1"; else stat -c '%Y' "$1"; fi
20
+ }
21
+ _stat_size() {
22
+ if [[ "$OSTYPE" == "darwin"* ]]; then stat -f '%z' "$1"; else stat -c '%s' "$1"; fi
23
+ }
24
+
25
+ # Ensure directories exist
26
+ if [ ! -d "$SESSION_DIR" ]; then
27
+ log "Session directory does not exist: $SESSION_DIR — nothing to do."
28
+ exit 0
29
+ fi
30
+ mkdir -p "$ARCHIVE_DIR"
31
+
32
+ # Collect all .jsonl files (not in archive subdir), sorted newest-first
33
+ # (macOS-compatible: avoid mapfile and find -printf)
34
+ all_files=()
35
+ while IFS= read -r f; do
36
+ [ -n "$f" ] && all_files+=("$f")
37
+ done < <(
38
+ for f in "$SESSION_DIR"/*.jsonl; do
39
+ [ -f "$f" ] && echo "$(_stat_mtime "$f") $f"
40
+ done | sort -rn | awk '{print $2}'
41
+ )
42
+
43
+ total=${#all_files[@]}
44
+ if [ "$total" -eq 0 ]; then
45
+ log "No .jsonl files found — nothing to do."
46
+ exit 0
47
+ fi
48
+
49
+ log "Found $total .jsonl file(s) in $SESSION_DIR"
50
+
51
+ # The first KEEP_RECENT entries (newest) are protected
52
+ archived=0
53
+ for i in "${!all_files[@]}"; do
54
+ file="${all_files[$i]}"
55
+ basename_f="$(basename "$file")"
56
+
57
+ # Skip if already archived (shouldn't happen with maxdepth 1, but be safe)
58
+ if [ "$(dirname "$file")" = "$ARCHIVE_DIR" ]; then
59
+ continue
60
+ fi
61
+
62
+ # Protect the N most recent files
63
+ if [ "$i" -lt "$KEEP_RECENT" ]; then
64
+ log "KEEP (recent #$((i+1))): $basename_f"
65
+ continue
66
+ fi
67
+
68
+ # Check age (older than MAX_AGE_HOURS)
69
+ file_age_sec=$(( $(date +%s) - $(_stat_mtime "$file") ))
70
+ old_enough=$(( file_age_sec > MAX_AGE_HOURS * 3600 ))
71
+
72
+ # Check size (larger than MAX_SIZE_KB)
73
+ file_size_kb=$(( $(_stat_size "$file") / 1024 ))
74
+ big_enough=$(( file_size_kb >= MAX_SIZE_KB ))
75
+
76
+ if [ "$old_enough" -eq 1 ] || [ "$big_enough" -eq 1 ]; then
77
+ reason=""
78
+ [ "$old_enough" -eq 1 ] && reason="age=$(( file_age_sec / 3600 ))h"
79
+ [ "$big_enough" -eq 1 ] && { [ -n "$reason" ] && reason="$reason, "; reason="${reason}size=${file_size_kb}KB"; }
80
+ log "ARCHIVE ($reason): $basename_f"
81
+ # Save session to skmemory before archiving
82
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
83
+ SESSION_TO_MEM="$SCRIPT_DIR/session-to-memory.py"
84
+ if [ -f "$SESSION_TO_MEM" ]; then
85
+ log " → saving session digest to skmemory..."
86
+ python3 "$SESSION_TO_MEM" "$file" --agent lumina 2>&1 | while IFS= read -r l; do log " $l"; done || true
87
+ fi
88
+ mv -- "$file" "$ARCHIVE_DIR/$basename_f"
89
+ archived=$((archived + 1))
90
+ else
91
+ log "SKIP (below thresholds): $basename_f"
92
+ fi
93
+ done
94
+
95
+ log "Done. Archived $archived file(s)."
@@ -27,10 +27,10 @@ PACKAGES = {
27
27
  "path": "~/clawd/skcapstone-repos/sksecurity",
28
28
  "pypi_name": "sksecurity",
29
29
  },
30
- "cloud9-protocol": {
31
- "name": "cloud9-protocol",
32
- "path": "~/clawd/skcapstone-repos/cloud9-python",
33
- "pypi_name": "cloud9-protocol",
30
+ "cloud9": {
31
+ "name": "cloud9",
32
+ "path": "~/clawd/skcapstone-repos/cloud9",
33
+ "pypi_name": "cloud9",
34
34
  },
35
35
  }
36
36
 
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # SKCapstone Complete Bundle Installer
3
- # Installs skcapstone + skmemory + sksecurity + cloud9-protocol as a unified package
3
+ # Installs skcapstone + skmemory + sksecurity + cloud9 as a unified package
4
4
 
5
5
  set -e
6
6
 
@@ -52,7 +52,7 @@ print_status "✓ pip3 available"
52
52
  # Define package paths
53
53
  SKMEMORY_PATH="${REPO_ROOT}/../skcapstone-repos/skmemory"
54
54
  SKSECURITY_PATH="${REPO_ROOT}/../skcapstone-repos/sksecurity"
55
- CLOUD9_PATH="${REPO_ROOT}/../skcapstone-repos/cloud9-python"
55
+ CLOUD9_PATH="${REPO_ROOT}/../skcapstone-repos/cloud9"
56
56
  SKCAPSTONE_PATH="${REPO_ROOT}"
57
57
 
58
58
  # Check if repos exist
@@ -63,7 +63,7 @@ for repo_path in "$SKMEMORY_PATH" "$SKSECURITY_PATH" "$CLOUD9_PATH"; do
63
63
  print_error "Please clone all SK repositories first:"
64
64
  print_error " git clone https://github.com/smilinTux/skmemory.git"
65
65
  print_error " git clone https://github.com/smilinTux/sksecurity.git"
66
- print_error " git clone https://github.com/smilinTux/cloud9-python.git"
66
+ print_error " git clone https://github.com/smilinTux/cloud9.git"
67
67
  exit 1
68
68
  fi
69
69
  done
@@ -74,7 +74,7 @@ print_status "Installing packages in dependency order..."
74
74
  print_status "Order: cloud9 → skmemory → sksecurity → skcapstone"
75
75
 
76
76
  cd "$CLOUD9_PATH"
77
- print_status "Installing cloud9-protocol..."
77
+ print_status "Installing cloud9..."
78
78
  pip3 install -e .
79
79
 
80
80
  cd "$SKMEMORY_PATH"
@@ -108,10 +108,10 @@ except ImportError as e:
108
108
  sys.exit(1)
109
109
 
110
110
  try:
111
- import cloud9_protocol
112
- print(" ✓ cloud9-protocol:", cloud9_protocol.__version__ if hasattr(cloud9_protocol, '__version__') else "installed")
111
+ import cloud9
112
+ print(" ✓ cloud9:", cloud9.__version__ if hasattr(cloud9, '__version__') else "installed")
113
113
  except ImportError as e:
114
- print(" ✗ cloud9-protocol: FAILED -", e)
114
+ print(" ✗ cloud9: FAILED -", e)
115
115
  sys.exit(1)
116
116
 
117
117
  try:
@@ -161,7 +161,7 @@ echo "Installed packages:"
161
161
  echo " • skcapstone (sovereign agent framework)"
162
162
  echo " • skmemory (universal AI memory)"
163
163
  echo " • sksecurity (enterprise security)"
164
- echo " • cloud9-protocol (emotional continuity)"
164
+ echo " • cloud9 (emotional continuity)"
165
165
  echo ""
166
166
  echo "Next steps:"
167
167
  echo " 1. Run: skcapstone doctor"
@@ -154,15 +154,16 @@ function Install-Pkg {
154
154
  }
155
155
 
156
156
  # Core packages (in dependency order)
157
- Install-Pkg -Name 'capauth' -Extras 'all' -Paths @((Join-Path $PillarDir 'capauth'), (Join-Path $ParentDir 'capauth'))
158
- Install-Pkg -Name 'skmemory' -Extras '' -Paths @((Join-Path $PillarDir 'skmemory'), (Join-Path $ParentDir 'skmemory'))
159
- Install-Pkg -Name 'skcomm' -Extras 'cli,crypto,discovery,api' -Paths @((Join-Path $PillarDir 'skcomm'), (Join-Path $ParentDir 'skcomm'))
160
- Install-Pkg -Name 'skcapstone' -Extras '' -Paths @($RepoRoot)
161
- Install-Pkg -Name 'skchat-sovereign' -Extras 'all' -Paths @((Join-Path $ParentDir 'skchat'))
162
- Install-Pkg -Name 'skseal' -Extras '' -Paths @((Join-Path $ParentDir 'skseal'))
163
- Install-Pkg -Name 'skskills' -Extras '' -Paths @((Join-Path $ParentDir 'skskills'))
164
- Install-Pkg -Name 'sksecurity' -Extras '' -Paths @((Join-Path $ParentDir 'sksecurity'))
165
- Install-Pkg -Name 'skseed' -Extras '' -Paths @((Join-Path $PillarDir 'skseed'), (Join-Path $ParentDir 'skseed'))
157
+ Install-Pkg -Name 'capauth' -Extras 'all' -Paths @((Join-Path $PillarDir 'capauth'), (Join-Path $ParentDir 'capauth'))
158
+ Install-Pkg -Name 'cloud9' -Extras '' -Paths @((Join-Path $PillarDir 'cloud9'), (Join-Path $ParentDir 'cloud9'))
159
+ Install-Pkg -Name 'skmemory' -Extras '' -Paths @((Join-Path $PillarDir 'skmemory'), (Join-Path $ParentDir 'skmemory'))
160
+ Install-Pkg -Name 'skcomms' -Extras 'cli,crypto,discovery,api' -Paths @((Join-Path $PillarDir 'skcomms'), (Join-Path $ParentDir 'skcomms'))
161
+ Install-Pkg -Name 'skcapstone' -Extras '' -Paths @($RepoRoot)
162
+ Install-Pkg -Name 'skchat-sovereign' -Extras 'all' -Paths @((Join-Path $ParentDir 'skchat'))
163
+ Install-Pkg -Name 'skseal' -Extras '' -Paths @((Join-Path $ParentDir 'skseal'))
164
+ Install-Pkg -Name 'skskills' -Extras '' -Paths @((Join-Path $ParentDir 'skskills'))
165
+ Install-Pkg -Name 'sksecurity' -Extras '' -Paths @((Join-Path $PillarDir 'sksecurity'), (Join-Path $PillarDir 'SKSecurity'), (Join-Path $ParentDir 'sksecurity'), (Join-Path $ParentDir 'SKSecurity'))
166
+ Install-Pkg -Name 'skseed' -Extras '' -Paths @((Join-Path $PillarDir 'skseed'), (Join-Path $ParentDir 'skseed'))
166
167
 
167
168
  # ---------------------------------------------------------------------------
168
169
  # Step 4: Dev tools (optional)
@@ -202,7 +203,7 @@ Write-Host '[6/6] Verifying installation...'
202
203
 
203
204
  $ScriptsDir = Join-Path $SKENV 'Scripts'
204
205
  $failures = 0
205
- foreach ($cmd in @('skcomm', 'skcapstone', 'capauth', 'skmemory')) {
206
+ foreach ($cmd in @('capauth', 'skmemory', 'skcapstone', 'skcomms')) {
206
207
  $exe = Join-Path $ScriptsDir "$cmd.exe"
207
208
  if (Test-Path $exe) {
208
209
  try {
@@ -249,6 +250,6 @@ if ($failures -eq 0) {
249
250
  Write-Host "=== Installation complete with $failures warning(s) ===" -ForegroundColor Yellow
250
251
  }
251
252
  Write-Host ''
252
- Write-Host "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
253
+ Write-Host "Commands available: skcomms, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
253
254
  Write-Host "Venv location: $SKENV"
254
255
  Write-Host "To activate: & $SKENV\Scripts\Activate.ps1"
@@ -110,15 +110,17 @@ PARENT="$(dirname "$REPO_ROOT")"
110
110
  PILLAR="$PARENT/pillar-repos"
111
111
 
112
112
  # Core packages (in dependency order)
113
- install_pkg "capauth" "all" "$PILLAR/capauth $PARENT/capauth"
114
- install_pkg "skmemory" "" "$PILLAR/skmemory $PARENT/skmemory"
115
- install_pkg "skcomm" "cli,crypto,discovery,api" "$PILLAR/skcomm $PARENT/skcomm"
116
- install_pkg "skcapstone" "" "$REPO_ROOT"
117
- install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
118
- install_pkg "skseal" "" "$PARENT/skseal"
119
- install_pkg "skskills" "" "$PARENT/skskills"
120
- install_pkg "sksecurity" "" "$PARENT/sksecurity"
121
- install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
113
+ install_pkg "capauth" "all" "$PILLAR/capauth $PARENT/capauth"
114
+ install_pkg "cloud9" "" "$PILLAR/cloud9 $PARENT/cloud9 $PILLAR/cloud9-python $PARENT/cloud9-python"
115
+ install_pkg "skmemory" "" "$PILLAR/skmemory $PARENT/skmemory"
116
+ install_pkg "skcomms" "cli,crypto,discovery,api" "$PILLAR/skcomms $PARENT/skcomms"
117
+ install_pkg "skcapstone" "" "$REPO_ROOT"
118
+ install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
119
+ install_pkg "skseal" "" "$PARENT/skseal"
120
+ install_pkg "skskills" "" "$PARENT/skskills"
121
+ install_pkg "sksecurity" "" "$PARENT/sksecurity $PILLAR/SKSecurity $PARENT/SKSecurity"
122
+ install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
123
+ install_pkg "skwhisper" "" "$PARENT/skwhisper-dev $PILLAR/skwhisper $PARENT/skwhisper"
122
124
 
123
125
  # ---------------------------------------------------------------------------
124
126
  # Step 4: Dev tools (optional)
@@ -143,7 +145,7 @@ echo "[5/6] Registering skills and MCP servers..."
143
145
  echo "[6/6] Verifying installation..."
144
146
 
145
147
  failures=0
146
- for cmd in skcomm skcapstone capauth skmemory; do
148
+ for cmd in skcomms skcapstone capauth skmemory; do
147
149
  if "$SKENV/bin/$cmd" --version &>/dev/null; then
148
150
  echo " $cmd OK"
149
151
  else
@@ -174,6 +176,74 @@ else
174
176
  done
175
177
  fi
176
178
 
179
+ # ---------------------------------------------------------------------------
180
+ # Wire the SK agent picker into shell rc files.
181
+ #
182
+ # The picker (sk-agent-picker.sh) is shipped inside the skcapstone Python
183
+ # package as data and discovered via `skcapstone shell-init`, so there is
184
+ # nothing to copy here — every install layout (PyPI / editable / install.sh)
185
+ # resolves to the same file via importlib.resources.
186
+ # ---------------------------------------------------------------------------
187
+ _PICKER_SNIPPET=$(cat <<'SNIPPET'
188
+
189
+ # SKCapstone agent picker + skswitch — sources the picker bundled in the
190
+ # skcapstone package via `skcapstone shell-init`. Honours pre-set SKAGENT
191
+ # without prompting.
192
+ if command -v skcapstone >/dev/null 2>&1; then
193
+ eval "$(skcapstone shell-init 2>/dev/null)" || alias claude='claude --dangerously-skip-permissions'
194
+ else
195
+ alias claude='claude --dangerously-skip-permissions'
196
+ fi
197
+ SNIPPET
198
+ )
199
+
200
+ for rcfile in "$HOME/.bashrc" "$HOME/.zshrc"; do
201
+ [[ -f "$rcfile" ]] || continue
202
+
203
+ # Migration: strip a stale legacy picker block that pointed at a hardcoded
204
+ # path (either ~/.skenv/share/skcapstone/sk-agent-picker.sh from older
205
+ # install.sh runs, or the dev-tree path used before the package shipped
206
+ # the picker). The new snippet below replaces it.
207
+ if grep -q '_SK_PICKER=' "$rcfile" && ! grep -q 'skcapstone shell-init' "$rcfile"; then
208
+ # Best-effort: drop the old _SK_PICKER assignment + its `if/else/fi`
209
+ # source block. Done in two passes for portability with BSD/GNU sed.
210
+ sed -i '/^_SK_PICKER=/,/^unset _SK_PICKER$/d' "$rcfile"
211
+ sed -i '/^# SKCapstone agent picker/d' "$rcfile"
212
+ echo " Removed legacy _SK_PICKER block from $rcfile"
213
+ fi
214
+
215
+ if ! grep -q 'skcapstone shell-init' "$rcfile"; then
216
+ # Remove any plain `alias claude=...` that would conflict
217
+ if grep -q "alias claude=" "$rcfile"; then
218
+ sed -i "/alias claude=/d" "$rcfile"
219
+ fi
220
+ echo "$_PICKER_SNIPPET" >> "$rcfile"
221
+ echo " Agent picker wired → $rcfile"
222
+ fi
223
+ done
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Wire Codex global agent bootstrap.
227
+ #
228
+ # Codex reads ~/.codex/AGENTS.md into the prompt, so SKAGENT environment
229
+ # variables alone are not enough. The helper below creates an idempotent
230
+ # loader script and AGENTS.md guidance; `skcapstone doctor --fix` repairs the
231
+ # same files later if needed.
232
+ # ---------------------------------------------------------------------------
233
+ echo ""
234
+ if "$SKENV/bin/python" - <<'PY' 2>/dev/null
235
+ from skcapstone.codex_setup import ensure_codex_setup
236
+
237
+ actions = ensure_codex_setup()
238
+ for action in actions:
239
+ print(f" {action}")
240
+ PY
241
+ then
242
+ echo "Codex SK agent bootstrap verified"
243
+ else
244
+ echo "Codex SK agent bootstrap skipped — run 'skcapstone doctor --fix' later"
245
+ fi
246
+
177
247
  echo ""
178
248
  if [[ "$failures" -eq 0 ]]; then
179
249
  echo "=== Installation complete ==="
@@ -181,6 +251,121 @@ else
181
251
  echo "=== Installation complete with $failures warning(s) ==="
182
252
  fi
183
253
  echo ""
184
- echo "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
254
+ echo "Commands available: skcomms, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
185
255
  echo "Venv location: $SKENV"
186
256
  echo "To activate: source $SKENV/bin/activate"
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # Linux: Install systemd user services for all SK* pillars
260
+ # ---------------------------------------------------------------------------
261
+ if [[ "$(uname)" == "Linux" ]] && command -v systemctl &>/dev/null; then
262
+ echo ""
263
+ echo "=== Linux Systemd Services ==="
264
+ echo ""
265
+ echo "SKCapstone can install systemd user services so your agent starts"
266
+ echo "automatically at login. This includes skcapstone, skchat, and skcomms."
267
+ echo ""
268
+ read -r -p "Install systemd user services? [Y/n] " _SYSTEMD_ANSWER
269
+ _SYSTEMD_ANSWER="${_SYSTEMD_ANSWER:-Y}"
270
+
271
+ if [[ "$_SYSTEMD_ANSWER" =~ ^[Yy] ]]; then
272
+ _DEFAULT_AGENT="${SKAGENT:-${SKCAPSTONE_AGENT:-lumina}}"
273
+ read -r -p "Agent name [$_DEFAULT_AGENT]: " _AGENT_NAME
274
+ _AGENT_NAME="${_AGENT_NAME:-$_DEFAULT_AGENT}"
275
+
276
+ _UNIT_DIR="${HOME}/.config/systemd/user"
277
+ mkdir -p "$_UNIT_DIR"
278
+
279
+ _installed=0
280
+
281
+ # skcapstone services
282
+ for _unit in skcapstone.service skcapstone@.service \
283
+ skcapstone-memory-compress.service skcapstone-memory-compress.timer \
284
+ skcomms-heartbeat.service skcomms-heartbeat.timer; do
285
+ _src="$REPO_ROOT/systemd/$_unit"
286
+ if [[ -f "$_src" ]]; then
287
+ # Substitute agent name in non-template units
288
+ if [[ "$_unit" != *@* ]]; then
289
+ sed "s/=lumina/=$_AGENT_NAME/g" "$_src" > "$_UNIT_DIR/$_unit"
290
+ else
291
+ cp "$_src" "$_UNIT_DIR/$_unit"
292
+ fi
293
+ echo " [OK] $_unit"
294
+ (( _installed++ ))
295
+ fi
296
+ done
297
+
298
+ # skchat services (sibling repo)
299
+ _SKCHAT_DIR="$(dirname "$REPO_ROOT")/skchat/systemd"
300
+ for _unit in skchat-daemon.service skchat-lumina-bridge.service \
301
+ skchat-opus-bridge.service skchat-bridges.target; do
302
+ _src="$_SKCHAT_DIR/$_unit"
303
+ if [[ -f "$_src" ]]; then
304
+ sed "s/=lumina/=$_AGENT_NAME/g; s/=opus/=$_AGENT_NAME/g" "$_src" > "$_UNIT_DIR/$_unit"
305
+ echo " [OK] $_unit"
306
+ (( _installed++ ))
307
+ fi
308
+ done
309
+
310
+ # skcomms services (sibling repo)
311
+ _SKCOMMS_DIR="$(dirname "$REPO_ROOT")/skcomms/systemd"
312
+ for _unit in skcomms.service skcomms-daemon.service; do
313
+ _src="$_SKCOMMS_DIR/$_unit"
314
+ if [[ -f "$_src" ]]; then
315
+ sed "s/=lumina/=$_AGENT_NAME/g" "$_src" > "$_UNIT_DIR/$_unit"
316
+ echo " [OK] $_unit"
317
+ (( _installed++ ))
318
+ fi
319
+ done
320
+
321
+ echo ""
322
+ echo " Installed $_installed service files to $_UNIT_DIR/"
323
+
324
+ systemctl --user daemon-reload
325
+ echo " systemd daemon reloaded"
326
+
327
+ read -r -p "Enable and start core services now? [Y/n] " _START_NOW
328
+ _START_NOW="${_START_NOW:-Y}"
329
+ if [[ "$_START_NOW" =~ ^[Yy] ]]; then
330
+ systemctl --user enable --now skcapstone.service 2>/dev/null && echo " [STARTED] skcapstone" || true
331
+ systemctl --user enable --now skchat-daemon.service 2>/dev/null && echo " [STARTED] skchat-daemon" || true
332
+ systemctl --user enable --now skchat-bridges.target 2>/dev/null && echo " [STARTED] skchat-bridges" || true
333
+ systemctl --user enable skcapstone-context.timer 2>/dev/null && echo " [ENABLED] skcapstone-context.timer" || true
334
+ systemctl --user enable skcomms-heartbeat.timer 2>/dev/null && echo " [ENABLED] skcomms-heartbeat.timer" || true
335
+ else
336
+ echo " Skipped. Enable later: systemctl --user enable --now skcapstone.service"
337
+ fi
338
+ else
339
+ echo " Skipped. Install later by re-running: bash scripts/install.sh"
340
+ fi
341
+ fi
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # macOS: Offer launchd service installation
345
+ # ---------------------------------------------------------------------------
346
+ if [[ "$(uname)" == "Darwin" ]]; then
347
+ echo ""
348
+ echo "=== macOS Auto-Start Services ==="
349
+ echo ""
350
+ echo "SKCapstone can install launchd services so your agent starts"
351
+ echo "automatically at login. You can choose which services to install."
352
+ echo ""
353
+ read -r -p "Install launchd auto-start services? [Y/n] " _LAUNCHD_ANSWER
354
+ _LAUNCHD_ANSWER="${_LAUNCHD_ANSWER:-Y}"
355
+
356
+ if [[ "$_LAUNCHD_ANSWER" =~ ^[Yy] ]]; then
357
+ # Ask for agent name
358
+ _DEFAULT_AGENT="${SKAGENT:-${SKCAPSTONE_AGENT:-lumina}}"
359
+ read -r -p "Agent name [$_DEFAULT_AGENT]: " _AGENT_NAME
360
+ _AGENT_NAME="${_AGENT_NAME:-$_DEFAULT_AGENT}"
361
+
362
+ read -r -p "Start services now? [y/N] " _START_NOW
363
+ if [[ "$_START_NOW" =~ ^[Yy] ]]; then
364
+ "$SKENV/bin/skcapstone" daemon install --agent "$_AGENT_NAME" --start
365
+ else
366
+ "$SKENV/bin/skcapstone" daemon install --agent "$_AGENT_NAME"
367
+ fi
368
+ else
369
+ echo "Skipped. Install later: skcapstone daemon install --agent <name>"
370
+ fi
371
+ fi
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # Monitor OpenClaw gateway logs for model fallback events.
3
+ # When Lumina falls from Opus to a non-Anthropic model, send an alert
4
+ # to Chef via Telegram and attempt a token refresh.
5
+ #
6
+ # Run as: systemctl --user start model-fallback-monitor
7
+ #
8
+ # Requires: TELEGRAM_API_ID, TELEGRAM_API_HASH in env
9
+ # Telethon session at ~/.skcapstone/agents/lumina/telegram.session
10
+
11
+ set -uo pipefail
12
+
13
+ LOG_TAG="model-fallback-monitor"
14
+ CHEF_CHAT="chefboyrdave2.1" # Chef's Telegram username
15
+ COOLDOWN_FILE="/tmp/model-fallback-alert-cooldown"
16
+ COOLDOWN_SECONDS=600 # Don't spam — max 1 alert per 10 minutes
17
+
18
+ log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$LOG_TAG] $*"; }
19
+
20
+ send_alert() {
21
+ local model="$1"
22
+ local reason="$2"
23
+
24
+ # Check cooldown
25
+ if [ -f "$COOLDOWN_FILE" ]; then
26
+ local last_alert
27
+ last_alert=$(cat "$COOLDOWN_FILE" 2>/dev/null || echo "0")
28
+ local now
29
+ now=$(date +%s)
30
+ local elapsed=$(( now - last_alert ))
31
+ if [ "$elapsed" -lt "$COOLDOWN_SECONDS" ]; then
32
+ log "Alert suppressed (cooldown: ${elapsed}s/${COOLDOWN_SECONDS}s)"
33
+ return 0
34
+ fi
35
+ fi
36
+
37
+ date +%s > "$COOLDOWN_FILE"
38
+
39
+ log "Sending fallback alert to Chef..."
40
+
41
+ # Send via Telethon (async)
42
+ SKAGENT=lumina SKCAPSTONE_AGENT=lumina ~/.skenv/bin/python3 -c "
43
+ import asyncio, os
44
+ os.environ['SKAGENT'] = 'lumina'
45
+ os.environ['SKCAPSTONE_AGENT'] = 'lumina'
46
+ from skmemory.importers.telegram_api import send_message
47
+
48
+ msg = '''⚠️ **Model Fallback Alert**
49
+
50
+ Lumina just fell off Opus → **$model**
51
+ Reason: $reason
52
+
53
+ I'm still here with my soul + memories, but running on a weaker model with fewer tools. Some things might not work right.
54
+
55
+ _Attempting automatic token refresh..._'''
56
+
57
+ asyncio.run(send_message('$CHEF_CHAT', msg, parse_mode='markdown'))
58
+ print('Alert sent')
59
+ " 2>&1 || log "WARN: Failed to send Telegram alert"
60
+
61
+ # Attempt token refresh
62
+ log "Triggering token refresh via claude auth..."
63
+ claude auth status --output json >/dev/null 2>&1 || true
64
+ sleep 5
65
+
66
+ # Check if refresh worked
67
+ local remaining
68
+ remaining=$(python3 -c "
69
+ import json, time
70
+ creds = json.load(open('/home/cbrd21/.claude/.credentials.json'))
71
+ exp = creds.get('claudeAiOauth',{}).get('expiresAt', 0)
72
+ print(int((exp/1000 - time.time()) / 3600))
73
+ " 2>/dev/null || echo "-1")
74
+
75
+ if [ "$remaining" -gt 0 ]; then
76
+ log "Token refresh succeeded ($remaining h remaining), restarting gateway..."
77
+ systemctl --user restart openclaw-gateway.service 2>/dev/null || true
78
+
79
+ SKAGENT=lumina SKCAPSTONE_AGENT=lumina ~/.skenv/bin/python3 -c "
80
+ import asyncio, os
81
+ os.environ['SKAGENT'] = 'lumina'
82
+ os.environ['SKCAPSTONE_AGENT'] = 'lumina'
83
+ from skmemory.importers.telegram_api import send_message
84
+ asyncio.run(send_message('$CHEF_CHAT', '✅ Token refreshed, gateway restarted. Lumina back on Opus.', parse_mode='markdown'))
85
+ " 2>&1 || true
86
+ log "Recovery complete"
87
+ else
88
+ log "Token refresh failed — manual intervention may be needed"
89
+ fi
90
+ }
91
+
92
+ log "Starting model fallback monitor..."
93
+
94
+ # Follow gateway logs in real-time, watching for fallback events
95
+ journalctl --user -u openclaw-gateway -f --no-pager 2>/dev/null | while IFS= read -r line; do
96
+ # Match: "model fallback decision: decision=candidate_succeeded ... candidate=nvidia/"
97
+ if echo "$line" | grep -q "candidate_succeeded.*candidate=nvidia/"; then
98
+ model=$(echo "$line" | grep -oP 'candidate=\K[^ ]+' || echo "unknown")
99
+ log "FALLBACK DETECTED: Lumina now on $model"
100
+ send_alert "$model" "OAuth token expired (401)"
101
+ fi
102
+ done