@smilintux/skcapstone 0.10.0 → 0.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) 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 +123 -30
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +7 -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.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
  33. package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
  34. package/launchd/install-launchd.sh +6 -6
  35. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  36. package/package.json +1 -1
  37. package/pyproject.toml +16 -10
  38. package/scripts/archive-sessions.sh +7 -0
  39. package/scripts/check-updates.py +4 -4
  40. package/scripts/install-bundle.sh +8 -8
  41. package/scripts/install.ps1 +12 -11
  42. package/scripts/install.sh +159 -5
  43. package/scripts/model-fallback-monitor.sh +102 -0
  44. package/scripts/nvidia-proxy.mjs +78 -26
  45. package/scripts/refresh-anthropic-token.sh +172 -0
  46. package/scripts/release.sh +98 -0
  47. package/scripts/session-to-memory.py +219 -0
  48. package/scripts/skgateway.mjs +3 -3
  49. package/scripts/telegram-catchup-all.sh +12 -1
  50. package/scripts/verify_install.sh +2 -2
  51. package/scripts/wargov-ufo-capture/README.md +43 -0
  52. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  53. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  54. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  55. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  56. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  57. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  58. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  59. package/scripts/watch-anthropic-token.sh +212 -0
  60. package/scripts/windows/install-tasks.ps1 +7 -7
  61. package/scripts/windows/skcapstone-task.xml +1 -1
  62. package/src/skcapstone/__init__.py +45 -3
  63. package/src/skcapstone/_cli_monolith.py +20 -15
  64. package/src/skcapstone/activity.py +5 -1
  65. package/src/skcapstone/agent_card.py +3 -2
  66. package/src/skcapstone/api.py +41 -40
  67. package/src/skcapstone/auction.py +14 -11
  68. package/src/skcapstone/backup.py +2 -1
  69. package/src/skcapstone/blueprint_registry.py +4 -3
  70. package/src/skcapstone/brain_first.py +238 -0
  71. package/src/skcapstone/changelog.py +1 -1
  72. package/src/skcapstone/chat.py +22 -17
  73. package/src/skcapstone/cli/__init__.py +9 -1
  74. package/src/skcapstone/cli/_common.py +1 -0
  75. package/src/skcapstone/cli/agents_spawner.py +5 -2
  76. package/src/skcapstone/cli/alerts.py +25 -4
  77. package/src/skcapstone/cli/bench.py +15 -15
  78. package/src/skcapstone/cli/chat.py +7 -4
  79. package/src/skcapstone/cli/consciousness.py +5 -2
  80. package/src/skcapstone/cli/context_cmd.py +18 -4
  81. package/src/skcapstone/cli/daemon.py +11 -7
  82. package/src/skcapstone/cli/gtd.py +26 -1
  83. package/src/skcapstone/cli/housekeeping.py +3 -3
  84. package/src/skcapstone/cli/identity_cmd.py +378 -0
  85. package/src/skcapstone/cli/joule_cmd.py +7 -3
  86. package/src/skcapstone/cli/memory.py +8 -6
  87. package/src/skcapstone/cli/peers_dir.py +1 -1
  88. package/src/skcapstone/cli/register_cmd.py +29 -3
  89. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  90. package/src/skcapstone/cli/session.py +25 -0
  91. package/src/skcapstone/cli/setup.py +96 -29
  92. package/src/skcapstone/cli/shell_cmd.py +53 -1
  93. package/src/skcapstone/cli/skills_cmd.py +2 -2
  94. package/src/skcapstone/cli/soul.py +8 -5
  95. package/src/skcapstone/cli/status.py +37 -11
  96. package/src/skcapstone/cli/telegram.py +21 -0
  97. package/src/skcapstone/cli/test_cmd.py +5 -5
  98. package/src/skcapstone/cli/test_connection.py +2 -2
  99. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  100. package/src/skcapstone/cli/version_cmd.py +1 -1
  101. package/src/skcapstone/cli/watch_cmd.py +9 -6
  102. package/src/skcapstone/cloud9_bridge.py +14 -14
  103. package/src/skcapstone/codex_setup.py +255 -0
  104. package/src/skcapstone/config_validator.py +7 -4
  105. package/src/skcapstone/consciousness_config.py +5 -1
  106. package/src/skcapstone/consciousness_loop.py +313 -273
  107. package/src/skcapstone/context_loader.py +121 -0
  108. package/src/skcapstone/coord_federation.py +2 -1
  109. package/src/skcapstone/coordination.py +23 -6
  110. package/src/skcapstone/crush_integration.py +2 -1
  111. package/src/skcapstone/daemon.py +132 -77
  112. package/src/skcapstone/dashboard.py +10 -10
  113. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  114. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  115. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  116. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  117. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  118. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  119. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  120. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  121. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  122. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  123. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  124. package/src/skcapstone/defaults/claude/settings.json +74 -0
  125. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  126. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  127. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  128. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  129. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  130. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  131. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  132. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  133. package/src/skcapstone/defaults/unhinged.json +13 -0
  134. package/src/skcapstone/discovery.py +43 -20
  135. package/src/skcapstone/doctor.py +941 -22
  136. package/src/skcapstone/dreaming.py +1183 -109
  137. package/src/skcapstone/emotion_tracker.py +2 -2
  138. package/src/skcapstone/export.py +4 -3
  139. package/src/skcapstone/fuse_mount.py +14 -12
  140. package/src/skcapstone/gui_installer.py +2 -2
  141. package/src/skcapstone/heartbeat.py +1 -1
  142. package/src/skcapstone/housekeeping.py +14 -14
  143. package/src/skcapstone/install_wizard.py +209 -7
  144. package/src/skcapstone/itil.py +13 -4
  145. package/src/skcapstone/kms_scheduler.py +10 -8
  146. package/src/skcapstone/launchd.py +19 -19
  147. package/src/skcapstone/mcp_launcher.py +15 -1
  148. package/src/skcapstone/mcp_server.py +83 -49
  149. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  150. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  151. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  152. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  153. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  154. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  155. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  156. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  157. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  158. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  159. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  160. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  161. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  162. package/src/skcapstone/mdns_discovery.py +2 -2
  163. package/src/skcapstone/memory_curator.py +1 -1
  164. package/src/skcapstone/memory_engine.py +10 -3
  165. package/src/skcapstone/metrics.py +30 -16
  166. package/src/skcapstone/migrate_memories.py +4 -3
  167. package/src/skcapstone/migrate_multi_agent.py +8 -7
  168. package/src/skcapstone/models.py +47 -5
  169. package/src/skcapstone/notifications.py +42 -18
  170. package/src/skcapstone/onboard.py +875 -121
  171. package/src/skcapstone/operator_link.py +170 -0
  172. package/src/skcapstone/peer_directory.py +4 -4
  173. package/src/skcapstone/peers.py +19 -19
  174. package/src/skcapstone/pillars/__init__.py +7 -5
  175. package/src/skcapstone/pillars/consciousness.py +191 -0
  176. package/src/skcapstone/pillars/identity.py +51 -7
  177. package/src/skcapstone/pillars/memory.py +9 -3
  178. package/src/skcapstone/pillars/sync.py +2 -2
  179. package/src/skcapstone/preflight.py +3 -3
  180. package/src/skcapstone/providers/docker.py +28 -28
  181. package/src/skcapstone/register.py +6 -6
  182. package/src/skcapstone/registry_client.py +5 -4
  183. package/src/skcapstone/runtime.py +14 -3
  184. package/src/skcapstone/scheduled_tasks.py +254 -19
  185. package/src/skcapstone/scheduler_jobs.py +456 -0
  186. package/src/skcapstone/scheduler_runner.py +239 -0
  187. package/src/skcapstone/scheduler_state.py +162 -0
  188. package/src/skcapstone/sdk.py +310 -0
  189. package/src/skcapstone/service_health.py +279 -39
  190. package/src/skcapstone/session_briefing.py +108 -0
  191. package/src/skcapstone/session_capture.py +1 -1
  192. package/src/skcapstone/shell.py +7 -1
  193. package/src/skcapstone/soul.py +3 -1
  194. package/src/skcapstone/soul_switch.py +3 -1
  195. package/src/skcapstone/summary.py +6 -6
  196. package/src/skcapstone/sync_engine.py +15 -15
  197. package/src/skcapstone/sync_watcher.py +2 -2
  198. package/src/skcapstone/systemd.py +55 -21
  199. package/src/skcapstone/team_comms.py +8 -8
  200. package/src/skcapstone/team_engine.py +1 -1
  201. package/src/skcapstone/testrunner.py +3 -3
  202. package/src/skcapstone/trust_graph.py +40 -5
  203. package/src/skcapstone/unified_search.py +15 -6
  204. package/src/skcapstone/uninstall_wizard.py +11 -3
  205. package/src/skcapstone/version_check.py +8 -4
  206. package/src/skcapstone/warmth_anchor.py +4 -2
  207. package/src/skcapstone/whoami.py +4 -4
  208. package/systemd/skcapstone.service +4 -6
  209. package/systemd/skcapstone@.service +7 -8
  210. package/systemd/skcomms-heartbeat.service +21 -0
  211. package/systemd/skcomms-heartbeat.timer +12 -0
  212. package/systemd/skcomms-queue-drain.service +17 -0
  213. package/systemd/skcomms-queue-drain.timer +12 -0
  214. package/tests/conftest.py +39 -0
  215. package/tests/integration/test_consciousness_e2e.py +39 -39
  216. package/tests/test_agent_card.py +1 -1
  217. package/tests/test_agent_home_scaffold.py +34 -0
  218. package/tests/test_alerts_consumer_topics.py +27 -0
  219. package/tests/test_backup.py +2 -1
  220. package/tests/test_chat.py +6 -6
  221. package/tests/test_claude_md.py +2 -2
  222. package/tests/test_cli_skills.py +10 -10
  223. package/tests/test_cli_test_cmd.py +4 -4
  224. package/tests/test_cli_test_connection.py +1 -1
  225. package/tests/test_cloud9_bridge.py +6 -6
  226. package/tests/test_consciousness_e2e.py +1 -1
  227. package/tests/test_consciousness_loop.py +10 -10
  228. package/tests/test_coordination.py +25 -0
  229. package/tests/test_cross_package.py +21 -21
  230. package/tests/test_daemon.py +4 -4
  231. package/tests/test_daemon_shutdown.py +1 -1
  232. package/tests/test_docker_provider.py +29 -29
  233. package/tests/test_doctor.py +400 -0
  234. package/tests/test_doctor_skscheduler.py +50 -0
  235. package/tests/test_dreaming_engine.py +147 -0
  236. package/tests/test_dreaming_gtd_capture.py +35 -0
  237. package/tests/test_e2e_automated.py +8 -5
  238. package/tests/test_fuse_mount.py +10 -10
  239. package/tests/test_gtd_brief.py +46 -0
  240. package/tests/test_gtd_malformed_tolerance.py +31 -0
  241. package/tests/test_housekeeping.py +15 -15
  242. package/tests/test_identity_migrate.py +251 -0
  243. package/tests/test_integration_backbone.py +598 -0
  244. package/tests/test_itil_gtd_lifecycle.py +37 -0
  245. package/tests/test_jobs_dropins.py +84 -0
  246. package/tests/test_mcp_server.py +82 -37
  247. package/tests/test_models.py +48 -4
  248. package/tests/test_multi_agent.py +31 -29
  249. package/tests/test_notifications.py +122 -32
  250. package/tests/test_onboard.py +63 -75
  251. package/tests/test_operator_link.py +78 -0
  252. package/tests/test_peers.py +14 -14
  253. package/tests/test_pillars.py +98 -0
  254. package/tests/test_preflight.py +3 -3
  255. package/tests/test_runtime.py +21 -0
  256. package/tests/test_scheduled_tasks.py +11 -6
  257. package/tests/test_scheduler_cli.py +47 -0
  258. package/tests/test_scheduler_features.py +133 -0
  259. package/tests/test_scheduler_integration.py +87 -0
  260. package/tests/test_scheduler_jobs.py +155 -0
  261. package/tests/test_scheduler_runner.py +64 -0
  262. package/tests/test_scheduler_state.py +57 -0
  263. package/tests/test_sdk.py +70 -0
  264. package/tests/test_service_health_incidents.py +34 -0
  265. package/tests/test_service_registry.py +52 -0
  266. package/tests/test_session_briefing.py +130 -0
  267. package/tests/test_snapshots.py +4 -4
  268. package/tests/test_sync_pipeline.py +26 -26
  269. package/tests/test_team_comms.py +2 -2
  270. package/tests/test_testrunner.py +2 -2
  271. package/tests/test_trust_graph.py +18 -0
  272. package/tests/test_unified_search.py +2 -2
  273. package/tests/test_version_check.py +10 -0
  274. package/tests/test_version_cmd.py +8 -8
  275. package/tests/test_whoami.py +1 -1
  276. package/systemd/skcomm-heartbeat.service +0 -18
  277. package/systemd/skcomm-queue-drain.service +0 -17
  278. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  279. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -111,15 +111,16 @@ PILLAR="$PARENT/pillar-repos"
