switchroom 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/agent-scheduler/index.js +29 -1
  2. package/dist/auth-broker/index.js +552 -48
  3. package/dist/cli/autoaccept-poll.js +29 -1
  4. package/dist/cli/drive-write-pretool.mjs +30 -2
  5. package/dist/cli/ms-365-write-pretool.mjs +30 -2
  6. package/dist/cli/switchroom.js +751 -36
  7. package/dist/host-control/main.js +3 -3
  8. package/dist/vault/approvals/kernel-server.js +2 -2
  9. package/dist/vault/broker/server.js +2 -2
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +1 -0
  12. package/skills/switchroom-cli/SKILL.md +25 -0
  13. package/telegram-plugin/auth-snapshot-format.ts +39 -0
  14. package/telegram-plugin/dist/gateway/gateway.js +363 -25
  15. package/telegram-plugin/external-spend.ts +135 -0
  16. package/telegram-plugin/gateway/gateway.ts +83 -67
  17. package/telegram-plugin/gateway/model-command.ts +106 -0
  18. package/telegram-plugin/gateway/narrative-lane.ts +23 -9
  19. package/telegram-plugin/gateway/status-pin-store.ts +64 -4
  20. package/telegram-plugin/gateway/usage-mask.ts +29 -0
  21. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
  22. package/telegram-plugin/quota-bar-format.ts +18 -0
  23. package/telegram-plugin/quota-check.ts +17 -2
  24. package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
  25. package/telegram-plugin/tests/external-spend.test.ts +168 -0
  26. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +57 -23
  27. package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
  28. package/telegram-plugin/tests/quota-check.test.ts +57 -0
  29. package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
  30. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
  31. package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
  32. package/telegram-plugin/tests/usage-mask.test.ts +35 -0
  33. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
  34. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +131 -1
@@ -7011,7 +7011,7 @@ var require_dist = __commonJS((exports) => {
7011
7011
  });
7012
7012
 
7013
7013
  // src/util/atomic.ts
7014
- import { closeSync, constants, fsyncSync, openSync, renameSync as renameSync2, rmSync, writeSync } from "node:fs";
7014
+ import { closeSync, constants, fchmodSync, fchownSync, fsyncSync, openSync, renameSync as renameSync2, rmSync, writeSync } from "node:fs";
7015
7015
  var TMP_OPEN_FLAGS;
