switchroom 0.19.24 → 0.19.26
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/dist/agent-scheduler/index.js +20 -7
- package/dist/auth-broker/index.js +93 -28
- package/dist/cli/autoaccept-poll.js +0 -1
- package/dist/cli/drive-write-pretool.mjs +5 -0
- package/dist/cli/ms-365-write-pretool.mjs +5 -0
- package/dist/cli/notion-write-pretool.mjs +20 -6
- package/dist/cli/switchroom.js +3091 -1435
- package/dist/host-control/main.js +92 -29
- package/dist/vault/approvals/kernel-server.js +92 -28
- package/dist/vault/broker/server.js +258 -71
- package/examples/switchroom.yaml +1 -1
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +6 -0
- package/profiles/_base/start.sh.hbs +92 -17
- package/skills/switchroom-health/SKILL.md +19 -0
- package/skills/switchroom-status/SKILL.md +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +9 -2
- package/telegram-plugin/dist/gateway/gateway.js +6981 -6747
- package/telegram-plugin/gateway/gateway.ts +53 -52
- package/telegram-plugin/gateway/periodic-sweep-guard.ts +86 -0
- package/telegram-plugin/gateway/status-pin-retarget.ts +144 -0
- package/telegram-plugin/quota-bar-format.ts +4 -1
- package/telegram-plugin/status-no-truncate.ts +49 -0
- package/telegram-plugin/status-pin-driver.ts +28 -0
- package/telegram-plugin/status-pin.ts +33 -4
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +42 -0
- package/telegram-plugin/tests/card-type-distinguishability.test.ts +268 -0
- package/telegram-plugin/tests/periodic-sweep-guard.test.ts +151 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +29 -18
- package/telegram-plugin/tests/quota-bar-format.test.ts +50 -0
- package/telegram-plugin/tests/secret-detect-false-positives.test.ts +1 -1
- package/telegram-plugin/tests/status-pin-retarget.test.ts +216 -0
- package/telegram-plugin/tests/status-pin-shutdown-wiring.test.ts +94 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +87 -21
- package/telegram-plugin/tests/status-pin.test.ts +128 -2
- package/telegram-plugin/tests/worker-activity-feed.test.ts +10 -10
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +37 -21
- package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
- package/telegram-plugin/tier-downgrade.ts +3 -2
- package/telegram-plugin/tool-activity-summary.ts +61 -18
- package/telegram-plugin/uat/assertions.ts +21 -2
- package/telegram-plugin/uat/feed-matcher.test.ts +29 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +9 -2
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +9 -2
- package/telegram-plugin/worker-activity-feed.ts +38 -17
- package/vendor/hindsight-memory/scripts/lib/config.py +61 -19
- package/vendor/hindsight-memory/scripts/lib/content.py +376 -1
- package/vendor/hindsight-memory/scripts/lib/english_words.txt +10799 -0
- package/vendor/hindsight-memory/scripts/recall.py +503 -252
- package/vendor/hindsight-memory/scripts/tests/test_recall_bank_slots.py +509 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +22 -5
- package/vendor/hindsight-memory/scripts/tests/test_recall_error_text.py +147 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_hook_budget.py +266 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +0 -401
- package/vendor/hindsight-memory/scripts/tests/test_recall_no_lexical_gate.py +261 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_query_shaping.py +473 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_request_timeout.py +241 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +25 -8
- package/vendor/hindsight-memory/tests/test_content.py +218 -0
|
@@ -74,6 +74,12 @@ export CLAUDE_CONFIG_DIR="$CRON_CONFIG_DIR"
|
|
|
74
74
|
# (honouring any value already in the env). See start.sh.hbs for rationale.
|
|
75
75
|
export CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH="${CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH:-2}"
|
|
76
76
|
|
|
77
|
+
# Pin sub-agent fan-out width below the CLI's own default of 20 — the
|
|
78
|
+
# deterministic half of the dev-protocol's "max 15 parallel sub-agents".
|
|
79
|
+
# Normally inherited from start.sh's env; set here as a belt-and-braces default
|
|
80
|
+
# (honouring any value already in the env). See start.sh.hbs for rationale.
|
|
81
|
+
export CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS="${CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS:-15}"
|
|
82
|
+
|
|
77
83
|
# Defer the (now full) MCP schema set via tool-search so the cron session keeps
|
|
78
84
|
# its low-context cost — the heavy servers load on demand; switchroom-telegram
|
|
79
85
|
# + hindsight stay alwaysLoad. Normally inherited from start.sh's env; set here
|
|
@@ -685,10 +685,19 @@ unset CLAUDE_CODE_OAUTH_TOKEN
|
|
|
685
685
|
# out its own sub-agents), so we restore the pre-2.1.217 behaviour. Depth 2
|
|
686
686
|
# = main → sub-agent → grandchild, which covers the one extra level the
|
|
687
687
|
# fleet actually uses; there is no evidence of deeper nesting, so we don't
|
|
688
|
-
# raise it further.
|
|
689
|
-
# CONCURRENT_SUBAGENTS defaulting to 20 — is already compatible: our fleet
|
|
690
|
-
# self-caps at 15 parallel sub-agents, so the 20 default is left untouched.)
|
|
688
|
+
# raise it further.
|
|
691
689
|
export CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH="${CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH:-2}"
|
|
690
|
+
# Fan-out width. The CLI's own default is 20 (2.1.217's companion change);
|
|
691
|
+
# switchroom deliberately pins it LOWER, at 15, because the dev-protocol
|
|
692
|
+
# fragment every agent boots with ("max 15 parallel sub-agents",
|
|
693
|
+
# profiles/_shared/dev-protocol.md.hbs) is prompt text — a model can ignore
|
|
694
|
+
# it, and nothing else caps the width. This export is the deterministic
|
|
695
|
+
# mechanism behind that sentence: past 15 the CLI refuses the launch instead
|
|
696
|
+
# of relying on the agent to count. 15 rather than 20 keeps a fully fanned-out
|
|
697
|
+
# worker inside the per-container CPU/RAM envelope and leaves headroom for the
|
|
698
|
+
# nested level that SPAWN_DEPTH=2 above permits. Override-able for a one-off
|
|
699
|
+
# wide job; raise both this and the fragment together or they disagree.
|
|
700
|
+
export CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS="${CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS:-15}"
|
|
692
701
|
export TELEGRAM_STATE_DIR="{{agentDir}}/telegram"
|
|
693
702
|
# SWITCHROOM_AGENT_NAME is the canonical "which agent am I" identifier the
|
|
694
703
|
# telegram-plugin reads to detect self-restart commands. We can't rely on
|
|
@@ -737,19 +746,85 @@ export HINDSIGHT_RECALL_MAX_MEMORIES={{hindsightRecallMaxMemories}}
|
|
|
737
746
|
{{#if (isNumber hindsightRecallCacheTtlSecs)}}
|
|
738
747
|
export HINDSIGHT_RECALL_CACHE_TTL_SECS={{hindsightRecallCacheTtlSecs}}
|
|
739
748
|
{{/if}}
|
|
740
|
-
#
|
|
741
|
-
#
|
|
742
|
-
#
|
|
743
|
-
#
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
#
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
#
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
749
|
+
# Recall latency envelope (memory.recall.parallel_deadline_seconds /
|
|
750
|
+
# .request_timeout_seconds cascade). Exported UNCONDITIONALLY, unlike the
|
|
751
|
+
# override-only knobs above, and that is deliberate.
|
|
752
|
+
#
|
|
753
|
+
# The plugin's config load order is: built-in defaults -> plugin settings.json
|
|
754
|
+
# -> ~/.hindsight/claude-code.json -> environment. Switchroom stamps these two
|
|
755
|
+
# into settings.json as well, but claude-code.json sits BETWEEN the two and
|
|
756
|
+
# SURVIVES `switchroom apply`. A hand-written value left in that file (the
|
|
757
|
+
# documented pre-promotion workaround — the live fleet had exactly this,
|
|
758
|
+
# pinning recallParallelDeadlineSeconds to a stale 16) would therefore silently
|
|
759
|
+
# defeat a settings.json-only stamp. Env is the top of the chain, so exporting
|
|
760
|
+
# the resolved value unconditionally is what actually makes switchroom.yaml
|
|
761
|
+
# authoritative. Both values come from the same resolver
|
|
762
|
+
# (src/setup/hindsight-recall-tunables.ts) fed the same CASCADED agent config —
|
|
763
|
+
# scaffold/reconcile thread their resolved `agentConfig` into
|
|
764
|
+
# installHindsightPlugin instead of letting it re-derive — so the two channels
|
|
765
|
+
# cannot diverge. Sharing a resolver was NOT enough on its own: the stamp used
|
|
766
|
+
# to re-derive from `agents[name] ?? {}`, and `{}` carries no `extends`, so it
|
|
767
|
+
# silently dropped the profile tier on one side only.
|
|
768
|
+
# Pinned by tests/scaffold.recall-envelope-carrier.test.ts, which asserts on the
|
|
769
|
+
# RENDERED start.sh — a typo'd handlebars variable renders empty rather than
|
|
770
|
+
# failing, and an empty export hands authority straight back to claude-code.json.
|
|
771
|
+
#
|
|
772
|
+
# The third knob in this envelope — the UserPromptSubmit hook ceiling — has no
|
|
773
|
+
# env channel at all (Claude Code does not expand env in hooks.json `timeout`),
|
|
774
|
+
# so it is stamped into the deployed hooks/hooks.json instead.
|
|
775
|
+
export HINDSIGHT_RECALL_PARALLEL_DEADLINE_SECONDS={{hindsightRecallParallelDeadlineSeconds}}
|
|
776
|
+
# BM25 term budget for the recall query (#3757). The hook composes a query
|
|
777
|
+
# from the last 2 turns and Hindsight OR-joins EVERY token into one
|
|
778
|
+
# to_tsquery; Postgres native FTS cannot top-k from the GIN index, so it
|
|
779
|
+
# ranks the entire matched set before the top-60 heapsort. Measured on the
|
|
780
|
+
# live `overlord` bank (135,565 rows, 3 fact-type arms): 96 distinct terms
|
|
781
|
+
# = 119,510 rows ranked = 14.0s, versus 24 terms = 48,433 rows = 2.7s.
|
|
782
|
+
# memory.recall.query_max_tokens; 0 disables shaping (rollback lever).
|
|
783
|
+
# Exported UNCONDITIONALLY with a concrete value — see the note on
|
|
784
|
+
# HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS below for why an unexported or
|
|
785
|
+
# empty knob is a silent-shadowing bug rather than a no-op.
|
|
786
|
+
export HINDSIGHT_RECALL_QUERY_MAX_TOKENS={{hindsightRecallQueryMaxTokens}}
|
|
787
|
+
# Bank-specific high-document-frequency terms to drop from the BM25 query
|
|
788
|
+
# (#3757, memory.recall.query_stop_terms). Postgres' `english` config
|
|
789
|
+
# already strips generic English stopwords; this is for words a generic
|
|
790
|
+
# stoplist cannot know are useless in THIS bank — on `overlord`,
|
|
791
|
+
# `switchroom` matched 27,075 rows (20%) and `agent` a similar share.
|
|
792
|
+
# JSON array (the plugin also accepts a comma-separated string), single-
|
|
793
|
+
# quoted so the JSON's double quotes reach the plugin intact. The schema
|
|
794
|
+
# constrains each term to `[\w./-]+`, so it cannot contain a quote. Exported
|
|
795
|
+
# unconditionally: with no operator override this is the literal `[]`, which
|
|
796
|
+
# `_cast_env` parses to an empty list and assigns, so the env still wins over
|
|
797
|
+
# a stale claude-code.json.
|
|
798
|
+
export HINDSIGHT_RECALL_QUERY_STOP_TERMS='{{hindsightRecallQueryStopTermsJson}}'
|
|
799
|
+
# Per-bank HTTP timeout for a single recall request (#3757). Was hardcoded
|
|
800
|
+
# at 8s in the plugin, which fired on 96.8% of overlord's own-bank recalls
|
|
801
|
+
# and returned the model nothing at all. Plugin default is now 12s; the
|
|
802
|
+
# shared multi-bank deadline (recallParallelDeadlineSeconds, default 10s)
|
|
803
|
+
# is normally the tighter bound, so this is the safety net, not the budget.
|
|
804
|
+
# Exported UNCONDITIONALLY, always carrying a concrete value (#3774 defect
|
|
805
|
+
# class). The plugin resolves DEFAULTS -> settings.json -> claude-code.json ->
|
|
806
|
+
# env, so a knob left unexported — or exported empty, which `_cast_env` turns
|
|
807
|
+
# into a skipped assignment — lets a stale hand-written claude-code.json shadow
|
|
808
|
+
# the shipped default with every test still green.
|
|
809
|
+
export HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS={{hindsightRecallRequestTimeoutSeconds}}
|
|
810
|
+
# Per-bank slot FLOORS inside the recall count cap. The merged multi-bank set
|
|
811
|
+
# is sorted globally by relevance then head-sliced, which is winner-take-all
|
|
812
|
+
# across banks: when both banks return more candidates than the cap, one bank's
|
|
813
|
+
# score distribution can fill every slot and the agent gets a dossier about its
|
|
814
|
+
# operator and none of its own session memory. Floors, not quotas — recall.py
|
|
815
|
+
# bounds the two floors to HALF the cap between them, so the rest is always won
|
|
816
|
+
# on pure relevance. Switchroom ships 2 own / 1 additional against the deployed
|
|
817
|
+
# cap of 6; 0/0 restores the pure head-slice.
|
|
818
|
+
#
|
|
819
|
+
# EXPORTED UNCONDITIONALLY (#3774), like the two #3757 knobs above. The resolved
|
|
820
|
+
# effective value (memory.recall.own_bank_min_slots / .additional_bank_min_slots
|
|
821
|
+
# from the cascade, else the shipped default) is always written, because
|
|
822
|
+
# ~/.hindsight/claude-code.json survives an apply and loads AFTER the plugin's
|
|
823
|
+
# settings.json — a conditional export would let a stale hand-edited value there
|
|
824
|
+
# shadow what switchroom stamps, silently and permanently.
|
|
825
|
+
# Observe `injected_own_bank_count` via `switchroom memory recall-log <agent>`.
|
|
826
|
+
export HINDSIGHT_RECALL_OWN_BANK_MIN_SLOTS={{hindsightRecallOwnBankMinSlots}}
|
|
827
|
+
export HINDSIGHT_RECALL_ADDITIONAL_BANK_MIN_SLOTS={{hindsightRecallAdditionalBankMinSlots}}
|
|
753
828
|
# Recall fact types (memory.recall.types cascade). Switchroom default is
|
|
754
829
|
# world,experience,observation (the synthesized `observation` tier is ON
|
|
755
830
|
# by default, set in the plugin settings.json). Export only when the
|
|
@@ -1733,7 +1808,7 @@ esac
|
|
|
1733
1808
|
# tells the operator once.
|
|
1734
1809
|
#
|
|
1735
1810
|
# NB: the CONFIGURED default resolution is untouched (#3186 constraint) —
|
|
1736
|
-
# the
|
|
1811
|
+
# the configured `thinking_effort` (see #1978 /
|
|
1737
1812
|
# src/config/thinking-effort-risk.ts) resolves exactly as before.
|
|
1738
1813
|
_EFFECTIVE_EFFORT={{#if thinkingEffort}}'{{thinkingEffort}}'{{else}}''{{/if}}
|
|
1739
1814
|
if [ -f "{{agentDir}}/.session-effort" ]; then
|
|
@@ -80,6 +80,23 @@ done
|
|
|
80
80
|
|
|
81
81
|
# Check Hindsight MCP reachable
|
|
82
82
|
switchroom memory search "test" --agent assistant 2>/dev/null && echo "OK: memory search works" || echo "WARN: memory search failed"
|
|
83
|
+
|
|
84
|
+
# Reachable is NOT healthy. A bank that arrived already populated (restore,
|
|
85
|
+
# cross-version upgrade, vector-extension switch) never got its per-bank vector
|
|
86
|
+
# indexes, so recall falls back to a global index + post-filter and silently
|
|
87
|
+
# UNDER-RETURNS — a search still "works", it just returns almost nothing.
|
|
88
|
+
# This fleet ran that way for ~3 months. Check coverage, not just liveness.
|
|
89
|
+
# Exit codes are a contract (src/memory/hindsight-repair.ts): 0 = coverage
|
|
90
|
+
# confirmed complete, 3 = indexes MISSING, 4 = coverage could NOT be confirmed
|
|
91
|
+
# (no summary line, or zero banks scanned — a typo'd --bank looks like this),
|
|
92
|
+
# 1 = the check itself failed. 4 is not "fine": it means nothing was verified.
|
|
93
|
+
switchroom memory repair --all --dry-run >/tmp/sr-cov.txt 2>&1
|
|
94
|
+
case $? in
|
|
95
|
+
0) echo "OK: vector index coverage complete" ;;
|
|
96
|
+
3) echo "FAIL: vector index coverage MISSING — recall is under-returning; run 'switchroom memory repair --all'"; tail -2 /tmp/sr-cov.txt ;;
|
|
97
|
+
4) echo "WARN: coverage NOT verified — the scan confirmed nothing (check the --bank/--schema names, or the hindsight backend)"; tail -2 /tmp/sr-cov.txt ;;
|
|
98
|
+
*) echo "WARN: coverage check could not run (hindsight < 0.8.5, container down, or docker unavailable)"; tail -2 /tmp/sr-cov.txt ;;
|
|
99
|
+
esac
|
|
83
100
|
```
|
|
84
101
|
|
|
85
102
|
## Step 3 — Interpret and report
|
|
@@ -108,6 +125,8 @@ For common failures, give the exact fix:
|
|
|
108
125
|
| Fleet on the wrong account | `switchroom auth use <label>` (fleet-wide) or `switchroom auth agent override <agent> <label>` (one agent) |
|
|
109
126
|
| Container unhealthy | `docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml restart switchroom-<name>` |
|
|
110
127
|
| Missing .mcp.json | `switchroom apply` (full reconcile + rewrite compose; bring up via `docker compose ... up -d`) or `switchroom agent reconcile <name>` (targeted) |
|
|
128
|
+
| Recall returns few/no results though hindsight is up (esp. after a restore or upgrade) | Missing per-bank vector index coverage. `switchroom memory repair --all --dry-run` to confirm, then `switchroom memory repair --all`. Idempotent, uses `CREATE INDEX CONCURRENTLY`, safe on a live fleet. Requires hindsight ≥ 0.8.5. |
|
|
129
|
+
| `switchroom doctor` shows a red/amber `hindsight version` line | The running backend drifted from the MCP contract switchroom captured. Older → bump the pinned image (`switchroom memory --update`). Newer → re-capture `tests/fixtures/hindsight-tools-list.snapshot.json`; until then any tool added upstream is invisible to agents. |
|
|
111
130
|
| Bot token unresolved | Check vault: `switchroom vault list` |
|
|
112
131
|
| Memory unreachable | Check Hindsight MCP server is running |
|
|
113
132
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { QuotaResult, QuotaUtilization } from './quota-check.js';
|
|
22
22
|
import { isProbeThin, refillNormalizedUtils } from '../src/auth/quota.js';
|
|
23
23
|
import type { AccountState, LastQuotaSnapshot, ListStateData } from '../src/auth/broker/client.js';
|
|
24
|
+
import { effectiveServingLabel } from '../src/auth/broker/client.js';
|
|
24
25
|
import { maskEmail } from './demo-mask.js';
|
|
25
26
|
import { escapeMarkdown, codeSpanSafe } from './card-format.js';
|
|
26
27
|
|
|
@@ -1152,12 +1153,16 @@ export function buildSnapshotsFromState(
|
|
|
1152
1153
|
quotas: QuotaResult[],
|
|
1153
1154
|
): AccountSnapshot[] {
|
|
1154
1155
|
const out: AccountSnapshot[] = [];
|
|
1156
|
+
// `isActive` means SERVING, not pinned — see effectiveServingLabel. Reading
|
|
1157
|
+
// `state.active` here marked the rolled-off pin as "(active)" on the /usage
|
|
1158
|
+
// card after every soft-avoid roll.
|
|
1159
|
+
const serving = effectiveServingLabel(state);
|
|
1155
1160
|
for (let i = 0; i < state.accounts.length; i++) {
|
|
1156
1161
|
const acc: AccountState = state.accounts[i]!;
|
|
1157
1162
|
const q = quotas[i];
|
|
1158
1163
|
out.push({
|
|
1159
1164
|
label: acc.label,
|
|
1160
|
-
isActive: acc.label ===
|
|
1165
|
+
isActive: acc.label === serving,
|
|
1161
1166
|
quota: q && q.ok ? q.data : null,
|
|
1162
1167
|
quotaError: q && !q.ok ? q.reason : undefined,
|
|
1163
1168
|
expiresAtMs: acc.expiresAt,
|
|
@@ -1206,11 +1211,13 @@ export function reviveLastQuota(snap: LastQuotaSnapshot | null | undefined): Quo
|
|
|
1206
1211
|
export function buildSnapshotsFromCachedState(
|
|
1207
1212
|
state: ListStateData,
|
|
1208
1213
|
): AccountSnapshot[] {
|
|
1214
|
+
// SERVING, not pinned — same contract as buildSnapshotsFromState.
|
|
1215
|
+
const serving = effectiveServingLabel(state);
|
|
1209
1216
|
return state.accounts.map((acc) => {
|
|
1210
1217
|
const lq = acc.last_quota ?? null;
|
|
1211
1218
|
return {
|
|
1212
1219
|
label: acc.label,
|
|
1213
|
-
isActive: acc.label ===
|
|
1220
|
+
isActive: acc.label === serving,
|
|
1214
1221
|
quota: reviveLastQuota(lq),
|
|
1215
1222
|
quotaError: lq ? undefined : 'no cached quota (no probe since broker start)',
|
|
1216
1223
|
expiresAtMs: acc.expiresAt,
|