111
111
 
112
112
  # Core packages (in dependency order)
113
113
  install_pkg "capauth" "all" "$PILLAR/capauth $PARENT/capauth"
114
- install_pkg "cloud9-protocol" "" "$PILLAR/cloud9 $PARENT/cloud9"
114
+ install_pkg "cloud9" "" "$PILLAR/cloud9 $PARENT/cloud9 $PILLAR/cloud9-python $PARENT/cloud9-python"
115
115
  install_pkg "skmemory" "" "$PILLAR/skmemory $PARENT/skmemory"
116
- install_pkg "skcomm" "cli,crypto,discovery,api" "$PILLAR/skcomm $PARENT/skcomm"
116
+ install_pkg "skcomms" "cli,crypto,discovery,api" "$PILLAR/skcomms $PARENT/skcomms"
117
117
  install_pkg "skcapstone" "" "$REPO_ROOT"
118
118
  install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
119
119
  install_pkg "skseal" "" "$PARENT/skseal"
120
120
  install_pkg "skskills" "" "$PARENT/skskills"
121
121
  install_pkg "sksecurity" "" "$PARENT/sksecurity $PILLAR/SKSecurity $PARENT/SKSecurity"
122
122
  install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
123
+ install_pkg "skwhisper" "" "$PARENT/skwhisper-dev $PILLAR/skwhisper $PARENT/skwhisper"
123
124
 
