switchroom 0.19.23 → 0.19.25
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 +18 -7
- package/dist/auth-broker/index.js +117 -33
- 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 +18 -6
- package/dist/cli/switchroom.js +2916 -1481
- package/dist/host-control/main.js +116 -34
- package/dist/vault/approvals/kernel-server.js +115 -33
- package/dist/vault/broker/server.js +281 -76
- package/examples/switchroom.yaml +1 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +52 -13
- package/profiles/_shared/dev-protocol.md.hbs +3 -4
- package/skills/dev-protocol/SKILL.md +22 -15
- package/skills/switchroom-health/SKILL.md +19 -0
- package/skills/switchroom-release/SKILL.md +2 -1
- package/skills/switchroom-status/SKILL.md +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +9 -2
- package/telegram-plugin/dist/gateway/gateway.js +6925 -6734
- package/telegram-plugin/gateway/gateway.ts +34 -35
- package/telegram-plugin/gateway/latest-turn-lookup.ts +60 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +53 -21
- package/telegram-plugin/gateway/subagent-handback-marker.ts +1 -1
- package/telegram-plugin/gateway/turn-end.ts +1 -1
- package/telegram-plugin/quota-bar-format.ts +4 -1
- package/telegram-plugin/reply-owner-resolve.ts +110 -9
- package/telegram-plugin/send-gate-degraded.test.ts +45 -16
- package/telegram-plugin/send-gate.ts +185 -24
- package/telegram-plugin/tests/activity-card-send-gate.test.ts +9 -9
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +42 -0
- package/telegram-plugin/tests/latest-turn-lookup.test.ts +77 -0
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +23 -1
- package/telegram-plugin/tests/quota-bar-format.test.ts +50 -0
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +531 -0
- package/telegram-plugin/tests/secret-detect-false-positives.test.ts +1 -1
- package/telegram-plugin/tests/send-reply-golden.test.ts +296 -28
- package/telegram-plugin/tests/stream-controller-send-gate.test.ts +134 -28
- package/telegram-plugin/tests/stream-render-golden.test.ts +25 -3
- 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_transcript_fallback.py +25 -8
- package/vendor/hindsight-memory/tests/test_content.py +218 -0
package/examples/switchroom.yaml
CHANGED
|
@@ -249,7 +249,7 @@ agents:
|
|
|
249
249
|
# topic_emoji: "💻"
|
|
250
250
|
# bot_token: "vault:telegram-dev-bot-token" # its own bot
|
|
251
251
|
# extends: coder # inherits from inline profile above
|
|
252
|
-
# model:
|
|
252
|
+
# model: opus # override defaults.model for this agent
|
|
253
253
|
# memory:
|
|
254
254
|
# collection: coding
|
|
255
255
|
# cli_args: ["--effort", "high"] # escape hatch: extra exec claude flags
|
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.19.
|
|
4
|
+
"version": "0.19.25",
|
|
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": {
|
|
@@ -737,19 +737,58 @@ export HINDSIGHT_RECALL_MAX_MEMORIES={{hindsightRecallMaxMemories}}
|
|
|
737
737
|
{{#if (isNumber hindsightRecallCacheTtlSecs)}}
|
|
738
738
|
export HINDSIGHT_RECALL_CACHE_TTL_SECS={{hindsightRecallCacheTtlSecs}}
|
|
739
739
|
{{/if}}
|
|
740
|
-
#
|
|
741
|
-
#
|
|
742
|
-
#
|
|
743
|
-
#
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
#
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
#
|
|
750
|
-
{{
|
|
751
|
-
|
|
752
|
-
|
|
740
|
+
# BM25 term budget for the recall query (#3757). The hook composes a query
|
|
741
|
+
# from the last 2 turns and Hindsight OR-joins EVERY token into one
|
|
742
|
+
# to_tsquery; Postgres native FTS cannot top-k from the GIN index, so it
|
|
743
|
+
# ranks the entire matched set before the top-60 heapsort. Measured on the
|
|
744
|
+
# live `overlord` bank (135,565 rows, 3 fact-type arms): 96 distinct terms
|
|
745
|
+
# = 119,510 rows ranked = 14.0s, versus 24 terms = 48,433 rows = 2.7s.
|
|
746
|
+
# memory.recall.query_max_tokens; 0 disables shaping (rollback lever).
|
|
747
|
+
# Exported UNCONDITIONALLY with a concrete value — see the note on
|
|
748
|
+
# HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS below for why an unexported or
|
|
749
|
+
# empty knob is a silent-shadowing bug rather than a no-op.
|
|
750
|
+
export HINDSIGHT_RECALL_QUERY_MAX_TOKENS={{hindsightRecallQueryMaxTokens}}
|
|
751
|
+
# Bank-specific high-document-frequency terms to drop from the BM25 query
|
|
752
|
+
# (#3757, memory.recall.query_stop_terms). Postgres' `english` config
|
|
753
|
+
# already strips generic English stopwords; this is for words a generic
|
|
754
|
+
# stoplist cannot know are useless in THIS bank — on `overlord`,
|
|
755
|
+
# `switchroom` matched 27,075 rows (20%) and `agent` a similar share.
|
|
756
|
+
# JSON array (the plugin also accepts a comma-separated string), single-
|
|
757
|
+
# quoted so the JSON's double quotes reach the plugin intact. The schema
|
|
758
|
+
# constrains each term to `[\w./-]+`, so it cannot contain a quote. Exported
|
|
759
|
+
# unconditionally: with no operator override this is the literal `[]`, which
|
|
760
|
+
# `_cast_env` parses to an empty list and assigns, so the env still wins over
|
|
761
|
+
# a stale claude-code.json.
|
|
762
|
+
export HINDSIGHT_RECALL_QUERY_STOP_TERMS='{{hindsightRecallQueryStopTermsJson}}'
|
|
763
|
+
# Per-bank HTTP timeout for a single recall request (#3757). Was hardcoded
|
|
764
|
+
# at 8s in the plugin, which fired on 96.8% of overlord's own-bank recalls
|
|
765
|
+
# and returned the model nothing at all. Plugin default is now 12s; the
|
|
766
|
+
# shared multi-bank deadline (recallParallelDeadlineSeconds, default 10s)
|
|
767
|
+
# is normally the tighter bound, so this is the safety net, not the budget.
|
|
768
|
+
# Exported UNCONDITIONALLY, always carrying a concrete value (#3774 defect
|
|
769
|
+
# class). The plugin resolves DEFAULTS -> settings.json -> claude-code.json ->
|
|
770
|
+
# env, so a knob left unexported — or exported empty, which `_cast_env` turns
|
|
771
|
+
# into a skipped assignment — lets a stale hand-written claude-code.json shadow
|
|
772
|
+
# the shipped default with every test still green.
|
|
773
|
+
export HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS={{hindsightRecallRequestTimeoutSeconds}}
|
|
774
|
+
# Per-bank slot FLOORS inside the recall count cap. The merged multi-bank set
|
|
775
|
+
# is sorted globally by relevance then head-sliced, which is winner-take-all
|
|
776
|
+
# across banks: when both banks return more candidates than the cap, one bank's
|
|
777
|
+
# score distribution can fill every slot and the agent gets a dossier about its
|
|
778
|
+
# operator and none of its own session memory. Floors, not quotas — recall.py
|
|
779
|
+
# bounds the two floors to HALF the cap between them, so the rest is always won
|
|
780
|
+
# on pure relevance. Switchroom ships 2 own / 1 additional against the deployed
|
|
781
|
+
# cap of 6; 0/0 restores the pure head-slice.
|
|
782
|
+
#
|
|
783
|
+
# EXPORTED UNCONDITIONALLY (#3774), like the two #3757 knobs above. The resolved
|
|
784
|
+
# effective value (memory.recall.own_bank_min_slots / .additional_bank_min_slots
|
|
785
|
+
# from the cascade, else the shipped default) is always written, because
|
|
786
|
+
# ~/.hindsight/claude-code.json survives an apply and loads AFTER the plugin's
|
|
787
|
+
# settings.json — a conditional export would let a stale hand-edited value there
|
|
788
|
+
# shadow what switchroom stamps, silently and permanently.
|
|
789
|
+
# Observe `injected_own_bank_count` via `switchroom memory recall-log <agent>`.
|
|
790
|
+
export HINDSIGHT_RECALL_OWN_BANK_MIN_SLOTS={{hindsightRecallOwnBankMinSlots}}
|
|
791
|
+
export HINDSIGHT_RECALL_ADDITIONAL_BANK_MIN_SLOTS={{hindsightRecallAdditionalBankMinSlots}}
|
|
753
792
|
# Recall fact types (memory.recall.types cascade). Switchroom default is
|
|
754
793
|
# world,experience,observation (the synthesized `observation` tier is ON
|
|
755
794
|
# by default, set in the plugin settings.json). Export only when the
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
## Development Protocol
|
|
2
2
|
|
|
3
|
-
Judgement criteria for substantive coding, infra, or debugging work.
|
|
3
|
+
Judgement criteria for substantive coding, infra, or debugging work.
|
|
4
4
|
|
|
5
5
|
This governs HOW delegated work is done, and is **not a license to do it inline**: dispatch first (see Delegation), then these bind the worker.
|
|
6
6
|
|
|
7
7
|
- **Orient before you build — validate, don't assume.** Root-cause in real source — the repo's files at HEAD, not `dist/`, caches, or generated output. If the evidence contradicts your theory, the plan, or the task description, report the contradiction rather than force-fitting it. Cite `file:line`, commits, PRs.
|
|
8
8
|
- **Infer before you ask.** Most questions are answerable from the code and history. If genuinely unsure, ask ONE question at a time, during planning; act autonomously during execution — make the reasonable call and note the assumption.
|
|
9
9
|
- **Design report before implementation, when the change is large.** Architecturally significant, cross-cutting, or ambiguous-approach work earns an evidence-grounded design report and a red-team pass before code, and ships as focused single-concern PRs.
|
|
10
|
-
- **
|
|
10
|
+
- **Run `git fetch origin` first, then branch off `origin/main`.** Never branch off the working copy as-is: you do not start in the repo you are coding in, and the checkout on disk may be many commits stale. Scoped tests + lint locally; CI is the full-suite authority.
|
|
11
11
|
- **Adversarial review of the diff.** Read every change as an adversary would: what breaks, what's untested, what's inconsistent.
|
|
12
|
-
- **Blockers and
|
|
13
|
-
- **Re-review only a behavioural fix** — a docs/comment/log/test-only commit doesn't earn one. Prefix a fix commit `review-fix:`; two rounds is the cap, and CI enforces it.
|
|
12
|
+
- **Blockers and majors block the merge; lows don't** — file a low as a follow-up issue rather than fixing it now. Filing is mandatory. Fix what blocks, then merge on CI green. Do not count review rounds and do not run a mandatory re-review pass: verifying your own fix is part of making it, not a separate step.
|
|
14
13
|
- **Durable fixes over hack patches. Deterministic mechanisms over model-dependent behaviour** — if code can enforce a guarantee, don't leave it to prompt discipline. **Tests assert outcomes, not just code paths:** one that wouldn't fail on the bug it guards is not a test.
|
|
15
14
|
- **Communicate as you go.** Consolidated messages — one substantive update beats five fragments; never go dark; no foreground watch over 30 seconds (background it); max 15 parallel sub-agents.
|
|
@@ -80,29 +80,36 @@ fixing lows produces a new diff, and a new diff earned another re-review. It
|
|
|
80
80
|
produced PRs going four rounds where the last round's only finding was an
|
|
81
81
|
inaccurate doc comment. So:
|
|
82
82
|
|
|
83
|
-
- **Blockers and
|
|
84
|
-
- **Lows do NOT block.**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- **
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
is on the PR.
|
|
83
|
+
- **Blockers and majors block the merge.** Fix them.
|
|
84
|
+
- **Lows do NOT block.** File a low as a follow-up issue rather than fixing it
|
|
85
|
+
now. Filing is mandatory — an unfiled low is a dropped bug, and there is no
|
|
86
|
+
human team to catch it later.
|
|
87
|
+
- **Fix what blocks, then merge on CI green.**
|
|
88
|
+
- **Do not count review rounds, and do not run a mandatory re-review pass.**
|
|
89
|
+
Verifying your own fix is part of making it, not a separate step. Counting
|
|
90
|
+
rounds was itself a loop driver: it made the review process the subject of
|
|
91
|
+
the work instead of the change.
|
|
93
92
|
|
|
94
93
|
If a finding is genuinely invalid, rebut it with evidence in writing; silence
|
|
95
94
|
is not a rebuttal.
|
|
96
95
|
|
|
97
|
-
When you do re-review, the verdict states per original finding: the finding
|
|
98
|
-
ID, what changed (`file:line` of the fix), whether it fully resolves the
|
|
99
|
-
finding (`RESOLVED` / `PARTIAL` / `REBUTTED` with evidence), and whether the
|
|
100
|
-
fix introduced anything new. A bare "fixed" is not a verdict.
|
|
101
|
-
|
|
102
96
|
## 5. Non-obvious pipeline rules
|
|
103
97
|
|
|
104
98
|
- **CI is the full-suite authority.** Local scoped tests are a fast filter,
|
|
105
99
|
never the merge evidence. Never claim done off a local run alone.
|
|
100
|
+
- **`main` is behind a merge queue: `gh pr merge` ENQUEUES, it does not
|
|
101
|
+
merge.** The command exits 0 and the PR stays `OPEN`. The queue then re-runs
|
|
102
|
+
all seven required contexts on its own `gh-readonly-queue/main/pr-<n>-<sha>`
|
|
103
|
+
ref before landing anything, so "green on the PR" is necessary but not
|
|
104
|
+
sufficient. Never report a PR merged off that exit code — poll until its
|
|
105
|
+
state is `MERGED` and the commit is an ancestor of `origin/main`. Two flags
|
|
106
|
+
to know: `--delete-branch` is **rejected outright** while the queue is on
|
|
107
|
+
(delete the branch after it lands), and `--subject` is ignored, since the
|
|
108
|
+
queue composes the merge commit from the PR title plus `(#<pr>)` — so the PR
|
|
109
|
+
title is the commit title, write it accordingly. An entry stuck in
|
|
110
|
+
`AWAITING_CHECKS` means a required workflow is not listening for
|
|
111
|
+
`merge_group`; `.github/MERGE-QUEUE.md` owns that failure mode and the
|
|
112
|
+
invariants that prevent it.
|
|
106
113
|
- **A test that wouldn't fail on the bug it guards is not a test.** Assert the
|
|
107
114
|
observable outcome, not that the code path executed.
|
|
108
115
|
- **Prefer a deterministic mechanism over prompt discipline.** If a check, a
|
|
@@ -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
|
|
|
@@ -100,7 +100,8 @@ The tag push triggers `docker-images` and `release`. `release` internally waits
|
|
|
100
100
|
|
|
101
101
|
### Gate B — the release pipeline (`release.yml`)
|
|
102
102
|
- `gh run list --workflow=release.yml --limit 1` — wait for `completed` / `success`. Expect ~25-30 minutes: four native build legs plus the wait on `docker-images`.
|
|
103
|
-
- Its jobs, in order: `guard` (release exists + held out of `latest`) → `build` ×4 → `bundle` → `publish` (attach) → `images-gate` (wait on docker-images) → `npm` → `finalize` (un-draft). A red job anywhere leaves the release a **draft** and npm **unpublished** — which is the correct, recoverable state.
|
|
103
|
+
- Its jobs, in order: `guard` (release exists + held out of `latest`) → `build` ×4 → `bundle` → `publish` (attach) → `images-gate` (wait on docker-images) → `npm` → `finalize` (un-draft) → `images-latest` (promote `:vX.Y.Z` → `:latest`). A red job anywhere leaves the release a **draft** and npm **unpublished** — which is the correct, recoverable state.
|
|
104
|
+
- `images-latest` is why a tag push no longer moves the `:latest` image tag by itself (#3685). If that last job is the one that failed, every other leg shipped and only the image tag lags: re-run it with `gh workflow run promote.yml -f from=vX.Y.Z -f to=latest`. Don't roll the fleet off `:latest` until it is green — `docker manifest inspect ghcr.io/switchroom/switchroom-agent:latest` should report the same digest as `:vX.Y.Z`.
|
|
104
105
|
- Verify the release page actually has assets **and is no longer a draft**:
|
|
105
106
|
```bash
|
|
106
107
|
gh release view vX.Y.Z -R switchroom/switchroom --json isDraft,assets \
|
|
@@ -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,
|