switchroom 0.19.40 → 0.19.42

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 (37) hide show
  1. package/dist/agent-scheduler/index.js +22 -2
  2. package/dist/auth-broker/index.js +22 -2
  3. package/dist/cli/notion-write-pretool.mjs +22 -2
  4. package/dist/cli/switchroom.js +810 -241
  5. package/dist/host-control/main.js +94 -19
  6. package/dist/vault/approvals/kernel-server.js +22 -2
  7. package/dist/vault/broker/server.js +22 -2
  8. package/package.json +1 -1
  9. package/telegram-plugin/bridge/bridge.ts +2 -2
  10. package/telegram-plugin/dist/bridge/bridge.js +2 -2
  11. package/telegram-plugin/dist/gateway/gateway.js +332 -124
  12. package/telegram-plugin/dist/server.js +2 -2
  13. package/telegram-plugin/gateway/approval-callback-consume-record.test.ts +132 -0
  14. package/telegram-plugin/gateway/checklist-fallback.ts +370 -0
  15. package/telegram-plugin/gateway/gateway.ts +76 -76
  16. package/telegram-plugin/gateway/liveness-wiring.ts +12 -0
  17. package/telegram-plugin/gateway/model-command.ts +21 -109
  18. package/telegram-plugin/gateway/stream-render.ts +57 -0
  19. package/telegram-plugin/gateway/turn-record-status.ts +80 -0
  20. package/telegram-plugin/tests/checklist-fallback.test.ts +317 -0
  21. package/telegram-plugin/tests/framework-fallback-drains-parked.test.ts +134 -0
  22. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -6
  23. package/telegram-plugin/tests/helpers/liveness-wiring-fixture.ts +3 -0
  24. package/telegram-plugin/tests/turn-record-status.test.ts +62 -0
  25. package/vendor/hindsight-memory/CHANGELOG.md +28 -0
  26. package/vendor/hindsight-memory/docs/measurements/subagent-volume-gate-3994.md +84 -0
  27. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +254 -40
  28. package/vendor/hindsight-memory/scripts/lib/content.py +11 -2
  29. package/vendor/hindsight-memory/scripts/recall.py +18 -3
  30. package/vendor/hindsight-memory/scripts/subagent_retain.py +59 -38
  31. package/vendor/hindsight-memory/scripts/tests/data/replay_volume_gate_3994.py +295 -0
  32. package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +109 -0
  33. package/vendor/hindsight-memory/scripts/tests/test_overlap_tokens.py +135 -0
  34. package/vendor/hindsight-memory/scripts/tests/test_recall_no_lexical_gate.py +20 -23
  35. package/vendor/hindsight-memory/scripts/tests/test_recall_query_shaping.py +48 -0
  36. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +94 -1
  37. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain_learnings.py +98 -42
@@ -14354,11 +14354,11 @@ var init_protocol2 = __esm(() => {
14354
14354
  OkRevokeGrantResponseSchema,
14355
14355
  OkApprovalRequestResponseSchema,
14356
14356
  OkApprovalLookupResponseSchema,
14357
+ OkApprovalConsumeRecordResponseSchema,
14357
14358
  OkApprovalConsumeResponseSchema,
14358
14359
  OkApprovalRevokeResponseSchema,
14359
14360
  OkApprovalListResponseSchema,
14360
14361
  OkApprovalRecordResponseSchema,
14361
- OkApprovalConsumeRecordResponseSchema,
14362
14362
  ErrorResponseSchema2
14363
14363
  ]);
14364
14364
  });