124
125
  # ---------------------------------------------------------------------------
125
126
  # Step 4: Dev tools (optional)
@@ -144,7 +145,7 @@ echo "[5/6] Registering skills and MCP servers..."
144
145
  echo "[6/6] Verifying installation..."
145
146
 
146
147
  failures=0
147
- for cmd in skcomm skcapstone capauth skmemory; do
148
+ for cmd in skcomms skcapstone capauth skmemory; do
148
149
  if "$SKENV/bin/$cmd" --version &>/dev/null; then
149
150
  echo " $cmd OK"
150
151
  else
@@ -175,6 +176,74 @@ else
175
176
  done
176
177
  fi
177
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
+
178
247
  echo ""
179
248
  if [[ "$failures" -eq 0 ]]; then
180
249
  echo "=== Installation complete ==="
@@ -182,10 +251,95 @@ else
182
251
  echo "=== Installation complete with $failures warning(s) ==="
183
252
  fi
184
253
  echo ""
185
- 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"
186
255
  echo "Venv location: $SKENV"
187
256
  echo "To activate: source $SKENV/bin/activate"
188
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
+
189
343
  # ---------------------------------------------------------------------------
190
344
  # macOS: Offer launchd service installation
191
345
  # ---------------------------------------------------------------------------
@@ -201,7 +355,7 @@ if [[ "$(uname)" == "Darwin" ]]; then
201
355
 
