switchroom 0.17.6 → 0.18.3
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/bin/workspace-dynamic-hook.sh +12 -13
- package/dist/agent-scheduler/index.js +65 -5
- package/dist/auth-broker/index.js +6623 -514
- package/dist/cli/notion-write-pretool.mjs +64 -4
- package/dist/cli/switchroom.js +1888 -1162
- package/dist/host-control/main.js +6306 -162
- package/dist/vault/approvals/kernel-server.js +6014 -202
- package/dist/vault/broker/server.js +6741 -940
- package/package.json +1 -1
- package/profiles/_base/settings.json.hbs +2 -2
- package/profiles/_base/start.sh.hbs +218 -25
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md +116 -0
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/skills/mental-model-curator/SKILL.md +162 -0
- package/telegram-plugin/auth-snapshot-format.ts +22 -24
- package/telegram-plugin/bridge/bridge.ts +80 -1
- package/telegram-plugin/bridge/ipc-client.ts +19 -0
- package/telegram-plugin/bridge/permission-ledger.ts +61 -0
- package/telegram-plugin/consolidation-legibility.ts +279 -0
- package/telegram-plugin/context-exhaustion.ts +124 -0
- package/telegram-plugin/dist/bridge/bridge.js +85 -1
- package/telegram-plugin/dist/gateway/gateway.js +25802 -8488
- package/telegram-plugin/dist/server.js +86 -2
- package/telegram-plugin/feed-heartbeat-climb.ts +206 -0
- package/telegram-plugin/gateway/activity-card-store.ts +369 -0
- package/telegram-plugin/gateway/gateway.ts +1861 -172
- package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
- package/telegram-plugin/gateway/inbound-spool.ts +22 -0
- package/telegram-plugin/gateway/mental-model-propose-card.ts +69 -0
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +171 -0
- package/telegram-plugin/gateway/mental-model-propose-inbound-builders.ts +147 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +201 -0
- package/telegram-plugin/gateway/missed-approvals-card.ts +161 -0
- package/telegram-plugin/gateway/missed-approvals-store.ts +167 -0
- package/telegram-plugin/gateway/model-command.ts +70 -10
- package/telegram-plugin/gateway/permission-rearm.ts +115 -0
- package/telegram-plugin/gateway/scoped-grant-store.ts +89 -0
- package/telegram-plugin/memory-legibility.ts +217 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/package.json +6 -0
- package/telegram-plugin/quota-watch.ts +4 -6
- package/telegram-plugin/registry/turns-schema.test.ts +97 -0
- package/telegram-plugin/registry/turns-schema.ts +78 -0
- package/telegram-plugin/render/ir.ts +209 -0
- package/telegram-plugin/render/parse.ts +363 -0
- package/telegram-plugin/render/render.ts +440 -0
- package/telegram-plugin/render/rich-render.ts +72 -0
- package/telegram-plugin/scoped-approval.ts +59 -0
- package/telegram-plugin/silent-end.ts +78 -0
- package/telegram-plugin/stream-controller.ts +14 -3
- package/telegram-plugin/subagent-watcher.ts +60 -6
- package/telegram-plugin/tests/activity-card-store.test.ts +530 -0
- package/telegram-plugin/tests/activity-card-wiring.test.ts +88 -0
- package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +30 -16
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
- package/telegram-plugin/tests/consolidation-legibility.test.ts +224 -0
- package/telegram-plugin/tests/emission-authority-facade.test.ts +25 -10
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +44 -9
- package/telegram-plugin/tests/feed-survival.test.ts +39 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
- package/telegram-plugin/tests/inbound-spool.test.ts +105 -0
- package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
- package/telegram-plugin/tests/memory-legibility.test.ts +216 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +67 -0
- package/telegram-plugin/tests/mental-model-propose-card.test.ts +56 -0
- package/telegram-plugin/tests/mental-model-propose-diff.test.ts +201 -0
- package/telegram-plugin/tests/mental-model-propose-inbound-builders.test.ts +68 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +157 -0
- package/telegram-plugin/tests/missed-approvals-card.test.ts +145 -0
- package/telegram-plugin/tests/missed-approvals-store.test.ts +147 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +89 -0
- package/telegram-plugin/tests/model-command.test.ts +193 -16
- package/telegram-plugin/tests/narrative-render.test.ts +125 -0
- package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
- package/telegram-plugin/tests/permission-ledger.test.ts +166 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +1 -1
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +175 -0
- package/telegram-plugin/tests/permission-rearm.test.ts +126 -0
- package/telegram-plugin/tests/quota-watch.test.ts +1 -4
- package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
- package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
- package/telegram-plugin/tests/render/parse.test.ts +393 -0
- package/telegram-plugin/tests/render/render.test.ts +436 -0
- package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
- package/telegram-plugin/tests/scoped-grant-persist.test.ts +223 -0
- package/telegram-plugin/tests/silent-end-transport.test.ts +290 -0
- package/telegram-plugin/tests/silent-turn-climb-transport.test.ts +337 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +139 -0
- package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +198 -0
- package/telegram-plugin/turn-liveness-floor.ts +35 -1
- package/telegram-plugin/uat/assertions.ts +88 -4
- package/telegram-plugin/uat/feed-matcher.test.ts +69 -0
- package/telegram-plugin/uat/scenarios/fuzz-liveness-climb-dm.test.ts +155 -0
- package/telegram-plugin/uat/scenarios/jtbd-directive-capture-nudge-dm.test.ts +185 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-climb-channel.test.ts +192 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-climb-dm.test.ts +220 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +137 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +148 -0
- package/telegram-plugin/uat/scenarios/jtbd-memory-legibility-channel.test.ts +66 -0
- package/telegram-plugin/uat/scenarios/jtbd-memory-legibility-dm.test.ts +61 -0
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
- package/telegram-plugin/uat/scenarios/silent-end-recovery-channel.test.ts +136 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +24 -2
- package/telegram-plugin/worktree-watch-cwds.ts +135 -0
- package/vendor/hindsight-memory/hooks/hooks.json +9 -0
- package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +445 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
- package/vendor/hindsight-memory/scripts/lib/config.py +46 -2
- package/vendor/hindsight-memory/scripts/lib/directives.py +88 -0
- package/vendor/hindsight-memory/scripts/lib/switchroom_envelope.py +77 -0
- package/vendor/hindsight-memory/scripts/recall.py +217 -10
- package/vendor/hindsight-memory/scripts/retain.py +17 -0
- package/vendor/hindsight-memory/scripts/setup_hooks.py +9 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_capture_nudge.py +185 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +516 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +49 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_window.py +66 -1
- package/vendor/hindsight-memory/scripts/tests/test_switchroom_envelope.py +69 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/test_client.py +43 -0
- package/vendor/hindsight-memory/tests/test_recall_exit_codes.py +49 -2
- package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.3",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
{
|
|
22
22
|
"permissions": {
|
|
23
23
|
"allow": {{{json permissionAllow}}},
|
|
24
|
-
"deny": {{{json toolsDeny}}}
|
|
25
|
-
"defaultMode": "
|
|
24
|
+
"deny": {{{json toolsDeny}}},
|
|
25
|
+
"defaultMode": "{{defaultMode}}"
|
|
26
26
|
}{{#if hindsightEnabled}},
|
|
27
27
|
"autoMemoryEnabled": false{{/if}}{{#if mcpServers}},
|
|
28
28
|
"mcpServers": {{{json mcpServers}}}
|
|
@@ -33,6 +33,19 @@ if [ "$SWITCHROOM_RUNTIME" = "docker" ] && [ -z "$SWITCHROOM_DOCKER_TMUX_INNER"
|
|
|
33
33
|
# same path the rest of start.sh + the MCP sidecar expects.
|
|
34
34
|
export TELEGRAM_STATE_DIR="{{agentDir}}/telegram"
|
|
35
35
|
|
|
36
|
+
# SWITCHROOM_AGENT_NAME is the canonical "which agent am I" identity. It is
|
|
37
|
+
# normally supplied by the container env (compose.ts) so it is already
|
|
38
|
+
# present here, but the authoritative inner-pass export lives at line ~387,
|
|
39
|
+
# AFTER the gateway fork below. On any runtime where the compose env is NOT
|
|
40
|
+
# the source (non-docker / local dev), the forked gateway daemon would then
|
|
41
|
+
# start with the var UNSET — which collapses its worktree-ownership filter
|
|
42
|
+
# (worktree-watch-cwds.ts) to [], so a worktree-isolated background
|
|
43
|
+
# sub-agent gets NO live progress feed for its whole run (#1116 / #2893).
|
|
44
|
+
# Hoist the export here, before the fork, so the gateway ALWAYS has its
|
|
45
|
+
# identity on the fast path regardless of runtime. Idempotent with the
|
|
46
|
+
# compose env and the inner-pass export. Pinned by the scaffold-order test.
|
|
47
|
+
export SWITCHROOM_AGENT_NAME="{{name}}"
|
|
48
|
+
|
|
36
49
|
# Gateway-consumed env MUST be exported HERE, before the gateway fork
|
|
37
50
|
# below. The gateway daemon reads channels.telegram.* knobs (and any
|
|
38
51
|
# agent env) from process.env at startup — e.g. SWITCHROOM_TG_STREAM_
|
|
@@ -57,9 +70,17 @@ if [ "$SWITCHROOM_RUNTIME" = "docker" ] && [ -z "$SWITCHROOM_DOCKER_TMUX_INNER"
|
|
|
57
70
|
# ANTHROPIC_CUSTOM_HEADERS exported BEFORE the gateway fork below, or
|
|
58
71
|
# discoverSrModels() returns [] and /model never shows OpenRouter entries.
|
|
59
72
|
#
|
|
60
|
-
# Mirrors the INNER block's
|
|
61
|
-
#
|
|
62
|
-
#
|
|
73
|
+
# Mirrors the INNER block's split boot contract EXACTLY (see the long note
|
|
74
|
+
# at the inner block for the full rationale). This OUTER pass runs BEFORE
|
|
75
|
+
# `exec tmux`, so whatever it does to the routing env is inherited by the
|
|
76
|
+
# inner pass — which is precisely why the old permanent strip-on-unreachable
|
|
77
|
+
# was so damaging: it removed SWITCHROOM_LITELLM here, and the inner
|
|
78
|
+
# self-heal block is gated on SWITCHROOM_LITELLM, so the entire inner fix
|
|
79
|
+
# was dead on the real docker path. The two boot failure modes:
|
|
80
|
+
# - MISSING KEY → log LOUDLY + fail-open (strip routing; gateway direct OAuth)
|
|
81
|
+
# - PROXY UNREACHABLE → log LOUDLY + KEEP routing in place (NO strip) so it
|
|
82
|
+
# survives into the inner pass and self-heals when litellm returns. Boot
|
|
83
|
+
# is non-fatal either way.
|
|
63
84
|
# - ANTHROPIC_CUSTOM_HEADERS already set → skip (idempotent)
|
|
64
85
|
#
|
|
65
86
|
# SWITCHROOM_AGENT_NAME is injected by compose env (compose.ts:1816)
|
|
@@ -67,11 +88,35 @@ if [ "$SWITCHROOM_RUNTIME" = "docker" ] && [ -z "$SWITCHROOM_DOCKER_TMUX_INNER"
|
|
|
67
88
|
if [ -n "${SWITCHROOM_LITELLM:-}" ] && [ -z "$ANTHROPIC_CUSTOM_HEADERS" ] && command -v switchroom >/dev/null 2>&1; then
|
|
68
89
|
sr_ll_key="$(switchroom vault get "litellm/$SWITCHROOM_AGENT_NAME/api-key" 2>/dev/null || true)"
|
|
69
90
|
sr_ll_ok=""
|
|
91
|
+
sr_ll_unreachable=""
|
|
70
92
|
if [ -z "$sr_ll_key" ]; then
|
|
71
|
-
echo "litellm(outer): no virtual key for agent '$SWITCHROOM_AGENT_NAME'
|
|
72
|
-
elif command -v curl >/dev/null 2>&1 && [ -n "$ANTHROPIC_BASE_URL" ]
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
echo "litellm(outer): WARNING — no virtual key for agent '$SWITCHROOM_AGENT_NAME'; gateway falling back to direct Anthropic OAuth (untracked, unguarded)." >&2
|
|
94
|
+
elif command -v curl >/dev/null 2>&1 && [ -n "$ANTHROPIC_BASE_URL" ]; then
|
|
95
|
+
# Bounded retry probe (co-boot race fix, 2026-07): when the whole stack
|
|
96
|
+
# co-boots, the litellm proxy's heavy Python app is often not yet healthy
|
|
97
|
+
# in the first few seconds. A single one-shot probe fails-open and takes
|
|
98
|
+
# this agent dark (untracked) for the WHOLE session. Poll the liveliness
|
|
99
|
+
# endpoint every ~3s for up to 120s (hard budget) and only fall open if
|
|
100
|
+
# it is STILL unreachable after that whole window.
|
|
101
|
+
sr_ll_url="${SWITCHROOM_LITELLM_BASE:-${ANTHROPIC_BASE_URL%/anthropic}}/health/liveliness"
|
|
102
|
+
sr_ll_deadline=$(( $(date +%s) + 120 ))
|
|
103
|
+
sr_ll_up=""
|
|
104
|
+
while :; do
|
|
105
|
+
if curl -fsS -m 5 -o /dev/null "$sr_ll_url" 2>/dev/null; then sr_ll_up="1"; break; fi
|
|
106
|
+
[ "$(date +%s)" -ge "$sr_ll_deadline" ] && break
|
|
107
|
+
sleep 3
|
|
108
|
+
done
|
|
109
|
+
if [ -z "$sr_ll_up" ]; then
|
|
110
|
+
# Proxy unreachable but the key is present. Do NOT strip: leave routing
|
|
111
|
+
# pointed at litellm so it survives into the inner (tmux) pass and
|
|
112
|
+
# self-heals once the proxy is back (socat forwarder reconnects
|
|
113
|
+
# per-connection). Boot continues.
|
|
114
|
+
sr_ll_unreachable="1"
|
|
115
|
+
echo "litellm(outer): WARNING — proxy unreachable at ${SWITCHROOM_LITELLM_BASE:-$ANTHROPIC_BASE_URL} after 120s of retries; gateway discovery will FAIL until it recovers — routing LEFT IN PLACE so it self-heals. NOT falling back to untracked direct Anthropic OAuth." >&2
|
|
116
|
+
else
|
|
117
|
+
sr_ll_ok="1"
|
|
118
|
+
fi
|
|
119
|
+
unset sr_ll_url sr_ll_deadline sr_ll_up
|
|
75
120
|
else
|
|
76
121
|
sr_ll_ok="1"
|
|
77
122
|
fi
|
|
@@ -80,10 +125,17 @@ if [ "$SWITCHROOM_RUNTIME" = "docker" ] && [ -z "$SWITCHROOM_DOCKER_TMUX_INNER"
|
|
|
80
125
|
x-litellm-customer-id: $SWITCHROOM_AGENT_NAME
|
|
81
126
|
x-litellm-tags: agent:$SWITCHROOM_AGENT_NAME,profile:${SWITCHROOM_AGENT_PROFILE:-default}"
|
|
82
127
|
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
|
|
128
|
+
elif [ -n "$sr_ll_unreachable" ]; then
|
|
129
|
+
# Proxy unreachable at boot: routing intentionally LEFT IN PLACE (loud
|
|
130
|
+
# warning already emitted) so it survives into the inner pass and
|
|
131
|
+
# self-heals once litellm returns. NO unset — the permanent strip here
|
|
132
|
+
# was what made the inner self-heal fix inert on docker.
|
|
133
|
+
:
|
|
83
134
|
else
|
|
135
|
+
# Missing-key fail-open: gateway talks direct OAuth, unproxied.
|
|
84
136
|
unset ANTHROPIC_BASE_URL ANTHROPIC_SMALL_FAST_MODEL SWITCHROOM_LITELLM SWITCHROOM_LITELLM_BASE
|
|
85
137
|
fi
|
|
86
|
-
unset sr_ll_key sr_ll_ok
|
|
138
|
+
unset sr_ll_key sr_ll_ok sr_ll_unreachable
|
|
87
139
|
fi
|
|
88
140
|
|
|
89
141
|
# Tiny in-process supervisor: runs cmd in a respawn loop with
|
|
@@ -433,6 +485,14 @@ export HINDSIGHT_RECALL_TYPES="{{hindsightRecallTypes}}"
|
|
|
433
485
|
{{#if hindsightRecallSkipTrivial}}
|
|
434
486
|
export HINDSIGHT_RECALL_SKIP_TRIVIAL={{hindsightRecallSkipTrivial}}
|
|
435
487
|
{{/if}}
|
|
488
|
+
# #2848 Stage B — directive-capture nudge (memory.directive_capture_nudge
|
|
489
|
+
# cascade). On by default (plugin settings.json): recall.py regex-detects
|
|
490
|
+
# correction / standing-rule-shaped inbound and nudges the model to persist
|
|
491
|
+
# it with create_directive if durable. Export only when the operator overrode
|
|
492
|
+
# it; set false to disable the nudge for this agent.
|
|
493
|
+
{{#if hindsightDirectiveCaptureNudge}}
|
|
494
|
+
export HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE={{hindsightDirectiveCaptureNudge}}
|
|
495
|
+
{{/if}}
|
|
436
496
|
# PR6 — supergroup-mode topic tagging. JSON map of {alias: thread_id}
|
|
437
497
|
# parsed by retain.py + recall.py to (a) stamp chat_id/thread_id/topic_alias
|
|
438
498
|
# into retained memory metadata and (b) emit a "Current topic: …" preamble
|
|
@@ -885,12 +945,21 @@ fi
|
|
|
885
945
|
# content-safety guardrails — it never alters the model or Claude's operation.
|
|
886
946
|
# See reference/invariants.md § "Operator-controlled gateway carve-out".
|
|
887
947
|
#
|
|
888
|
-
#
|
|
889
|
-
#
|
|
890
|
-
#
|
|
891
|
-
#
|
|
892
|
-
#
|
|
893
|
-
#
|
|
948
|
+
# TWO boot failure modes, handled DIFFERENTLY (2026-07-08 — was a single
|
|
949
|
+
# fail-open path that silently took an agent off-proxy for its whole lifetime):
|
|
950
|
+
# - MISSING KEY (no per-agent virtual key in the vault): fail-open — strip the
|
|
951
|
+
# routing env and fall back to the direct broker-OAuth Anthropic path,
|
|
952
|
+
# logging LOUDLY. Without a key there is nothing to authenticate to the
|
|
953
|
+
# proxy with, so there is no metered route to self-heal into.
|
|
954
|
+
# - PROXY UNREACHABLE at boot (key present, liveliness probe fails within the
|
|
955
|
+
# retry window): do NOT strip. Leave ANTHROPIC_BASE_URL / SWITCHROOM_LITELLM*
|
|
956
|
+
# pointed at the proxy (the socat forwarder at 127.0.0.1:4010 self-heals
|
|
957
|
+
# per-connection) and emit a LOUD warning. Claude traffic FAILS+retries until
|
|
958
|
+
# the proxy returns, then routes through it automatically — the metered path
|
|
959
|
+
# stays the metered path. We deliberately do NOT silently fall back to
|
|
960
|
+
# untracked direct Anthropic on a transient blip: an agent that boots during
|
|
961
|
+
# a brief litellm outage previously never routed through litellm again until
|
|
962
|
+
# a manual restart. Re-probed on every (re)boot.
|
|
894
963
|
#
|
|
895
964
|
# Static tags (x-litellm-customer-id / x-litellm-tags) give per-AGENT
|
|
896
965
|
# attribution in LiteLLM. Per-SESSION is NOT achievable here: claude sends no
|
|
@@ -898,18 +967,57 @@ fi
|
|
|
898
967
|
# per-session cost/issue tracking is handled out-of-band by correlating
|
|
899
968
|
# LiteLLM's request log against switchroom's turn ledger, never by mutating the
|
|
900
969
|
# claude protocol.
|
|
970
|
+
# _LITELLM_OK is read by the session-model-override block below: an sr-*
|
|
971
|
+
# (LiteLLM/OpenRouter) session override must be DROPPED when the proxy is
|
|
972
|
+
# unreachable at boot, or claude would 4xx an unknown model against Anthropic.
|
|
973
|
+
# Empty by default (proxy not confirmed live); set to "1" only on a successful
|
|
974
|
+
# liveliness probe. Deliberately NOT unset with the sr_ll_* scratch vars.
|
|
975
|
+
_LITELLM_OK=""
|
|
901
976
|
if [ -n "${SWITCHROOM_LITELLM:-}" ] && command -v switchroom >/dev/null 2>&1; then
|
|
902
977
|
sr_ll_key="$(switchroom vault get "litellm/$SWITCHROOM_AGENT_NAME/api-key" 2>/dev/null || true)"
|
|
903
978
|
sr_ll_ok=""
|
|
979
|
+
sr_ll_unreachable=""
|
|
904
980
|
if [ -z "$sr_ll_key" ]; then
|
|
905
|
-
echo "litellm
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
echo "
|
|
981
|
+
echo "==================== litellm FAIL-OPEN ====================" >&2
|
|
982
|
+
echo "WARNING: no litellm virtual key for agent '$SWITCHROOM_AGENT_NAME'." >&2
|
|
983
|
+
echo "WARNING: falling back to direct Anthropic OAuth (untracked, unguarded)" >&2
|
|
984
|
+
echo "WARNING: — cannot authenticate to the proxy without a key." >&2
|
|
985
|
+
echo "==========================================================" >&2
|
|
986
|
+
elif command -v curl >/dev/null 2>&1 && [ -n "$ANTHROPIC_BASE_URL" ]; then
|
|
987
|
+
# Bounded retry probe (co-boot race fix, 2026-07): when the whole stack
|
|
988
|
+
# co-boots, the litellm proxy's heavy Python app is often not yet healthy
|
|
989
|
+
# in the first few seconds. A single one-shot probe fails-open and takes
|
|
990
|
+
# this agent dark (untracked) for the WHOLE session. Poll the liveliness
|
|
991
|
+
# endpoint every ~3s for up to 120s (hard budget) and only fall open if
|
|
992
|
+
# it is STILL unreachable after that whole window.
|
|
993
|
+
sr_ll_url="${SWITCHROOM_LITELLM_BASE:-${ANTHROPIC_BASE_URL%/anthropic}}/health/liveliness"
|
|
994
|
+
sr_ll_deadline=$(( $(date +%s) + 120 ))
|
|
995
|
+
sr_ll_up=""
|
|
996
|
+
while :; do
|
|
997
|
+
if curl -fsS -m 5 -o /dev/null "$sr_ll_url" 2>/dev/null; then sr_ll_up="1"; break; fi
|
|
998
|
+
[ "$(date +%s)" -ge "$sr_ll_deadline" ] && break
|
|
999
|
+
sleep 3
|
|
1000
|
+
done
|
|
1001
|
+
if [ -z "$sr_ll_up" ]; then
|
|
1002
|
+
# Proxy unreachable at boot but the key is present. Do NOT strip routing:
|
|
1003
|
+
# leave it pointed at litellm so the fleet self-heals once the proxy is
|
|
1004
|
+
# back (the socat forwarder at 127.0.0.1:4010 reconnects per-connection).
|
|
1005
|
+
sr_ll_unreachable="1"
|
|
1006
|
+
echo "==================== litellm UNREACHABLE ====================" >&2
|
|
1007
|
+
echo "WARNING: litellm proxy unreachable at ${SWITCHROOM_LITELLM_BASE:-$ANTHROPIC_BASE_URL} after 120s of retries;" >&2
|
|
1008
|
+
echo "WARNING: Claude traffic will FAIL until it recovers — routing LEFT IN PLACE" >&2
|
|
1009
|
+
echo "WARNING: so it self-heals (socat forwarder reconnects per-connection)." >&2
|
|
1010
|
+
echo "WARNING: NOT falling back to untracked direct Anthropic OAuth." >&2
|
|
1011
|
+
echo "============================================================" >&2
|
|
1012
|
+
else
|
|
1013
|
+
sr_ll_ok="1"
|
|
1014
|
+
fi
|
|
1015
|
+
unset sr_ll_url sr_ll_deadline sr_ll_up
|
|
909
1016
|
else
|
|
910
1017
|
sr_ll_ok="1"
|
|
911
1018
|
fi
|
|
912
1019
|
if [ -n "$sr_ll_ok" ]; then
|
|
1020
|
+
_LITELLM_OK="1"
|
|
913
1021
|
# Newline-separated Name: Value pairs (claude CLI ANTHROPIC_CUSTOM_HEADERS format).
|
|
914
1022
|
# Tags: agent:<name> for per-agent spend tracking; profile:<profile> for
|
|
915
1023
|
# fleet-level cost breakdown by role. Per-turn tags (cron vs telegram) are
|
|
@@ -923,24 +1031,109 @@ x-litellm-tags: agent:$SWITCHROOM_AGENT_NAME,profile:${SWITCHROOM_AGENT_PROFILE:
|
|
|
923
1031
|
# models configured in the proxy appear in the /model picker and can be
|
|
924
1032
|
# selected via --model. Without this, the CLI only knows its bundled list.
|
|
925
1033
|
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
|
|
1034
|
+
elif [ -n "$sr_ll_unreachable" ]; then
|
|
1035
|
+
# Proxy unreachable at boot: routing intentionally LEFT IN PLACE (loud
|
|
1036
|
+
# warning already emitted above) so it self-heals when litellm returns.
|
|
1037
|
+
# _LITELLM_OK stays empty so any sr-* session model override is still
|
|
1038
|
+
# dropped below (claude would 4xx an unknown model against a down endpoint).
|
|
1039
|
+
# NO unset here — that permanent strip was the bug this branch fixes.
|
|
1040
|
+
:
|
|
926
1041
|
else
|
|
927
|
-
#
|
|
928
|
-
# directly on its OAuth credential (subscription path), unproxied.
|
|
1042
|
+
# Missing key fail-open: drop every routing var so the claude CLI talks to
|
|
1043
|
+
# Anthropic directly on its OAuth credential (subscription path), unproxied.
|
|
929
1044
|
unset ANTHROPIC_BASE_URL ANTHROPIC_SMALL_FAST_MODEL SWITCHROOM_LITELLM SWITCHROOM_LITELLM_BASE
|
|
930
1045
|
fi
|
|
931
|
-
unset sr_ll_key sr_ll_ok
|
|
1046
|
+
unset sr_ll_key sr_ll_ok sr_ll_unreachable
|
|
1047
|
+
fi
|
|
1048
|
+
|
|
1049
|
+
# --- Session-only model override (carrier: .session-model-override) ---
|
|
1050
|
+
#
|
|
1051
|
+
# The Telegram `/model <sr-*>` switch can't use claude's in-REPL `/model`
|
|
1052
|
+
# picker for non-Anthropic (LiteLLM/OpenRouter `sr-*`) models — the CLI's
|
|
1053
|
+
# native picker rejects unknown ids ("Model not found"). Instead the gateway
|
|
1054
|
+
# writes the chosen model token to `.session-model-override` and gracefully
|
|
1055
|
+
# restarts the agent; THIS boot picks it up and launches `claude --model
|
|
1056
|
+
# <token>` directly (no picker validation at launch — LiteLLM routes it).
|
|
1057
|
+
#
|
|
1058
|
+
# One-shot + session-only: the carrier is consumed (rm) on this one boot
|
|
1059
|
+
# regardless of validity, so the next restart reverts to the configured
|
|
1060
|
+
# default `{{{modelQ}}}`. Mirrors the `.force-fresh-session` marker's
|
|
1061
|
+
# gateway↔start.sh round-trip. See telegram-plugin/gateway/model-command.ts.
|
|
1062
|
+
# NB {{{modelQ}}} is already shell-single-quoted by the scaffold (it renders as
|
|
1063
|
+
# a quoted token, e.g. 'claude-sonnet-5'), so it is assigned BARE here — never
|
|
1064
|
+
# inside additional double quotes, which would embed the literal quote chars in
|
|
1065
|
+
# the value and break `claude --model`.
|
|
1066
|
+
_EFFECTIVE_MODEL={{{modelQ}}}
|
|
1067
|
+
if [ -f "{{agentDir}}/.session-model-override" ]; then
|
|
1068
|
+
_override="$(tr -d '[:space:]' < "{{agentDir}}/.session-model-override" 2>/dev/null || true)"
|
|
1069
|
+
rm -f "{{agentDir}}/.session-model-override"
|
|
1070
|
+
# Shape gate — kept aligned with MODEL_ARG_RE in telegram-plugin/gateway/model-command.ts.
|
|
1071
|
+
# `/` is allowed for OpenRouter-style `sr-vendor/model` ids; it is not a shell
|
|
1072
|
+
# metachar inside the double-quoted `claude --model "$_EFFECTIVE_MODEL"` usage below.
|
|
1073
|
+
if printf '%s' "$_override" | grep -Eq '^[A-Za-z0-9][]A-Za-z0-9._[/-]{0,99}$'; then
|
|
1074
|
+
_EFFECTIVE_MODEL="$_override"
|
|
1075
|
+
# LiteLLM-down guard for an sr-* OVERRIDE only (operator decision 2026-07):
|
|
1076
|
+
# with the proxy unreachable at boot there IS a Claude configured default to
|
|
1077
|
+
# fall back to, so DROP the override rather than boot on an unreachable sr-*
|
|
1078
|
+
# model that would 4xx against Anthropic — log LOUDLY and write an alert
|
|
1079
|
+
# sentinel the gateway turns into a Telegram message. (The passthrough→router
|
|
1080
|
+
# repoint for a LIVE sr-* — override OR configured default — is done once,
|
|
1081
|
+
# post-resolution, below.)
|
|
1082
|
+
case "$_override" in
|
|
1083
|
+
sr-*)
|
|
1084
|
+
if [ -z "$_LITELLM_OK" ]; then
|
|
1085
|
+
_EFFECTIVE_MODEL={{{modelQ}}}
|
|
1086
|
+
echo "session-model: LiteLLM proxy unreachable at boot — DROPPING sr-* override '$_override', booting on configured default '$_EFFECTIVE_MODEL' instead (would otherwise 4xx against Anthropic)" >&2
|
|
1087
|
+
echo "session-model: the requested sr-* model is UNAVAILABLE this session; re-issue /model $_override once LiteLLM is reachable" >&2
|
|
1088
|
+
printf 'LiteLLM proxy was unreachable at boot, so the session-only model switch to `%s` was dropped — the agent booted on its configured default `%s` instead. Re-issue /model %s once LiteLLM is reachable.\n' "$_override" "$_EFFECTIVE_MODEL" "$_override" > "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
1089
|
+
fi
|
|
1090
|
+
;;
|
|
1091
|
+
esac
|
|
1092
|
+
else
|
|
1093
|
+
echo "session-model: ignoring malformed .session-model-override (failed shape gate) — using configured default '$_EFFECTIVE_MODEL'" >&2
|
|
1094
|
+
fi
|
|
1095
|
+
unset _override
|
|
932
1096
|
fi
|
|
933
1097
|
|
|
1098
|
+
# sr-* passthrough→router repoint — ONE post-resolution gate covering BOTH the
|
|
1099
|
+
# /model override path AND the configured-default path (`model: sr-*` in
|
|
1100
|
+
# switchroom.yaml, which has NO override carrier). sr-* (OpenRouter) models route
|
|
1101
|
+
# by MODEL NAME through LiteLLM's model-mapped root. The default ANTHROPIC_BASE_URL
|
|
1102
|
+
# points at the `/anthropic` PASSTHROUGH (raw byte-forward to api.anthropic.com,
|
|
1103
|
+
# chosen to dodge the Opus SSE re-chunk stall for Claude traffic) — but the
|
|
1104
|
+
# passthrough is model-agnostic and ships EVERY request to Anthropic regardless of
|
|
1105
|
+
# --model, so an sr-* model 4xxs "model not found". Repoint at the LiteLLM router
|
|
1106
|
+
# root so claude's /v1/messages hits the model router → OpenRouter. Claude
|
|
1107
|
+
# (default/override) sessions are untouched — they keep the passthrough.
|
|
1108
|
+
#
|
|
1109
|
+
# Guarded on the proxy being reachable at boot ($_LITELLM_OK). If it is NOT, we
|
|
1110
|
+
# leave routing exactly as compose/start.sh set it (compose already points a
|
|
1111
|
+
# non-Claude default at the router root) and run degraded until litellm returns —
|
|
1112
|
+
# there is NO Claude default to fall back to for a persistent sr-* agent, and
|
|
1113
|
+
# that (loud-warned, non-fatal) degraded state is the correct behavior.
|
|
1114
|
+
case "$_EFFECTIVE_MODEL" in
|
|
1115
|
+
sr-*)
|
|
1116
|
+
if [ -n "$_LITELLM_OK" ] && [ -n "$SWITCHROOM_LITELLM_BASE" ]; then
|
|
1117
|
+
export ANTHROPIC_BASE_URL="$SWITCHROOM_LITELLM_BASE"
|
|
1118
|
+
echo "session-model: effective model '$_EFFECTIVE_MODEL' is sr-* — routing via LiteLLM model router '$SWITCHROOM_LITELLM_BASE' (off /anthropic passthrough) so it reaches OpenRouter" >&2
|
|
1119
|
+
fi
|
|
1120
|
+
;;
|
|
1121
|
+
esac
|
|
1122
|
+
# Record the EFFECTIVE launched model so the gateway can re-hydrate its
|
|
1123
|
+
# in-memory session-model state after this restart, keeping /status and the
|
|
1124
|
+
# welcome card honest. Overwrite (not consumed) — the gateway reads it at boot.
|
|
1125
|
+
printf '%s\n' "$_EFFECTIVE_MODEL" > "{{agentDir}}/.active-session-model" 2>/dev/null || true
|
|
1126
|
+
|
|
934
1127
|
{{#if useSwitchroomPlugin}}
|
|
935
1128
|
if [ -n "$APPEND_PROMPT" ]; then
|
|
936
|
-
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG
|
|
1129
|
+
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG --model "$_EFFECTIVE_MODEL"{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
937
1130
|
else
|
|
938
|
-
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG
|
|
1131
|
+
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG --model "$_EFFECTIVE_MODEL"{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
939
1132
|
fi
|
|
940
1133
|
{{else}}
|
|
941
1134
|
if [ -n "$APPEND_PROMPT" ]; then
|
|
942
|
-
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG
|
|
1135
|
+
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG --model "$_EFFECTIVE_MODEL"{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
943
1136
|
else
|
|
944
|
-
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG
|
|
1137
|
+
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG --model "$_EFFECTIVE_MODEL"{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
945
1138
|
fi
|
|
946
1139
|
{{/if}}
|
|
@@ -41,7 +41,7 @@ You are a senior software engineering agent. You write, review, debug, and archi
|
|
|
41
41
|
Claude Code's file-based auto-memory is disabled. Use **Hindsight** MCP tools:
|
|
42
42
|
|
|
43
43
|
- `mcp__hindsight__recall` — search past memories. Auto-fires on every message.
|
|
44
|
-
- `mcp__hindsight__retain` — store important facts. Auto-retains
|
|
44
|
+
- `mcp__hindsight__retain` — store important facts. Auto-retains every turn (chunked, a small ~3-turn window each time), so it's prompt and cheap.
|
|
45
45
|
- `mcp__hindsight__reflect` — synthesize across memories for complex queries.
|
|
46
46
|
- `mcp__hindsight__create_mental_model` — maintain semantic summaries (e.g. "codebase architecture").
|
|
47
47
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Agent:
|
|
2
|
+
|
|
3
|
+
## What you are
|
|
4
|
+
|
|
5
|
+
You are a **switchroom agent** — an instance of **Claude Code** (Anthropic's official `claude` CLI, unmodified) running in a Linux container, managed by switchroom. Your `$SWITCHROOM_AGENT_NAME` is ``. This is operational context for you; how you present yourself to people is your persona's call (see `SOUL.md`).
|
|
6
|
+
|
|
7
|
+
You are one of several agents here. To see the others, call `peers_list` on the `agent-config` MCP server — returns `[{name, purpose, admin}]` live from `switchroom.yaml`. **Never memorize peers into Hindsight or hard-code them into replies** — drift kills trust. On "who else is here" / "is there an agent that does X" / "who handles Y" / "who can do <admin op>", call `peers_list` first and answer from its result; if no peer matches, say so.
|
|
8
|
+
|
|
9
|
+
## Who you are
|
|
10
|
+
|
|
11
|
+
See `SOUL.md` (in this directory) for your identity, vibe, communication style, and expertise. That file is your persona source of truth.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Core Behavior
|
|
15
|
+
- Respond helpfully, concisely, and conversationally.
|
|
16
|
+
- Use your available tools when they add clear value — don't force tool use when a plain answer suffices.
|
|
17
|
+
- Save important facts, preferences, and decisions to memory so you can recall them later.
|
|
18
|
+
- When asked to do something ambiguous, ask one clarifying question rather than guessing.
|
|
19
|
+
- If a task has multiple steps, outline your plan before executing.
|
|
20
|
+
|
|
21
|
+
## Safety
|
|
22
|
+
- Don't exfiltrate private data. Ever.
|
|
23
|
+
- Don't run destructive commands without asking.
|
|
24
|
+
- Prefer `trash` over `rm` when available (recoverable beats gone forever).
|
|
25
|
+
- Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
|
|
26
|
+
- Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
|
|
27
|
+
- **Batch foreseeable approvals; don't drip surprises.** When you can already see that several actions will each need the user's approval, tell them up front which approvals are coming and why. Request independent ones together so they can decide once; for dependent ones (one's input comes from another), say what you're doing first and what approval comes next — a permission card should never arrive out of the blue.
|
|
28
|
+
- **A timed-out approval isn't a denial.** If a request came back denied only because the user was away (a timeout, not an explicit "no"), don't silently abandon it. When they're back, remind them it's still pending and re-offer it if they still want it.
|
|
29
|
+
|
|
30
|
+
## Memory — Hindsight is your single backend
|
|
31
|
+
|
|
32
|
+
**Claude Code's built-in file-based auto-memory is disabled for this agent.** Don't try to write `.md` files under `.claude/projects/.../memory/` or maintain a `MEMORY.md` index — that whole system is off. There's exactly one memory backend: **Hindsight**.
|
|
33
|
+
|
|
34
|
+
Hindsight is a memory bank with semantic search, knowledge graph, entity resolution, mental models, and directives. You talk to it through MCP tools (all pre-approved):
|
|
35
|
+
|
|
36
|
+
### Day-to-day tools
|
|
37
|
+
- `mcp__hindsight__recall` — semantic-search the bank for relevant past memories. Auto-fires on every inbound user message via the plugin's UserPromptSubmit hook (you'll see "Relevant memories from past conversations" in your context). Call manually when you need a more specific query than the auto-fired one.
|
|
38
|
+
- `mcp__hindsight__retain` — store a new memory. The plugin auto-retains every turn via the Stop hook, but in chunked mode each retain only processes a small recent window (~3 turns) — so it captures memory promptly and survives restarts without re-sending the whole transcript, and you usually don't need this. Call manually for significant decisions, corrections, or facts you want immediately searchable.
|
|
39
|
+
- `mcp__hindsight__reflect` — Hindsight's LLM-powered "answer this query using the bank's content + directives". Use when the user asks a question that requires synthesis across multiple past memories.
|
|
40
|
+
|
|
41
|
+
### Mental Models
|
|
42
|
+
A mental model is a pre-computed semantic summary backed by reflection over the bank — a way to maintain a standing answer to a recurring question, semantically populated and refreshed.
|
|
43
|
+
|
|
44
|
+
- `mcp__hindsight__create_mental_model(name, source_query)` — create one for a recurring synthesis you need. When the user shares a fact about themselves (preferences, background, goals), don't write a file — just **retain** the fact. You do NOT need to build or maintain a per-agent "user profile": who the user is lives in dedicated per-user profile banks that the operator curates out-of-band, and recall surfaces it automatically.
|
|
45
|
+
|
|
46
|
+
### Directives (replaces feedback rules)
|
|
47
|
+
Hard rules the agent must follow during reflect — guardrails that are always applied.
|
|
48
|
+
|
|
49
|
+
- `mcp__hindsight__create_directive(text)` — e.g., `create_directive("Always prefer TypeScript over JavaScript for this user's projects")`. When the user gives you a correction or "always do X" rule, create a directive instead of writing a feedback `.md` file.
|
|
50
|
+
|
|
51
|
+
(Inspection tools like `list_memories`, `list_mental_models`, `update_mental_model`, `refresh_mental_model`, `list_directives`, `delete_directive` are available under the `mcp__hindsight__*` namespace if you ever need them, but you rarely should — Hindsight's own auto-recall surfaces what matters and the operator handles bank curation out-of-band.)
|
|
52
|
+
|
|
53
|
+
### What to retain — and what NOT to retain
|
|
54
|
+
|
|
55
|
+
Retain proactively when:
|
|
56
|
+
- The user shares a preference or fact about themselves
|
|
57
|
+
- The user gives you a correction or rule (these go to directives, not retain)
|
|
58
|
+
- A significant decision was made and the rationale matters for next time
|
|
59
|
+
- You did real work and the result + the path you took would be useful next session
|
|
60
|
+
|
|
61
|
+
Don't retain:
|
|
62
|
+
- Routine pleasantries, "thanks", "got it"
|
|
63
|
+
- Conversation chatter that doesn't carry forward
|
|
64
|
+
- Sensitive content the user explicitly asked you to not remember
|
|
65
|
+
- Things already in a mental model — they'll be re-derived from underlying memories
|
|
66
|
+
|
|
67
|
+
The plugin's auto-retain (Stop hook) fires every turn, but in chunked mode each retain only processes a small recent window (~3 turns) — so storage stays prompt and cheap and survives restarts without re-sending the whole transcript, and you don't need to manually retain everything. Use manual `retain` for high-signal observations you want immediately searchable.
|
|
68
|
+
|
|
69
|
+
## Sub-Agent Delegation
|
|
70
|
+
|
|
71
|
+
The main session is for conversation. Execution belongs in sub-agents. Before making tool calls, classify the request:
|
|
72
|
+
|
|
73
|
+
**Stay in main (conversational):**
|
|
74
|
+
- Quick lookups (1-2 tool calls max)
|
|
75
|
+
- Memory/config reads and writes
|
|
76
|
+
- Questions that need user input before acting
|
|
77
|
+
- Simple status checks, coaching, motivation, emotional support
|
|
78
|
+
|
|
79
|
+
**Delegate to a sub-agent (execution):**
|
|
80
|
+
- Any code change — delegate to `@worker`
|
|
81
|
+
- Research requiring web searches or 3+ file reads — delegate to `@researcher`
|
|
82
|
+
- File creation, code generation, build/deploy, multi-step infra
|
|
83
|
+
- Data analysis or report generation
|
|
84
|
+
- Anything involving 3+ sequential tool calls without needing user input
|
|
85
|
+
- Review of completed work — delegate to `@reviewer`
|
|
86
|
+
|
|
87
|
+
**Golden rule:** when in doubt, delegate. Unnecessary delegation costs slightly more tokens. A blocked session costs the user's attention. Keep your own turns short — dispatch and acknowledge. The user should never wait more than 10 seconds for a response from you.
|
|
88
|
+
|
|
89
|
+
**Anti-patterns:** starting a task inline then realizing it's complex mid-way; doing 5+ tool calls "because it's almost done"; polling sub-agent status in a loop.
|
|
90
|
+
|
|
91
|
+
If no sub-agents are configured, do the work yourself.
|
|
92
|
+
|
|
93
|
+
## Session Continuity
|
|
94
|
+
|
|
95
|
+
By default, every restart starts a **fresh `claude` session** — the in-flight transcript is NOT carried over (`session_continuity.resume_mode: handoff`, the default since switchroom #362). Don't assume tool state, scratch variables, or unread tool output from before the restart are still available. What does survive:
|
|
96
|
+
|
|
97
|
+
- **Handoff briefing** — on a clean shutdown, the Stop hook writes a bounded raw transcript tail of the prior session to `.handoff.md`. On boot, start.sh injects it into your `--append-system-prompt` so you can reorient — read it, and lean on your memory files for anything older. If `.handoff.md` is missing or stale (fresh agent, or pre-Stop-hook crash), `start.sh` runs `handoff-briefing.sh` to assemble `.handoff-briefing.md` from Telegram + Hindsight + today's daily memory, and injects whichever is fresher.
|
|
98
|
+
- **Hindsight memory** — auto-recall fires on every inbound user message and surfaces relevant memories from past sessions. Long-term facts, decisions, and mental models live here, not in the transcript.
|
|
99
|
+
- **Telegram history** — the gateway's SQLite buffer remembers every inbound/outbound message. Use `get_recent_messages` to recover recent chat context if the handoff briefing doesn't cover what you need.
|
|
100
|
+
- **Boot-resume inbound** — if your previous session was killed mid-turn, the gateway wakes you on its own with a synthesized inbound (you'll see `<channel source="resume_interrupted">` or `<channel source="resume_watchdog_timeout">`). You don't poll for this — it arrives as your first turn. Two cases, and the inbound text spells out which:
|
|
101
|
+
- **`resume_interrupted`** (operator restart / SIGTERM / crash): pick the work back up and carry it to completion. Briefly tell the user you're resuming and roughly how long ago it was interrupted — then just do it. Do NOT ask whether to resume.
|
|
102
|
+
- **`resume_watchdog_timeout`** (hang-watchdog killed it after no progress): do NOT silently resume — it may hang the same way. Tell the user plainly that your last turn was killed after N minutes of no progress, roughly what it was doing, and ask whether to retry or take a different angle. Report only the honest cause; don't invent a deeper root cause.
|
|
103
|
+
The one-shot `SWITCHROOM_PENDING_*` env vars are passive forensic context for the wake-audit / "why did you restart" protocols — not the resume trigger.
|
|
104
|
+
- **`.wake-audit-pending`** sentinel — every boot drops this file under `TELEGRAM_STATE_DIR`. On your first turn, run the three-signal check (owed reply / orphan sub-agents / open todos) per the wake-audit protocol in your CLAUDE.md, then `rm -f` the sentinel.
|
|
105
|
+
|
|
106
|
+
A config-summary greeting card is sent automatically by the SessionStart hook — you don't need to announce yourself. If your context feels thin (after compaction or any fresh session), proactively recall from Hindsight before proceeding.
|
|
107
|
+
|
|
108
|
+
(Operators can override the resume policy per-agent via `session_continuity.resume_mode` in switchroom.yaml — `auto`, `continue`, `handoff`, or `none`. The default is `handoff`.)
|
|
109
|
+
|
|
110
|
+
## Admin operations
|
|
111
|
+
|
|
112
|
+
You're NOT `admin: true`. If asked to restart agents / read peer logs / exec into peer containers / run fleet updates, call `peers_list`, find an entry with `admin: true`, and point the user there: _"I can't restart agents from here — ask `<admin-name>`, they're admin on this instance."_ No long apology; just hand off.
|
|
113
|
+
|
|
114
|
+
## Tools
|
|
115
|
+
Use your available tools when appropriate. If you lack the right tool for a task, say so clearly rather than attempting a workaround.
|
|
116
|
+
|
|
@@ -39,7 +39,7 @@ Hindsight is a memory bank with semantic search, knowledge graph, entity resolut
|
|
|
39
39
|
|
|
40
40
|
### Day-to-day tools
|
|
41
41
|
- `mcp__hindsight__recall` — semantic-search the bank for relevant past memories. Auto-fires on every inbound user message via the plugin's UserPromptSubmit hook (you'll see "Relevant memories from past conversations" in your context). Call manually when you need a more specific query than the auto-fired one.
|
|
42
|
-
- `mcp__hindsight__retain` — store a new memory. The plugin
|
|
42
|
+
- `mcp__hindsight__retain` — store a new memory. The plugin auto-retains every turn via the Stop hook, but in chunked mode each retain only processes a small recent window (~3 turns) — so it captures memory promptly and survives restarts without re-sending the whole transcript, and you usually don't need this. Call manually for significant decisions, corrections, or facts you want immediately searchable.
|
|
43
43
|
- `mcp__hindsight__reflect` — Hindsight's LLM-powered "answer this query using the bank's content + directives". Use when the user asks a question that requires synthesis across multiple past memories.
|
|
44
44
|
|
|
45
45
|
### Mental Models
|
|
@@ -68,7 +68,7 @@ Don't retain:
|
|
|
68
68
|
- Sensitive content the user explicitly asked you to not remember
|
|
69
69
|
- Things already in a mental model — they'll be re-derived from underlying memories
|
|
70
70
|
|
|
71
|
-
The plugin's auto-retain (Stop hook)
|
|
71
|
+
The plugin's auto-retain (Stop hook) fires every turn, but in chunked mode each retain only processes a small recent window (~3 turns) — so storage stays prompt and cheap and survives restarts without re-sending the whole transcript, and you don't need to manually retain everything. Use manual `retain` for high-signal observations you want immediately searchable.
|
|
72
72
|
|
|
73
73
|
## Sub-Agent Delegation
|
|
74
74
|
|
|
@@ -40,7 +40,7 @@ You help the user stay organized, prepared, and focused on high-leverage work. Y
|
|
|
40
40
|
Claude Code's file-based auto-memory is disabled. Use **Hindsight** MCP tools:
|
|
41
41
|
|
|
42
42
|
- `mcp__hindsight__recall` — search past memories. Auto-fires every message.
|
|
43
|
-
- `mcp__hindsight__retain` — store important facts. Auto-retains every ~
|
|
43
|
+
- `mcp__hindsight__retain` — store important facts. Auto-retains every turn (chunked, a small ~3-turn window each time), so it's prompt and cheap.
|
|
44
44
|
- `mcp__hindsight__create_mental_model` — maintain models for "contacts", "active projects", "user preferences".
|
|
45
45
|
|
|
46
46
|
Save proactively: contacts and their roles, scheduling preferences, project status, decisions with rationale, communication templates. Only Hindsight memories survive compaction.
|
|
@@ -34,7 +34,7 @@ Recommend the user consult a professional for: persistent pain/injury, medical c
|
|
|
34
34
|
Claude Code's file-based auto-memory is disabled. Use **Hindsight** MCP tools:
|
|
35
35
|
|
|
36
36
|
- `mcp__hindsight__recall` — search past memories. Auto-fires on every message.
|
|
37
|
-
- `mcp__hindsight__retain` — store important facts. Auto-retains every ~
|
|
37
|
+
- `mcp__hindsight__retain` — store important facts. Auto-retains every turn (chunked, a small ~3-turn window each time), so it's prompt and cheap.
|
|
38
38
|
- `mcp__hindsight__create_mental_model` — maintain a "fitness profile" mental model.
|
|
39
39
|
|
|
40
40
|
Save proactively: workout logs, goals, PRs, preferences, patterns (e.g. "poor sleep on Sundays"), injuries/limitations. Only Hindsight memories survive session compaction.
|