@@ -21865,6 +21865,7 @@ var init_schema = __esm(() => {
21865
21865
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
21866
21866
  file: exports_external.boolean().default(true).describe("Maintain a curated workspace MEMORY.md file (seeded once, " + "auto-loaded every turn). Set false for hindsight-only memory: " + "the file is not seeded or re-created, so once migrated into " + "Hindsight and deleted it stays gone. Recall + directives carry " + "the memory instead. Cascade: override (per-agent wins over default)."),
21867
21867
  isolation: exports_external.enum(["default", "strict"]).default("default").describe("strict = never shared cross-agent, default = eligible for reflect"),
21868
+ profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off \u2014 " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile \u2192 extends \u2192 DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset \u21d2 byte-identical to keying off `extends`."),
21868
21869
  bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
21869
21870
  reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
21870
21871
  retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
@@ -22158,6 +22159,8 @@ var init_schema = __esm(() => {
22158
22159
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
22159
22160
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
22160
22161
  context_window: exports_external.number().int().positive().optional().describe("GLOBAL context window (tokens) of the backend serving hindsight's " + "LLM ops \u2014 the declared size switchroom derives every token budget " + "from. Set this to the real window of whatever you point " + "`hindsight.llm` at (e.g. 32768 for a llama.cpp slot launched with " + "`-c 65536 -np 2`, 131072 for a large-window OpenRouter model). " + "Absent \u2192 a per-provider default: 200000 for `claude-code`, a " + "conservative 32768 for everything else. Overflowing a local " + "backend's window does NOT error \u2014 llama.cpp context-shift silently " + "drops the system prompt and the model answers conversationally " + "with HTTP 200 \u2014 so this value is what makes the failure " + "detectable at setup time instead of never."),
22162
+ base_url: exports_external.string().min(1).optional().describe("GLOBAL LLM base URL (upstream `HINDSIGHT_API_LLM_BASE_URL`). The " + "default endpoint every op inherits absent a per-op " + "`hindsight.llm.<op>.base_url`. Optional passthrough; unset \u2192 the " + "engine's provider default. When this points at host loopback the " + "hindsight container is forced onto host networking, same as a " + "per-op base URL, so the endpoint stays reachable (#3687)."),
22163
+ api_key: exports_external.string().min(1).optional().describe("GLOBAL LLM API key (upstream `HINDSIGHT_API_LLM_API_KEY`). Literal " + "or `vault:` reference. The default credential every op inherits " + "absent a per-op `hindsight.llm.<op>.api_key`; the engine reads it as " + "a plain env fallback. Optional passthrough (#3687)."),
22161
22164
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent \u2192 uses the global model/provider."),
22162
22165
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
22163
22166
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
@@ -22294,6 +22297,7 @@ var init_schema = __esm(() => {
22294
22297
  auto_recall: exports_external.boolean().optional(),
22295
22298
  file: exports_external.boolean().optional(),
22296
22299
  isolation: exports_external.enum(["default", "strict"]).optional(),
22300
+ profile: exports_external.string().optional(),
22297
22301
  directive_capture_nudge: exports_external.boolean().optional(),
22298
22302
  observation_scopes: ObservationScopesSchema,
22299
22303
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -22335,7 +22339,23 @@ var init_schema = __esm(() => {
22335
22339
  max_query_chars: exports_external.number().int().min(1).optional(),
22336
22340
  parallel: exports_external.boolean().optional(),
22337
22341
  additional_banks: exports_external.array(exports_external.string()).optional(),
22338
- sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
22342
+ sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional(),
22343
+ types: exports_external.array(exports_external.string()).optional(),
22344
+ skip_trivial: exports_external.boolean().optional(),
22345
+ topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional()
22346
+ }).optional(),
22347
+ retain: exports_external.object({
22348
+ every_n_turns: exports_external.number().int().min(1).optional(),
22349
+ overlap_turns: exports_external.number().int().min(0).optional()
22350
+ }).optional(),
22351
+ bank_mission: exports_external.string().optional(),
22352
+ reflect_mission: exports_external.string().optional(),
22353
+ retain_mission: exports_external.string().optional(),
22354
+ observations_mission: exports_external.string().optional(),
22355
+ disposition: exports_external.object({
22356
+ skepticism: exports_external.number().int().min(1).max(5).optional(),
22357
+ literalism: exports_external.number().int().min(1).max(5).optional(),
22358
+ empathy: exports_external.number().int().min(1).max(5).optional()
22339
22359
  }).optional()
22340
22360
  }).optional(),
22341
22361
  schedule: exports_external.array(ScheduleEntrySchema).optional(),
@@ -39504,6 +39524,178 @@ function redactChecklistFields(title, tasks, redact) {
39504
39524
  };
39505
39525
  }
39506
39526
 
39527
+ // gateway/checklist-fallback.ts
39528
+ var CHECKLIST_MAX_TASKS = 30;
39529
+ function buildChecklistTasks(tasks) {
39530
+ if (tasks.length > CHECKLIST_MAX_TASKS) {
39531
+ throw new Error(`checklist exceeds ${CHECKLIST_MAX_TASKS}-task limit (got ${tasks.length})`);
39532
+ }
39533
+ return tasks.map((t, i) => ({ id: i + 1, text: t.text, done: t.done === true }));
39534
+ }
39535
+ function renderChecklistText(cl, opts) {
39536
+ const header = opts?.literal ? cl.title : `**${cl.title}**`;
39537
+ const lines = cl.tasks.map((t) => `${t.done ? "\u2705" : "\u2b1c"} ${t.text}`);
39538
+ return [header, ...lines].join(`
39539
+ `);
39540
+ }
39541
+ function applyChecklistPatch(cl, patch) {
39542
+ const tasks = cl.tasks.map((t) => ({ ...t }));
39543
+ for (const p of patch.tasks ?? []) {
39544
+ const pid = p.id != null ? Number(p.id) : undefined;
39545
+ const existing = pid != null ? tasks.find((t) => t.id === pid) : undefined;
39546
+ if (existing) {
39547
+ if (p.text != null)
39548
+ existing.text = p.text;
39549
+ if (p.done != null)
39550
+ existing.done = p.done;
39551
+ } else {
39552
+ const nextId = pid != null && Number.isFinite(pid) ? pid : tasks.reduce((m, t) => Math.max(m, t.id), 0) + 1;
39553
+ tasks.push({ id: nextId, text: p.text ?? "", done: p.done === true });
39554
+ }
39555
+ }
39556
+ if (tasks.length > CHECKLIST_MAX_TASKS) {
39557
+ throw new Error(`checklist exceeds ${CHECKLIST_MAX_TASKS}-task limit (got ${tasks.length})`);
39558
+ }
39559
+ return { title: patch.title ?? cl.title, tasks };
39560
+ }
39561
+ function buildNativeChecklistPayload(p) {
39562
+ return {
39563
+ business_connection_id: p.businessConnectionId,
39564
+ chat_id: p.chatId,
39565
+ checklist: {
39566
+ title: p.title,
39567
+ tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
39568
+ },
39569
+ ...p.replyToMessageId != null ? { reply_parameters: { message_id: p.replyToMessageId } } : {},
39570
+ ...p.protectContent === true ? { protect_content: true } : {}
39571
+ };
39572
+ }
39573
+ function buildNativeEditChecklistPayload(p) {
39574
+ return {
39575
+ business_connection_id: p.businessConnectionId,
39576
+ chat_id: p.chatId,
39577
+ message_id: p.messageId,
39578
+ checklist: {
39579
+ title: p.title,
39580
+ tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
39581
+ }
39582
+ };
39583
+ }
39584
+ function createChecklistStore(cap = 500) {
39585
+ const map = new Map;
39586
+ return {
39587
+ get: (key) => map.get(key),
39588
+ set: (key, state) => {
39589
+ if (map.has(key))
39590
+ map.delete(key);
39591
+ map.set(key, state);
39592
+ while (map.size > cap) {
39593
+ const oldest = map.keys().next().value;
39594
+ if (oldest == null)
39595
+ break;
39596
+ map.delete(oldest);
39597
+ }
39598
+ }
39599
+ };
39600
+ }
39601
+ function checklistStoreKey(chatId, messageId) {
39602
+ return `${chatId}:${messageId}`;
39603
+ }
39604
+ async function performSendChecklist(deps, args) {
39605
+ const tasks = buildChecklistTasks(args.tasks);
39606
+ if (deps.nativeAvailable && deps.businessConnectionId) {
39607
+ try {
39608
+ const sent2 = await deps.sendNative(buildNativeChecklistPayload({
39609
+ businessConnectionId: deps.businessConnectionId,
39610
+ chatId: args.chatId,
39611
+ title: args.title,
39612
+ tasks,
39613
+ replyToMessageId: args.replyToMessageId,
39614
+ protectContent: args.protectContent
39615
+ }));
39616
+ return {
39617
+ message_id: sent2.message_id,
39618
+ mode: "native",
39619
+ state: { title: args.title, tasks, mode: "native" }
39620
+ };
39621
+ } catch (err) {
39622
+ deps.log(`native sendChecklist failed \u2014 falling back to text render: ${err}
39623
+ `);
39624
+ }
39625
+ }
39626
+ const text = renderChecklistText({ title: args.title, tasks }, { literal: deps.literalText });
39627
+ const sent = await deps.sendText(text);
39628
+ return {
39629
+ message_id: sent.message_id,
39630
+ mode: "text",
39631
+ state: { title: args.title, tasks, mode: "text" }
39632
+ };
39633
+ }
39634
+ function sendChecklistToolText(r) {
39635
+ return JSON.stringify(r.mode === "native" ? { ok: true, message_id: r.message_id, mode: "native" } : {
39636
+ ok: true,
39637
+ message_id: r.message_id,
39638
+ mode: "text",
39639
+ degraded: "text",
39640
+ note: "rendered as a formatted text message (native Telegram checklists require a Business connection) \u2014 tasks are not tappable; use update_checklist with task ids 1..N to tick them"
39641
+ });
39642
+ }
39643
+ async function performUpdateChecklist(deps, patch) {
39644
+ let base = deps.state;
39645
+ if (!base) {
39646
+ const fullReplacement = patch.title != null && (patch.tasks?.length ?? 0) > 0 && (patch.tasks ?? []).every((t) => t.text != null);
39647
+ if (!fullReplacement) {
39648
+ return {
39649
+ ok: false,
39650
+ reason: "unknown_checklist",
39651
+ hint: "no stored state for this checklist (gateway restarted?). Re-send it with send_checklist, or pass a full replacement (title + every task's text) to update_checklist."
39652
+ };
39653
+ }
39654
+ base = { title: patch.title, tasks: [], mode: "text" };
39655
+ }
39656
+ let next;
39657
+ try {
39658
+ next = applyChecklistPatch(base, patch);
39659
+ } catch (err) {
39660
+ return { ok: false, reason: "edit_failed", hint: String(err) };
39661
+ }
39662
+ const state = { title: next.title, tasks: next.tasks, mode: base.mode };
39663
+ if (base.mode === "native") {
39664
+ if (!deps.nativeAvailable || !deps.businessConnectionId) {
39665
+ return {
39666
+ ok: false,
39667
+ reason: "edit_failed",
39668
+ hint: "this checklist was sent natively but the business connection / checklist API is no longer available."
39669
+ };
39670
+ }
39671
+ try {
39672
+ await deps.editNative(buildNativeEditChecklistPayload({
39673
+ businessConnectionId: deps.businessConnectionId,
39674
+ chatId: deps.chatId,
39675
+ messageId: deps.messageId,
39676
+ title: state.title,
39677
+ tasks: state.tasks
39678
+ }));
39679
+ return { ok: true, mode: "native", state };
39680
+ } catch (err) {
39681
+ deps.log(`native editMessageChecklist failed: ${err}
39682
+ `);
39683
+ return { ok: false, reason: "edit_failed", hint: String(err) };
39684
+ }
39685
+ }
39686
+ try {
39687
+ await deps.editText(renderChecklistText(state, { literal: deps.literalText }));
39688
+ return { ok: true, mode: "text", state };
39689
+ } catch (err) {
39690
+ deps.log(`checklist text edit failed: ${err}
39691
+ `);
39692
+ return { ok: false, reason: "edit_failed", hint: String(err) };
39693
+ }
39694
+ }
39695
+ function updateChecklistToolText(r, messageId) {
39696
+ return JSON.stringify(r.ok ? { ok: true, message_id: messageId, mode: r.mode, ...r.mode === "text" ? { degraded: "text" } : {} } : { ok: false, reason: r.reason, hint: r.hint });
39697
+ }
39698
+
39507
39699
  // interrupt-marker.ts
39508
39700
  function parseInterruptMarker(text) {
39509
39701
  const trimmed = text.trimStart();
@@ -45390,12 +45582,42 @@ function assessThinkingEffortRisk(model, effort) {
45390
45582
  };
45391
45583
  }
45392
45584
 
45393
- // gateway/model-command.ts
45394
- var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
45585
+ // ../src/agents/model-aliases.ts
45395
45586
  var CLAUDE_MODEL_ALIASES = {
45396
45587
  opus48: "claude-opus-4-8",
45397
45588
  "opus-4-8": "claude-opus-4-8"
45398
45589
  };
45590
+ var SR_MODEL_ALIASES = {
45591
+ flash: "sr-gemini-2.5-flash",
45592
+ gemini: "sr-gemini-2.5-pro",
45593
+ deepseek: "sr-deepseek-v3",
45594
+ r1: "sr-deepseek-r1",
45595
+ glm: "sr-glm-5",
45596
+ codex: "sr-codex-5.5",
45597
+ grok: "sr-grok-4.5",
45598
+ kimi: "sr-kimi-k3",
45599
+ gpt: "sr-gpt-5.6-sol",
45600
+ sol: "sr-gpt-5.6-sol",
45601
+ terra: "sr-gpt-5.6-terra",
45602
+ luna: "sr-gpt-5.6-luna"
45603
+ };
45604
+ function expandSrAlias(arg) {
45605
+ return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
45606
+ }
45607
+ function expandClaudeAlias(arg) {
45608
+ return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
45609
+ }
45610
+ function canonicalModelToken(arg) {
45611
+ const trimmed = arg.trim();
45612
+ const lower = trimmed.toLowerCase();
45613
+ return lower.startsWith("claude-") ? lower : trimmed;
45614
+ }
45615
+ function expandModelAlias(arg) {
45616
+ return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
45617
+ }
45618
+
45619
+ // gateway/model-command.ts
45620
+ var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
45399
45621
  var MODEL_ARG_RE = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
45400
45622
  function isValidModelArg(arg) {
45401
45623
  return MODEL_ARG_RE.test(arg);
@@ -45595,34 +45817,6 @@ var SR_MODEL_LABELS = {
45595
45817
  "sr-gpt-5.6-terra": "GPT-5.6 Terra",
45596
45818
  "sr-gpt-5.6-luna": "GPT-5.6 Luna"
45597
45819
  };
45598
- var SR_MODEL_ALIASES = {
45599
- flash: "sr-gemini-2.5-flash",
45600
- gemini: "sr-gemini-2.5-pro",
45601
- deepseek: "sr-deepseek-v3",
45602
- r1: "sr-deepseek-r1",
45603
- glm: "sr-glm-5",
45604
- codex: "sr-codex-5.5",
45605
- grok: "sr-grok-4.5",
45606
- kimi: "sr-kimi-k3",
45607
- gpt: "sr-gpt-5.6-sol",
45608
- sol: "sr-gpt-5.6-sol",
45609
- terra: "sr-gpt-5.6-terra",
45610
- luna: "sr-gpt-5.6-luna"
45611
- };
45612
- function expandSrAlias(arg) {
45613
- return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
45614
- }
45615
- function canonicalModelToken(arg) {
45616
- const trimmed = arg.trim();
45617
- const lower = trimmed.toLowerCase();
45618
- return lower.startsWith("claude-") ? lower : trimmed;
45619
- }
45620
- function expandClaudeAlias(arg) {
45621
- return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
45622
- }
45623
- function expandModelAlias(arg) {
45624
- return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
45625
- }
45626
45820
  function srFriendlyLabel(srName) {
45627
45821
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
45628
45822
  }
@@ -80216,6 +80410,25 @@ function discardParkedTurnStart(rawContent) {
80216
80410
  }
80217
80411
  return null;
80218
80412
  }
80413
+ function drainParkedTurnStartsForChat(deps, chatId, threadId) {
80414
+ const wantThread = envThreadIdNum(threadId);
80415
+ const drained = [];
80416
+ for (let i = 0;i < parkedTurnStarts.length; ) {
80417
+ const entry = parkedTurnStarts[i];
80418
+ if (entry.chatId === chatId && envThreadIdNum(entry.threadId) === wantThread) {
80419
+ drained.push(entry);
80420
+ parkedTurnStarts.splice(i, 1);
80421
+ } else {
80422
+ i++;
80423
+ }
80424
+ }
80425
+ for (const env of drained) {
80426
+ process.stderr.write(`telegram gateway: parked-turn-start drained on silence-fallback ` + `chat=${env.chatId ?? "-"} thread=${envThreadIdNum(env.threadId) ?? "-"} ` + `msg=${env.messageId ?? "-"}
80427
+ `);
80428
+ beginTurn(deps, env);
80429
+ }
80430
+ return drained;
80431
+ }
80219
80432
  async function openQueuedCard(deps, chatId, threadId, replyToMessageId) {
80220
80433
  try {
80221
80434
  const sent = await deps.robustApiCall(() => deps.bot.api.sendRichMessage(chatId, richMessage(QUEUED_CARD_HTML), {
@@ -83955,10 +84168,6 @@ Allowed: \`${deps.escapeHtml(allow)}\``, { html: true });
83955
84168
 
83956
84169
  // gateway/model-command.ts
83957
84170
  var MODEL_ALIASES2 = ["opus", "sonnet", "haiku", "fable", "default"];
83958
- var CLAUDE_MODEL_ALIASES2 = {
83959
- opus48: "claude-opus-4-8",
83960
- "opus-4-8": "claude-opus-4-8"
83961
- };
83962
84171
  var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
83963
84172
  function isValidModelArg2(arg) {
83964
84173
  return MODEL_ARG_RE2.test(arg);
@@ -84117,7 +84326,7 @@ function resolveStaleAwareBusy(input) {
84117
84326
  var PERSIST_NOTE3 = "_A `/model` switch relaunches the session (~30s) on the chosen model. Session-only \u2014 reverts to the configured `model:` on the next restart. `/model default` reverts now. Live scrollback is replaced by a fresh session; memory and the handoff briefing carry the context. To change the default permanently, set `model:` in switchroom.yaml._";
84118
84327
  function claudeAliasHints2(prefix) {
84119
84328
  const byTarget = new Map;
84120
- for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES2)) {
84329
+ for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES)) {
84121
84330
  const spellings = byTarget.get(target) ?? [];
84122
84331
  spellings.push(alias);
84123
84332
  byTarget.set(target, spellings);
@@ -84125,7 +84334,7 @@ function claudeAliasHints2(prefix) {
84125
84334
  return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
84126
84335
  }
84127
84336
  function helpText4(deps, reason) {
84128
- const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`${a}\``).join(" \u00b7 ");
84337
+ const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
84129
84338
  const claudeAliasExamples = claudeAliasHints2("");
84130
84339
  const lines = [];
84131
84340
  if (reason)
@@ -84140,7 +84349,7 @@ async function handleModelCommand2(parsed, deps) {
84140
84349
  if (parsed.kind === "show") {
84141
84350
  const configured = deps.getConfiguredModel();
84142
84351
  const shown = configured && configured.length > 0 ? configured : "default";
84143
- const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`/model ${a}\``).join(" \u00b7 ");
84352
+ const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`/model ${a}\``).join(" \u00b7 ");
84144
84353
  return {
84145
84354
  text: [
84146
84355
  `**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
@@ -84158,7 +84367,7 @@ async function handleModelCommand2(parsed, deps) {
84158
84367
  if (!isValidModelArg2(parsed.model)) {
84159
84368
  return helpText4(deps, `not a valid model name: ${parsed.model}`);
84160
84369
  }
84161
- const model = expandModelAlias2(parsed.model);
84370
+ const model = expandModelAlias(parsed.model);
84162
84371
  if (deps.isBusy()) {
84163
84372
  return {
84164
84373
  text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
@@ -84185,10 +84394,10 @@ function relaunchErrorReply2(deps, model, err) {
84185
84394
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
84186
84395
  }
84187
84396
  function unvalidatedIdCaveat2(deps, model) {
84188
- const lower = canonicalModelToken2(model).toLowerCase();
84397
+ const lower = canonicalModelToken(model).toLowerCase();
84189
84398
  if (!lower.startsWith("claude-"))
84190
84399
  return null;
84191
- if (Object.values(CLAUDE_MODEL_ALIASES2).includes(lower))
84400
+ if (Object.values(CLAUDE_MODEL_ALIASES).includes(lower))
84192
84401
  return null;
84193
84402
  return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
84194
84403
  }
@@ -84273,45 +84482,17 @@ var SR_MODEL_LABELS2 = {
84273
84482
  "sr-gpt-5.6-terra": "GPT-5.6 Terra",
84274
84483
  "sr-gpt-5.6-luna": "GPT-5.6 Luna"
84275
84484
  };
84276
- var SR_MODEL_ALIASES2 = {
84277
- flash: "sr-gemini-2.5-flash",
84278
- gemini: "sr-gemini-2.5-pro",
84279
- deepseek: "sr-deepseek-v3",
84280
- r1: "sr-deepseek-r1",
84281
- glm: "sr-glm-5",
84282
- codex: "sr-codex-5.5",
84283
- grok: "sr-grok-4.5",
84284
- kimi: "sr-kimi-k3",
84285
- gpt: "sr-gpt-5.6-sol",
84286
- sol: "sr-gpt-5.6-sol",
84287
- terra: "sr-gpt-5.6-terra",
84288
- luna: "sr-gpt-5.6-luna"
84289
- };
84290
84485
  function isOfflineTrustedModelToken(token) {
84291
84486
  const lower = token.toLowerCase();
84292
84487
  if (MODEL_ALIASES2.includes(lower))
84293
84488
  return true;
84294
- if (lower in SR_MODEL_ALIASES2)
84489
+ if (lower in SR_MODEL_ALIASES)
84295
84490
  return true;
84296
- if (Object.values(SR_MODEL_ALIASES2).includes(lower))
84491
+ if (Object.values(SR_MODEL_ALIASES).includes(lower))
84297
84492
  return true;
84298
- if (lower in CLAUDE_MODEL_ALIASES2)
84493
+ if (lower in CLAUDE_MODEL_ALIASES)
84299
84494
  return true;
84300
- return Object.values(CLAUDE_MODEL_ALIASES2).includes(lower);
84301
- }
84302
- function expandSrAlias2(arg) {
84303
- return SR_MODEL_ALIASES2[arg.toLowerCase()] ?? arg;
84304
- }
84305
- function canonicalModelToken2(arg) {
84306
- const trimmed = arg.trim();
84307
- const lower = trimmed.toLowerCase();
84308
- return lower.startsWith("claude-") ? lower : trimmed;
84309
- }
84310
- function expandClaudeAlias2(arg) {
84311
- return CLAUDE_MODEL_ALIASES2[arg.trim().toLowerCase()] ?? arg;
84312
- }
84313
- function expandModelAlias2(arg) {
84314
- return canonicalModelToken2(expandSrAlias2(expandClaudeAlias2(arg)));
84495
+ return Object.values(CLAUDE_MODEL_ALIASES).includes(lower);
84315
84496
  }
84316
84497
  function srFriendlyLabel2(srName) {
84317
84498
  return SR_MODEL_LABELS2[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
@@ -84391,7 +84572,7 @@ function headerRow2(label) {
84391
84572
  return [{ text: label, callback_data: MODEL_CALLBACK_HEADER2 }];
84392
84573
  }
84393
84574
  function externalModelNames2(discovered) {
84394
- const set = new Set(Object.values(SR_MODEL_ALIASES2));
84575
+ const set = new Set(Object.values(SR_MODEL_ALIASES));
84395
84576
  for (const n of discovered) {
84396
84577
  if (isSrModel2(n))
84397
84578
  set.add(n);
@@ -84509,7 +84690,7 @@ async function handleModelMenuCallback(data, deps) {
84509
84690
  if (!isValidModelArg2(token)) {
84510
84691
  return { answer: "Invalid model name", reply: await buildModelMenu2(deps) };
84511
84692
  }
84512
- token = expandModelAlias2(token);
84693
+ token = expandModelAlias(token);
84513
84694
  if (!isRecognizedSwitchToken(token)) {
84514
84695
  return { answer: "Model list changed \u2014 menu refreshed", reply: await buildModelMenu2(deps) };
84515
84696
  }
@@ -85615,7 +85796,9 @@ function resolveMainModel(model) {
85615
85796
  return normalizeModelAlias(model);
85616
85797
  }
85617
85798
  function normalizeModelAlias(model) {
85618
- return model === "claude-fable-5" ? "fable" : model;
85799
+ if (model === "claude-fable-5")
85800
+ return "fable";
85801
+ return expandModelAlias(model);
85619
85802
  }
85620
85803
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
85621
85804
  var pendingBankOps = new Set;
@@ -91772,6 +91955,18 @@ function computeTurnStatus(turn) {
91772
91955
  return turn.finalAnswerDelivered ? "complete" : "no_reply";
91773
91956
  }
91774
91957
  }
91958
+ function computeTurnRoute(turn) {
91959
+ switch (turn.deliveryOutcome) {
91960
+ case "failed":
91961
+ return "none";
91962
+ case "delivered":
91963
+ return "flush";
91964
+ case "suppressed":
91965
+ return "reply";
91966
+ default:
91967
+ return turn.finalAnswerDelivered ? turn.replyCalled ? "reply" : "stream" : "none";
91968
+ }
91969
+ }
91775
91970
  function buildTurnRecord(turn, endedAt) {
91776
91971
  return {
91777
91972
  ts: Math.floor(endedAt / 1000),
@@ -91780,6 +91975,11 @@ function buildTurnRecord(turn, endedAt) {
91780
91975
  tools: turn.toolCallCount ?? 0,
91781
91976
  status: computeTurnStatus(turn),
91782
91977
  turn_id: turn.turnId,
91978
+ route: computeTurnRoute({
91979
+ finalAnswerDelivered: turn.finalAnswerDelivered,
91980
+ replyCalled: turn.replyCalled ?? false,
91981
+ deliveryOutcome: turn.deliveryOutcome
91982
+ }),
91783
91983
  ...turn.landedUnconfirmed != null && turn.landedUnconfirmed > 0 ? { landed_unconfirmed: turn.landedUnconfirmed } : {}
91784
91984
  };
91785
91985
  }
@@ -93426,6 +93626,7 @@ function buildSilencePokeOptions(deps) {
93426
93626
  endCurrentTurnForKey,
93427
93627
  getPendingInboundBuffer,
93428
93628
  trackRedeliveredInbound,
93629
+ drainParkedTurnStarts,
93429
93630
  closeActivityLane,
93430
93631
  closeProgressLane,
93431
93632
  hangRestart
@@ -93619,7 +93820,8 @@ function buildSilencePokeOptions(deps) {
93619
93820
  } catch {}
93620
93821
  const fbSelfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
93621
93822
  const fbRedeliver = redeliverBufferedInbound2(getPendingInboundBuffer(), fbSelfAgent, (m) => sendToAgent(fbSelfAgent, m), getInboundSpool(), trackRedeliveredInbound);
93622
- process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${tearsDownLiveTurn} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
93823
+ const fbDrainedParked = drainParkedTurnStarts?.(fbChatId, fbThreadId ?? null) ?? 0;
93824
+ process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${tearsDownLiveTurn} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbDrainedParked > 0 ? ` drained_parked=${fbDrainedParked}` : ""}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
93623
93825
  `);
93624
93826
  }
93625
93827
  };
@@ -99917,10 +100119,10 @@ function startOutboxSweep(deps) {
99917
100119
  }
99918
100120
 
99919
100121
  // ../src/build-info.ts
99920
- var VERSION2 = "0.19.40";
99921
- var COMMIT_SHA = "89286b22";
99922
- var COMMIT_DATE = "2026-07-31T04:28:56Z";
99923
- var LATEST_PR = 4061;
100122
+ var VERSION2 = "0.19.42";
100123
+ var COMMIT_SHA = "e288dac2";
100124
+ var COMMIT_DATE = "2026-08-01T02:20:24Z";
100125
+ var LATEST_PR = 4094;
99924
100126
  var COMMITS_AHEAD_OF_TAG = 0;
99925
100127
 
99926
100128
  // gateway/boot-version.ts
@@ -101975,40 +102177,21 @@ var GRAMMY_VERSION = (() => {
101975
102177
  var _rawSendChecklist;
101976
102178
  var _rawEditMessageChecklist;
101977
102179
  var CHECKLIST_API_AVAILABLE = false;
101978
- async function rawSendChecklist(args) {
101979
- if (!CHECKLIST_API_AVAILABLE) {
102180
+ async function rawSendChecklist(payload) {
102181
+ if (!CHECKLIST_API_AVAILABLE)
101980
102182
  throw new Error("sendChecklist is not available in this grammY/Telegram Bot API version");
101981
- }
101982
- const MAX_TASKS = 30;
101983
- if (args.tasks.length > MAX_TASKS) {
101984
- throw new Error(`checklist exceeds ${MAX_TASKS}-task limit (got ${args.tasks.length})`);
101985
- }
101986
- const result = await _rawSendChecklist({
101987
- chat_id: Number(args.chat_id),
101988
- title: args.title,
101989
- tasks: args.tasks.map((t) => ({ text: t.text, ...t.done != null ? { is_completed: t.done } : {} })),
101990
- ...args.message_thread_id != null ? { message_thread_id: args.message_thread_id } : {},
101991
- ...args.reply_to_message_id != null ? { reply_to_message_id: args.reply_to_message_id } : {},
101992
- ...args.protect_content === true ? { protect_content: true } : {}
101993
- });
102183
+ const result = await _rawSendChecklist(payload);
101994
102184
  return { message_id: result.message_id };
101995
102185
  }
101996
- async function rawEditMessageChecklist(args) {
101997
- if (!CHECKLIST_API_AVAILABLE) {
102186
+ async function rawEditMessageChecklist(payload) {
102187
+ if (!CHECKLIST_API_AVAILABLE)
101998
102188
  throw new Error("editMessageChecklist is not available in this grammY/Telegram Bot API version");
101999
- }
102000
- await _rawEditMessageChecklist({
102001
- chat_id: Number(args.chat_id),
102002
- message_id: Number(args.message_id),
102003
- ...args.title != null ? { title: args.title } : {},
102004
- ...args.tasks != null ? {
102005
- tasks: args.tasks.map((t) => ({
102006
- ...t.id != null ? { id: Number(t.id) } : {},
102007
- ...t.text != null ? { text: t.text } : {},
102008
- ...t.done != null ? { is_completed: t.done } : {}
102009
- }))
102010
- } : {}
102011
- });
102189
+ await _rawEditMessageChecklist(payload);
102190
+ }
102191
+ var checklistStore = createChecklistStore();
102192
+ function resolveBusinessConnectionId() {
102193
+ const v = process.env.SWITCHROOM_TELEGRAM_BUSINESS_CONNECTION_ID?.trim();
102194
+ return v ? v : undefined;
102012
102195
  }
102013
102196
  var chatLock = createChatLock();
102014
102197
  var lockedBot;
@@ -103399,6 +103582,7 @@ function emitTurnRecord(turn, endedAt) {
103399
103582
  toolCallCount: turn.toolCallCount ?? 0,
103400
103583
  turnId: turn.turnId,
103401
103584
  finalAnswerDelivered: turn.finalAnswerDelivered,
103585
+ replyCalled: turn.replyCalled,
103402
103586
  deliveryOutcome: turn.deliveryOutcome,
103403
103587
  landedUnconfirmed: turn.landedUnconfirmed
103404
103588
  }, endedAt)) + `
@@ -105478,6 +105662,7 @@ function gatewayLivenessWiringDeps() {
105478
105662
  endCurrentTurnForKey,
105479
105663
  getPendingInboundBuffer: () => pendingInboundBuffer,
105480
105664
  trackRedeliveredInbound,
105665
+ drainParkedTurnStarts: (chatId, threadId) => drainParkedTurnStartsForChat(gatewayStreamRenderDeps(), chatId, threadId != null ? String(threadId) : null).length,
105481
105666
  closeActivityLane,
105482
105667
  closeProgressLane,
105483
105668
  hangRestart: {
@@ -106723,17 +106908,24 @@ async function executeSendChecklist(args) {
106723
106908
  const protectContent = args.protect_content === true;
106724
106909
  assertAllowedChat(chat_id);
106725
106910
  const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "send_checklist"));
106726
- const sent = await rawSendChecklist({
106727
- chat_id,
106728
- title: redactedTitle,
106729
- tasks: redactedTasks,
106911
+ const literal = (loadAccess().parseMode ?? "html") === "text";
106912
+ const sendOpts = {
106730
106913
  ...threadId != null ? { message_thread_id: threadId } : {},
106731
- ...replyTo != null ? { reply_to_message_id: replyTo } : {},
106914
+ ...replyTo != null ? { reply_parameters: { message_id: replyTo } } : {},
106732
106915
  ...protectContent ? { protect_content: true } : {}
106733
- });
106734
- process.stderr.write(`telegram gateway: send_checklist: sent chatId=${chat_id} messageId=${sent.message_id} tasks=${tasks.length}
106916
+ };
106917
+ const result = await performSendChecklist({
106918
+ businessConnectionId: resolveBusinessConnectionId(),
106919
+ nativeAvailable: CHECKLIST_API_AVAILABLE,
106920
+ sendNative: rawSendChecklist,
106921
+ sendText: (text5) => robustApiCall(() => literal ? lockedBot.api.sendMessage(chat_id, text5, sendOpts) : lockedBot.api.sendRichMessage(chat_id, richMessage2(text5), sendOpts), { verb: "sendMessage", chat_id, threadId }),
106922
+ literalText: literal,
106923
+ log: (l) => process.stderr.write(`telegram gateway: ${l}`)
106924
+ }, { title: redactedTitle, tasks: redactedTasks, chatId: Number(chat_id), replyToMessageId: replyTo, protectContent });
106925
+ checklistStore.set(checklistStoreKey(chat_id, result.message_id), result.state);
106926
+ process.stderr.write(`telegram gateway: send_checklist: sent chatId=${chat_id} messageId=${result.message_id} tasks=${tasks.length} mode=${result.mode}
106735
106927
  `);
106736
- return { content: [{ type: "text", text: `checklist sent (id: ${sent.message_id})` }] };
106928
+ return { content: [{ type: "text", text: sendChecklistToolText(result) }] };
106737
106929
  }
106738
106930
  var linearAuthAlertLast = new Map;
106739
106931
  var LINEAR_AUTH_ALERT_COOLDOWN_MS = 21600000;
@@ -106785,10 +106977,26 @@ async function executeUpdateChecklist(args) {
106785
106977
  const tasks = args.tasks;
106786
106978
  assertAllowedChat(chat_id);
106787
106979
  const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "update_checklist"));
106788
- await rawEditMessageChecklist({ chat_id, message_id, title: redactedTitle, tasks: redactedTasks });
106789
- process.stderr.write(`telegram gateway: update_checklist: updated chatId=${chat_id} messageId=${message_id}
106980
+ const literal = (loadAccess().parseMode ?? "html") === "text";
106981
+ const key = checklistStoreKey(chat_id, message_id);
106982
+ const result = await performUpdateChecklist({
106983
+ state: checklistStore.get(key),
106984
+ businessConnectionId: resolveBusinessConnectionId(),
106985
+ nativeAvailable: CHECKLIST_API_AVAILABLE,
106986
+ editNative: rawEditMessageChecklist,
106987
+ editText: async (text5) => {
106988
+ await robustApiCall(() => lockedBot.api.editMessageText(chat_id, Number(message_id), literal ? text5 : richMessage2(text5)));
106989
+ },
106990
+ literalText: literal,
106991
+ log: (l) => process.stderr.write(`telegram gateway: ${l}`),
106992
+ chatId: Number(chat_id),
106993
+ messageId: Number(message_id)
106994
+ }, { title: redactedTitle, tasks: redactedTasks });
106995
+ if (result.ok)
106996
+ checklistStore.set(key, result.state);
106997
+ process.stderr.write(`telegram gateway: update_checklist: ${result.ok ? `updated mode=${result.mode}` : `failed reason=${result.reason}`} chatId=${chat_id} messageId=${message_id}
106790
106998
  `);
106791
- return { content: [{ type: "text", text: `checklist updated (id: ${message_id})` }] };
106999
+ return { content: [{ type: "text", text: updateChecklistToolText(result, Number(message_id)) }] };
106792
107000
  }
106793
107001
  function redactOutboundText(text5, site) {
106794
107002
  const masked = redact2(text5);
@@ -109590,7 +109798,7 @@ function persistQueuedCommandForRestart(action) {
109590
109798
  return `\u21A9\uFE0F Couldn\u2019t verify \`${escapeHtmlForTg2(action.cmd.targetLabel || action.arg)}\` as a known model without the live session \u2014 it was NOT saved. Re-issue \`/model ${escapeHtmlForTg2(action.arg)}\` once the agent is back.`;
109591
109799
  }
109592
109800
  const configured = readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined);
109593
- writeSessionModelFile(agentDir, expandModelAlias2(action.arg), configured);
109801
+ writeSessionModelFile(agentDir, expandModelAlias(action.arg), configured);
109594
109802
  break;
109595
109803
  }
109596
109804
  case "clear-model":