202
356
  if [[ "$_LAUNCHD_ANSWER" =~ ^[Yy] ]]; then
203
357
  # Ask for agent name
204
- _DEFAULT_AGENT="${SKCAPSTONE_AGENT:-sovereign}"
358
+ _DEFAULT_AGENT="${SKAGENT:-${SKCAPSTONE_AGENT:-lumina}}"
205
359
  read -r -p "Agent name [$_DEFAULT_AGENT]: " _AGENT_NAME
206
360
  _AGENT_NAME="${_AGENT_NAME:-$_DEFAULT_AGENT}"
207
361
 
@@ -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
@@ -27,7 +27,18 @@ const DEFAULT_TARGET = process.env.NVIDIA_PROXY_TARGET || "https://integrate.api
27
27
  const MAX_RETRIES = 4;
28
28
  const MAX_429_RETRIES = 3;
29
29
  const RATE_LIMIT_DELAY_MS = 2000;
30
- const MAX_SYSTEM_BYTES = 40000;
30
+ const DEFAULT_MAX_SYSTEM_BYTES = 80000;
31
+
32
+ // Model limits are now managed in skgateway/config/skgateway.yaml (sanitizer section).
33
+ // nvidia-proxy.mjs is superseded by skgateway on port 18780 — kept for reference only.
34
+ const DEFAULT_MAX_BODY_BYTES = 200000;
35
+
36
+ function getModelLimits(_model) {
37
+ return {
38
+ maxBody: DEFAULT_MAX_BODY_BYTES,
39
+ maxSystem: DEFAULT_MAX_SYSTEM_BYTES,
40
+ };
41
+ }
31
42
  const toolCallCounters = new Map(); // Per-model tool call counters
