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.
- package/dist/agent-scheduler/index.js +29 -1
- package/dist/auth-broker/index.js +552 -48
- package/dist/cli/autoaccept-poll.js +29 -1
- package/dist/cli/drive-write-pretool.mjs +30 -2
- package/dist/cli/ms-365-write-pretool.mjs +30 -2
- package/dist/cli/switchroom.js +751 -36
- package/dist/host-control/main.js +3 -3
- package/dist/vault/approvals/kernel-server.js +2 -2
- package/dist/vault/broker/server.js +2 -2
- package/package.json +3 -2
- package/profiles/_base/start.sh.hbs +1 -0
- package/skills/switchroom-cli/SKILL.md +25 -0
- package/telegram-plugin/auth-snapshot-format.ts +39 -0
- package/telegram-plugin/dist/gateway/gateway.js +363 -25
- package/telegram-plugin/external-spend.ts +135 -0
- package/telegram-plugin/gateway/gateway.ts +83 -67
- package/telegram-plugin/gateway/model-command.ts +106 -0
- package/telegram-plugin/gateway/narrative-lane.ts +23 -9
- package/telegram-plugin/gateway/status-pin-store.ts +64 -4
- package/telegram-plugin/gateway/usage-mask.ts +29 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
- package/telegram-plugin/quota-bar-format.ts +18 -0
- package/telegram-plugin/quota-check.ts +17 -2
- package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
- package/telegram-plugin/tests/external-spend.test.ts +168 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +57 -23
- package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
- package/telegram-plugin/tests/quota-check.test.ts +57 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
- package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
- package/telegram-plugin/tests/usage-mask.test.ts +35 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
- 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.
|
|
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.
|
|
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
|