7016
7016
  var init_atomic = __esm(() => {
7017
7017
  TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
@@ -20732,7 +20732,7 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
20732
20732
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
20733
20733
 
20734
20734
  // src/memory/hindsight.ts
20735
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
20735
+ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made.";
20736
20736
  var PROFILE_MEMORY_DEFAULTS = {
20737
20737
  "health-coach": {
20738
20738
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -26663,7 +26663,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
26663
26663
  import { dirname as dirname4, join as join7 } from "node:path";
26664
26664
 
26665
26665
  // src/build-info.ts
26666
- var VERSION = "0.19.0";
26666
+ var VERSION = "0.19.2";
26667
26667
 
26668
26668
  // src/cli/resolve-version.ts
26669
26669
  function readPackageVersion() {
@@ -4857,7 +4857,7 @@ var init_schema = __esm(() => {
4857
4857
  });
4858
4858
 
4859
4859
  // src/util/atomic.ts
4860
- import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4860
+ import { closeSync, constants, fchmodSync, fchownSync, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4861
4861
  var TMP_OPEN_FLAGS;
4862
4862
  var init_atomic = __esm(() => {
4863
4863
  TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
@@ -19117,7 +19117,7 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
19117
19117
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
19118
19118
 
19119
19119
  // src/memory/hindsight.ts
19120
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
19120
+ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made.";
19121
19121
  var PROFILE_MEMORY_DEFAULTS = {
19122
19122
  "health-coach": {
19123
19123
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -4857,7 +4857,7 @@ var init_schema = __esm(() => {
4857
4857
  });
4858
4858
 
4859
4859
  // src/util/atomic.ts
4860
- import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4860
+ import { closeSync, constants, fchmodSync, fchownSync, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4861
4861
  var TMP_OPEN_FLAGS;
4862
4862
  var init_atomic = __esm(() => {
4863
4863
  TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
@@ -18825,7 +18825,7 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
18825
18825
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
18826
18826
 
18827
18827
  // src/memory/hindsight.ts
18828
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
18828
+ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made.";
18829
18829
  var PROFILE_MEMORY_DEFAULTS = {
18830
18830
  "health-coach": {
18831
18831
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
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.0",
4
+ "version": "0.19.2",
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": {
@@ -28,7 +28,7 @@
28
28
  "test:vitest": "vitest run",
29
29
  "test:bun": "bun test telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
30
30
  "test:watch": "vitest",
31
- "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs",
31
+ "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-litellm-config-guard.mjs",
32
32
  "lint:tsc": "tsc --noEmit",
33
33
  "lint:plugin-references": "node scripts/check-plugin-references.mjs",
34
34
  "lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
@@ -37,6 +37,7 @@
37
37
  "lint:web-subscription-honest": "node scripts/check-web-subscription-honest.mjs",
38
38
  "lint:no-broadcast-delivery": "node scripts/check-no-broadcast-delivery.mjs",
39
39
  "lint:gateway-line-ratchet": "node scripts/check-gateway-line-ratchet.mjs",
40
+ "lint:litellm-config-guard": "node scripts/check-litellm-config-guard.mjs",
40
41
  "prepublishOnly": "npm run build && npm run lint && npm test"
41
42
  },
42
43
  "dependencies": {
@@ -1211,6 +1211,7 @@ fi
1211
1211
 
1212
1212
  # --- Session model resolution (consume-once .session-model carrier) ---
1213
1213
  #
1214
+ # Doctor (checkStartShSessionModelCarrier) and tests pin this path against gateway SESSION_MODEL_FILE — regenerate via `switchroom apply` if fleet start.sh drifts.
1214
1215
  # Contract: reference/rfcs/session-model-stickiness.md §0.05 (rev 5, operator
1215
1216
  # decision 2026-07-16 — DETERMINISTIC SWITCH, superseding rev 4 §0.1's "live
1216
1217
  # Claude switches write no carrier"). A `/model` override lasts only for the
@@ -263,6 +263,31 @@ If the user asks whether scheduled runs were missed during downtime: the schedul
263
263
 
264
264
  ---
265
265
 
266
+ ## Hindsight model change — "swap the memory model", "move retain off the subscription"
267
+
268
+ Changing which LLM runs Hindsight's retain / reflect / consolidation ops is a
269
+ config-plus-recreate operation with three known traps. **Follow the runbook:
270
+ `docs/operators/hindsight-model-change.md`.** The short version:
271
+
272
+ 1. Edit `hindsight.llm.{retain,reflect,consolidation}` in the operator's
273
+ `switchroom.yaml` — keep the outgoing block in a dated comment for rollback.
274
+ 2. Pick a full lane: non-Claude via LiteLLM needs `provider: litellm` +
275
+ `model` + `base_url` + `api_key`; subscription Claude needs
276
+ `provider: claude-code` + a Claude model name with NO base_url/api_key.
277
+ A per-op `model:` alone does **not** reroute (docs/model-routing.md G1/G5).
278
+ 3. Apply with `switchroom memory setup --recreate` **on the host** — a plain
279
+ `docker restart` does not re-derive env, and running the CLI inside an
280
+ agent container silently reads the stale `SWITCHROOM_CONFIG` snapshot.
281
+ 4. Verify with `docker inspect switchroom-hindsight`:
282
+ `HINDSIGHT_API_<OP>_LLM_MODEL` / `_PROVIDER` / `_BASE_URL` match the yaml,
283
+ and health reaches `healthy`.
284
+ 5. `docker restart switchroom-hostd` after the yaml edit, and **re-verify after
285
+ any fleet rollout** — the singletons bind-mount the yaml as a single file,
286
+ so an atomic (rename-based) edit leaves them pinned to the stale old inode
287
+ and a hostd-driven rollout can silently revert the model change.
288
+
289
+ ---
290
+
266
291
  ## Telegram plugin reference — "what MCP tools", "how does reply work"
267
292
 
268
293
  The `switchroom-telegram` plugin is an enhanced fork of the official Telegram MCP plugin and is the default for all switchroom agents. It exposes **9 MCP tools** (all prefixed `mcp__switchroom-telegram__`):
@@ -1011,6 +1011,45 @@ export function zipProbeResults(
1011
1011
  return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
1012
1012
  }
1013
1013
 
1014
+ /**
1015
+ * The freshness-marker subset of the /usage card's render opts. Mirrors the
1016
+ * three mutually-prioritised fields on `UsageCardRenderOpts`
1017
+ * (quota-bar-format.ts) without importing across the module — cache-stale >
1018
+ * live > probe-failed.
1019
+ */
1020
+ export interface UsageFooterFreshness {
1021
+ staleCachedAtMs?: number;
1022
+ liveProbedAtMs?: number;
1023
+ probeFailed?: boolean;
1024
+ }
1025
+
1026
+ /**
1027
+ * Decide the /usage card's freshness footer from a broker probe response.
1028
+ *
1029
+ * Adversarial-review F1: the caller must NEVER stamp "Live" when no account
1030
+ * row carries usable live data. A non-empty `results` array is NOT proof of
1031
+ * that — opProbeQuota (src/auth/broker/server.ts) returns
1032
+ * `{result:{ok:false}, served:"live"}` for a failed live probe against an
1033
+ * empty cache, so every row can be `ok:false` while `results.length > 0`.
1034
+ * `zipProbeResults` only marks `served==="cache"` rows stale, so those failed
1035
+ * rows carry no cache stamp either. The honest signal is whether ANY row's
1036
+ * `result.ok` is true.
1037
+ *
1038
+ * Precedence (highest first): cache-served data (`staleCachedAtMs`, real but
1039
+ * stale) → live data present (`liveProbedAtMs`) → nothing usable
1040
+ * (`probeFailed`). Extracted so the decision is unit-testable and can't drift
1041
+ * back to a length check.
1042
+ */
1043
+ export function deriveUsageFooterFreshness(
1044
+ results: readonly ProbeQuotaResultRow[],
1045
+ staleCachedAtMs: number | undefined,
1046
+ liveProbedAtMs: number,
1047
+ ): UsageFooterFreshness {
1048
+ if (staleCachedAtMs != null) return { staleCachedAtMs };
1049
+ if (results.some((r) => r.result.ok)) return { liveProbedAtMs };
1050
+ return { probeFailed: true };
1051
+ }
1052
+
1014
1053
  /**
1015
1054
  * Given the broker's `listState` data + a parallel array of live quota
1016
1055
  * results (same length, same order), return the AccountSnapshot[] the