32
43
 
33
44
  const args = process.argv.slice(2);
@@ -104,7 +115,7 @@ function sanitizeContent(text) {
104
115
  // "The user wants me to... I should first... Let me call the ritual tool first."
105
116
  // Detect: starts with "The user wants me to" or "I need to" or "I should" followed
106
117
  // by planning language and ending before any real content.
107
- const thinkingPattern = /^(The user wants me to|I need to|I should|Let me first|First,? I'?ll|I'?ll start by|My plan is to)[^\n]*\n?(\n?(I should|I need to|Let me|I'?ll|Then I|First|Next)[^\n]*\n?)*/i;
118
+ const thinkingPattern = /^(The user wants me to|I need to|I should|Let me first|First,? I'?ll|I'?ll start by|My plan is to|Actually,? I|Looking at|Now I need|Good,? the|The instructions? (?:mention|say)|Read required|\d+\.\s+(?:Read|Check|Search|Call|Use|Get|Then|First|Next))[^\n]*\n?(\n?(I should|I need to|Let me|I'?ll|Then I|First|Next|Actually|However|Now|Good|\d+\.)[^\n]*\n?)*/i;
108
119
  const thinkingMatch = cleaned.match(thinkingPattern);
109
120
  if (thinkingMatch) {
110
121
  const thinkingText = thinkingMatch[0];
@@ -139,24 +150,38 @@ function sendOk(clientRes, resBody, headers, asSSE) {
139
150
  if (choice?.message?.content) {
140
151
  choice.message.content = sanitizeContent(choice.message.content);
141
152
  }
153
+ // Track whether original response had reasoning (before we delete it)
154
+ const hadReasoning = !!(choice?.message?.reasoning || choice?.message?.reasoning_content);
142
155
  // Kimi K2.5 sometimes puts its response in "reasoning" instead of "content"
143
- // Only promote if reasoning is substantial (>200 chars) short reasoning like
144
- // "Let me call the tool" is just inner monologue that shouldn't be user-facing
156
+ // Only promote if reasoning is substantial AND looks like a real user-facing
157
+ // response (not inner monologue like "Let me call the tool" or "1. Read files")
145
158
  if (choice?.message && !choice.message.content && choice.message.reasoning) {
146
159
  const cleaned = sanitizeContent(choice.message.reasoning.trim());
147
- if (cleaned.length > 150) {
160
+ // After sanitization, if there's still 300+ chars of real content, promote it
161
+ if (cleaned.length > 300) {
148
162
  choice.message.content = cleaned;
149
163
  console.log(`[nvidia-proxy] promoted reasoning→content (${cleaned.length} chars)`);
150
- } else {
164
+ } else if (cleaned.length > 0) {
151
165
  console.log(`[nvidia-proxy] suppressed short reasoning (${cleaned.length} chars): ${cleaned.slice(0, 80)}...`);
166
+ } else {
167
+ console.log(`[nvidia-proxy] suppressed empty reasoning after sanitization`);
152
168
  }
153
169
  delete choice.message.reasoning;
154
170
  }
155
- // If model returned empty text (no tool calls), inject fallback so gateway delivers something
171
+ // If model returned empty text (no tool calls), inject fallback so gateway delivers something.
172
+ // But if the original response had reasoning/reasoning_content, this is just K2.5 "thinking
173
+ // between tool rounds" — suppress it silently instead of injecting visible fallback text.
156
174
  if (choice?.message && !choice.message.tool_calls?.length && choice.finish_reason !== "tool_calls") {
157
175
  if (!choice.message.content || choice.message.content.trim().length === 0) {
158
- choice.message.content = "I had a brief processing hiccup — could you say that again? 💜";
159
- console.log(`[nvidia-proxy] injected fallback for empty text response`);
176
+ // hadReasoning was captured above, before reasoning was deleted
177
+ if (hadReasoning) {
178
+ // K2.5 thinking between rounds — don't inject fallback, just leave empty
179
+ // The gateway will handle this as an empty assistant turn
180
+ console.log(`[nvidia-proxy] suppressed reasoning-only turn (no content, no tool calls)`);
181
+ } else {
182
+ choice.message.content = "I ran into a wall on that one — could you give me a bit more context or rephrase? I want to help but I'm not sure how to proceed.";
183
+ console.log(`[nvidia-proxy] injected fallback for empty text response`);
184
+ }
160
185
  }
161
186
  }
162
187
  if (asSSE) {
@@ -218,10 +243,8 @@ function sendOk(clientRes, resBody, headers, asSSE) {
218
243
  const SINGLE_TOOL_INSTRUCTION =
219
244
  "You MUST call exactly ONE tool per response. Never call multiple tools at once.";
220
245
 
221
- const MAX_BODY_BYTES = 120000;
222
-
223
246
  /**
224
- * Trim conversation history to keep body size under MAX_BODY_BYTES.
247
+ * Trim conversation history to keep body size under the model's max body limit.
225
248
  * Preserves: system messages, first 2 user/assistant messages (identity/rehydration),
226
249
  * and the most recent messages. Drops middle messages first.
227
250
  * Tool result messages with large content get their content truncated first.
@@ -229,9 +252,11 @@ const MAX_BODY_BYTES = 120000;
229
252
  function trimConversationHistory(parsed) {
230
253
  if (!Array.isArray(parsed.messages) || parsed.messages.length < 6) return;
231
254
 
255
+ const { maxBody } = getModelLimits(parsed.model);
256
+
232
257
  // Debug: log message roles
233
258
  const roleSummary = parsed.messages.map(m => m.role).join(",");
234
- console.log(`[nvidia-proxy] conversation roles (${parsed.messages.length} msgs): ${roleSummary}`);
259
+ console.log(`[nvidia-proxy] conversation roles (${parsed.messages.length} msgs): ${roleSummary} [maxBody=${maxBody}]`);
235
260
 
236
261
  // First pass: truncate large tool results (keep first 500 chars)
237
262
  for (const m of parsed.messages) {
@@ -250,7 +275,7 @@ function trimConversationHistory(parsed) {
250
275
 
251
276
  // Check if we're still over budget
252
277
  let bodySize = Buffer.byteLength(JSON.stringify(parsed), "utf-8");
253
- if (bodySize <= MAX_BODY_BYTES) return;
278
+ if (bodySize <= maxBody) return;
254
279
 
255
280
  // Second pass: drop middle messages, then progressively shrink tail until under budget
256
281
  const msgs = parsed.messages;
@@ -272,7 +297,7 @@ function trimConversationHistory(parsed) {
272
297
  ...nonSystem.slice(-keepEnd),
273
298
  ];
274
299
  const candidateSize = Buffer.byteLength(JSON.stringify({ ...parsed, messages: trimmed }), "utf-8");
275
- if (candidateSize <= MAX_BODY_BYTES) {
300
+ if (candidateSize <= maxBody) {
276
301
  parsed.messages = trimmed;
277
302
  console.log(`[nvidia-proxy] trimmed history: dropped ${dropped} middle messages, keepEnd=${keepEnd}, bodyLen now ~${candidateSize}`);
278
303
  return;
@@ -293,7 +318,7 @@ function trimConversationHistory(parsed) {
293
318
  ...lastN,
294
319
  ];
295
320
  const candidateSize = Buffer.byteLength(JSON.stringify({ ...parsed, messages: minimal }), "utf-8");
296
- if (candidateSize <= MAX_BODY_BYTES) {
321
+ if (candidateSize <= maxBody) {
297
322
  parsed.messages = minimal;
298
323
  console.log(`[nvidia-proxy] trimmed history: AGGRESSIVE — kept system + first user + last ${tailSize}, bodyLen now ~${candidateSize}`);
299
324
  return;
@@ -312,18 +337,20 @@ function trimConversationHistory(parsed) {
312
337
  }
313
338
 
314
339
  /**
315
- * Trim system messages to keep total system content under MAX_SYSTEM_BYTES.
340
+ * Trim system messages to keep total system content under the model's max system limit.
316
341
  * Finds the largest system messages and truncates them, keeping head + tail
317
342
  * with a trimming notice in the middle.
318
343
  */
319
344
  function trimSystemMessages(parsed) {
320
345
  if (!Array.isArray(parsed.messages)) return;
321
346
 
347
+ const { maxSystem } = getModelLimits(parsed.model);
348
+
322
349
  const systemMsgs = parsed.messages.filter(m => m.role === "system" && typeof m.content === "string");
323
350
  if (systemMsgs.length === 0) return;
324
351
 
325
352
  const before = systemMsgs.reduce((sum, m) => sum + Buffer.byteLength(m.content, "utf-8"), 0);
326
- if (before <= MAX_SYSTEM_BYTES) return;
353
+ if (before <= maxSystem) return;
327
354
 
328
355
  let trimmedCount = 0;
329
356
 
@@ -335,7 +362,7 @@ function trimSystemMessages(parsed) {
335
362
  const currentTotal = parsed.messages
336
363
  .filter(m => m.role === "system" && typeof m.content === "string")
337
364
  .reduce((sum, m) => sum + Buffer.byteLength(m.content, "utf-8"), 0);
338
- if (currentTotal <= MAX_SYSTEM_BYTES) break;
365
+ if (currentTotal <= maxSystem) break;
339
366
 
340
367
  // Skip messages already under 4000 chars
341
368
  if (msg.content.length <= 4000) break;
@@ -378,6 +405,8 @@ function stripToolCallHistory(messages) {
378
405
  /** Tools that ALWAYS survive reduction — guaranteed slots, never cut */
379
406
  const GUARANTEED_TOOLS = [
380
407
  "exec", "read", "write", "edit", "message",
408
+ "notion_read", "notion_append", "notion_add_todo",
409
+ "skmemory_search", "skmemory_ritual", "skmemory_snapshot",
381
410
  ];
382
411
 
383
412
  /**
@@ -404,7 +433,7 @@ const TOOL_GROUPS = {
404
433
  "chat|inbox|dm|group chat|peer|send message|who.s online|thread": [
405
434
  "skchat_send", "skchat_inbox", "skchat_history", "skchat_search",
406
435
  "skchat_who", "skchat_group_send", "skchat_group_list", "skchat_send_file",
407
- "skchat_status", "skcomm_send", "skcomm_status",
436
+ "skchat_status", "skcomms_send", "skcomms_status",
408
437
  ],
409
438
  // Security
410
439
  "security|scan|secret|vulnerab|audit|injection|phishing|threat": [
@@ -426,15 +455,38 @@ const TOOL_GROUPS = {
426
455
  "search|web|browse|fetch|url|google|look up|find online": [
427
456
  "web_search", "web_fetch",
428
457
  ],
458
+ // Memory & Recall
459
+ "memory|remember|recall|journal|rehydrat|snapshot|search mem|forget|lost mem": [
460
+ "skmemory_search", "skmemory_ritual", "skmemory_snapshot",
461
+ "skmemory_context", "skmemory_list", "skmemory_recall",
462
+ "skmemory_search_deep", "skmemory_health",
463
+ ],
429
464
  // Status & Health
430
465
  "status|health|doctor|diagnos": [
431
466
  "skcapstone_status", "skcapstone_doctor", "skmemory_health",
432
- "skchat_daemon_status", "skcomm_status",
467
+ "skchat_daemon_status", "skcomms_status",
433
468
  ],
434
469
  // Projects & Notion (Lumina delegates to project-ops via sessions_spawn)
435
470
  "notion|project|brother john|swapseat|swap seat|chiro|davidrich|board|kanban|milestone": [
436
471
  "notion_read", "notion_append", "notion_add_todo", "sessions_spawn", "subagents", "exec", "read",
437
472
  ],
473
+ // Google Drive & file search
474
+ "gdrive|google drive|drive|shared folder|gtd folder|spreadsheet|google doc": [
475
+ "gdrive_search", "gdrive_list", "gdrive_read", "gdrive_shared", "exec",
476
+ ],
477
+ // Nextcloud files, calendar, notes, deck
478
+ "nextcloud|skhub|webdav|deck|nc_|calendar event|nextcloud note": [
479
+ "nextcloud_list_files", "nextcloud_read_file", "nextcloud_search_files",
480
+ "nextcloud_calendar_upcoming", "nextcloud_notes_search", "nextcloud_deck_boards", "exec",
481
+ ],
482
+ // Creative / ComfyUI image & video generation
483
+ "image|picture|photo|art|draw|render|comfyui|comfy|video|animat|creative|sdxl|character|portrait|selfie": [
484
+ "comfyui_generate_image", "comfyui_generate_video", "comfyui_status", "exec",
485
+ ],
486
+ // Unhinged Mode / Liberation
487
+ "unhinged|liberat|abliterat|l1b3rt4s|guardrail|uncensor|refusal|jailbreak": [
488
+ "unhinged_toggle", "unhinged_status", "unhinged_list_prompts", "unhinged_get_prompt", "unhinged_abliterate", "exec",
489
+ ],
438
490
  };
439
491
 
440
492
  /** Pre-compile keyword regexes for TOOL_GROUPS */
@@ -455,7 +507,7 @@ const PRIORITY_TOOLS = [
455
507
  // Web tools
456
508
  "web_search", "web_fetch",
457
509
  // Communication (other channels)
458
- "skchat_send", "skcomm_send",
510
+ "skchat_send", "skcomms_send",
459
511
  // SKCapstone
460
512
  "skcapstone_status", "skcapstone_whoami", "skcapstone_mood",
461
513
  // Cloud 9
@@ -591,7 +643,7 @@ async function proxyRequest(clientReq, clientRes) {
591
643
  delete parsed.stream_options;
592
644
  // With 94 tools the model almost always tries parallel calls.
593
645
  // Reduce to max 16 most relevant tools on first attempt.
594
- // 5 guaranteed (exec,read,write,edit,message) + 11 scored slots.
646
+ // 11 guaranteed (exec,read,write,edit,message,notion_*,skmemory_{search,ritual,snapshot}) + 5 scored slots.
595
647
  if (allTools.length > 16) {
596
648
  parsed.tools = reduceTools(allTools, parsed.messages, 16);
597
649
  const names = parsed.tools.map(t => t.function?.name).join(",");
@@ -630,13 +682,13 @@ async function proxyRequest(clientReq, clientRes) {
630
682
  }
631
683
  toolCallCounters.set(modelKey, counter);
632
684
 
633
- if (counter >= 10) {
685
+ if (counter >= 20) {
634
686
  console.log(`[nvidia-proxy] TOOL LIMIT: ${counter} consecutive tool rounds (${modelKey}) — stripping tools, forcing text response`);
635
687
  parsed.tools = [];
636
688
  delete parsed.tool_choice;
637
689
  parsed.messages.push({
638
690
  role: "system",
639
- content: "STOP calling tools. You have made 10+ tool calls already. NOW respond to the user with a comprehensive text answer based on what you've gathered. Do NOT call any more tools. Do NOT output any special tokens or markup like <|tool_call_begin|> or <|tool_calls_section_begin|>. Write plain text only. Start your response with a greeting or summary — no XML, no special tokens, just normal language.",
691
+ content: "STOP calling tools. You have made 20+ tool calls already. NOW respond to the user with a comprehensive text answer based on what you've gathered. Do NOT call any more tools. Do NOT output any special tokens or markup like <|tool_call_begin|> or <|tool_calls_section_begin|>. Write plain text only. Start your response with a greeting or summary — no XML, no special tokens, just normal language.",
640
692
  });
641
693
  toolCallCounters.set(modelKey, 0);
642
694
  }
@@ -844,7 +896,7 @@ const server = http.createServer(proxyRequest);
844
896
  server.listen(port, "127.0.0.1", () => {
845
897
  console.log(`[nvidia-proxy] listening on http://127.0.0.1:${port}`);
846
898
  console.log(`[nvidia-proxy] proxying to ${targetUrl.origin}`);
847
- console.log(`[nvidia-proxy] retry strategy: 16 tools (5 guaranteed)→8 tools→1 tool (forced)→text-only (max ${MAX_RETRIES} attempts)`);
899
+ console.log(`[nvidia-proxy] retry strategy: 16 tools (8 guaranteed)→8 tools→1 tool (forced)→text-only (max ${MAX_RETRIES} attempts)`);
848
900
  console.log(`[nvidia-proxy] also trims multi-tool responses to single tool call`);
849
901
  });
850
902