@smilintux/skcapstone 0.10.0 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
package/scripts/install.sh
CHANGED
|
@@ -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
|
|
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 "
|
|
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
|
|
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:
|
|
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:-
|
|
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
|
package/scripts/nvidia-proxy.mjs
CHANGED
|
@@ -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
|
|
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
|
|
144
|
-
// "Let me call the tool"
|
|
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
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
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 <=
|
|
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 <=
|
|
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 <=
|
|
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
|
|
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 <=
|
|
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 <=
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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
|
-
//
|
|
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 >=
|
|
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
|
|
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 (
|
|
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
|
|