switchroom 0.19.19 → 0.19.23

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 (76) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +121 -1
  3. package/dist/cli/notion-write-pretool.mjs +2 -1
  4. package/dist/cli/switchroom.js +2995 -1583
  5. package/dist/host-control/main.js +122 -2
  6. package/dist/vault/approvals/kernel-server.js +124 -4
  7. package/dist/vault/broker/server.js +124 -4
  8. package/package.json +7 -4
  9. package/profiles/_base/start.sh.hbs +101 -0
  10. package/profiles/_shared/agent-self-service.md.hbs +64 -109
  11. package/profiles/_shared/delegation-golden-rule.md.hbs +5 -5
  12. package/profiles/_shared/dev-protocol.md.hbs +13 -42
  13. package/profiles/_shared/execution-discipline.md.hbs +7 -14
  14. package/profiles/coding/CLAUDE.md.hbs +0 -6
  15. package/profiles/default/CLAUDE.md.hbs +21 -50
  16. package/skills/dev-protocol/SKILL.md +90 -107
  17. package/skills/switchroom-release/SKILL.md +103 -20
  18. package/telegram-plugin/bunfig.toml +10 -0
  19. package/telegram-plugin/card-format.ts +92 -3
  20. package/telegram-plugin/dist/gateway/gateway.js +873 -184
  21. package/telegram-plugin/edit-flood-fuse.ts +477 -0
  22. package/telegram-plugin/format.ts +19 -7
  23. package/telegram-plugin/gateway/backstop-delivery.ts +97 -16
  24. package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
  25. package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
  26. package/telegram-plugin/gateway/captured-answer-resume.ts +46 -17
  27. package/telegram-plugin/gateway/gateway.ts +75 -63
  28. package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
  29. package/telegram-plugin/gateway/narrative-lane.ts +49 -3
  30. package/telegram-plugin/gateway/outbound-send-path.ts +8 -1
  31. package/telegram-plugin/gateway/status-pin-api.ts +145 -0
  32. package/telegram-plugin/gateway/stream-render.ts +6 -0
  33. package/telegram-plugin/gateway/turn-record-status.ts +19 -0
  34. package/telegram-plugin/gateway/turns-jsonl-rotate.ts +65 -0
  35. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
  36. package/telegram-plugin/retry-api-call.ts +15 -2
  37. package/telegram-plugin/send-gate.ts +1 -1
  38. package/telegram-plugin/status-no-truncate.ts +64 -1
  39. package/telegram-plugin/status-pin-driver.ts +50 -27
  40. package/telegram-plugin/status-pin.ts +43 -5
  41. package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
  42. package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
  43. package/telegram-plugin/tests/agent-state-dir-preload.test.ts +33 -0
  44. package/telegram-plugin/tests/backstop-delivery.test.ts +204 -7
  45. package/telegram-plugin/tests/backstop-readback-probe.test.ts +12 -0
  46. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
  47. package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
  48. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  49. package/telegram-plugin/tests/captured-answer-resume.test.ts +104 -0
  50. package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
  51. package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
  52. package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
  53. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
  54. package/telegram-plugin/tests/status-pin.test.ts +106 -5
  55. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
  56. package/telegram-plugin/tests/tool-activity-summary.test.ts +19 -10
  57. package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +92 -1
  58. package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
  59. package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
  60. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
  61. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
  62. package/telegram-plugin/tool-activity-summary.ts +85 -13
  63. package/telegram-plugin/worker-activity-feed.ts +5 -1
  64. package/vendor/hindsight-memory/scripts/drain_pending.py +304 -34
  65. package/vendor/hindsight-memory/scripts/lib/pending.py +886 -70
  66. package/vendor/hindsight-memory/scripts/lib/retain_split.py +71 -13
  67. package/vendor/hindsight-memory/scripts/recall.py +74 -5
  68. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +1602 -14
  69. package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
  70. package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
  71. package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
  72. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
  73. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
  74. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +93 -13
  75. package/vendor/hindsight-memory/tests/test_drain_pending.py +44 -3
  76. package/vendor/hindsight-memory/tests/test_pending.py +12 -4
@@ -7379,7 +7379,17 @@ var init_text_voice_scrub = __esm(() => {
7379
7379
  });
7380
7380
 
7381
7381
  // card-format.ts
7382
- function stackCardLines(lines) {
7382
+ function normalizeLineTail(line, collapseSafe) {
7383
+ let out = line.replace(/[ \t\r]+$/, "");
7384
+ if (!collapseSafe || COLLAPSE_SAFE_SEPARATOR.length === 0)
7385
+ return out;
7386
+ while (out.endsWith(COLLAPSE_SAFE_SEPARATOR)) {
7387
+ out = out.slice(0, -COLLAPSE_SAFE_SEPARATOR.length).replace(/[ \t\r]+$/, "");
7388
+ }
7389
+ return out;
7390
+ }
7391
+ function stackCardLines(lines, opts) {
7392
+ const collapseSafe = opts?.collapseSafe === true;
7383
7393
  const pieces = [];
7384
7394
  for (let i = 0;i < lines.length; i++) {
7385
7395
  const line = lines[i];
@@ -7389,11 +7399,14 @@ function stackCardLines(lines) {
7389
7399
  const cur = line.trim();
7390
7400
  const next = lines[i + 1].trim();
7391
7401
  if (cur === "" || next === "") {
7402
+ if (collapseSafe && cur !== "") {
7403
+ pieces[pieces.length - 1] = normalizeLineTail(line, true) + COLLAPSE_SAFE_SEPARATOR;
7404
+ }
7392
7405
  pieces.push(`
7393
7406
  `);
7394
7407
  continue;
7395
7408
  }
7396
- pieces[pieces.length - 1] = line.replace(/[ \t\r]+$/, "");
7409
+ pieces[pieces.length - 1] = normalizeLineTail(line, collapseSafe) + (collapseSafe ? COLLAPSE_SAFE_SEPARATOR : "");
7397
7410
  pieces.push(`
7398
7411
  `);
7399
7412
  }
@@ -7439,6 +7452,7 @@ function cleanWorkerResultParagraph(s) {
7439
7452
  }
7440
7453
  return kept.join(" ").replace(/\s+/g, " ").trim();
7441
7454
  }
7455
+ var COLLAPSE_SAFE_SEPARATOR = "\u00a0";
7442
7456
  var init_card_format = __esm(() => {
7443
7457
  init_format();
7444
7458
  init_text_voice_scrub();
@@ -21037,7 +21051,8 @@ var init_schema = __esm(() => {
21037
21051
  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."),
21038
21052
  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."),
21039
21053
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
21040
- }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults.")
21054
+ }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
21055
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "LLM_MAX_CONCURRENT, RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
21041
21056
  });
21042
21057
  MicrosoftWorkspaceConfigSchema = exports_external.object({
21043
21058
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -35381,7 +35396,7 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35381
35396
 
35382
35397
  // gateway/quota-cache.ts
35383
35398
  import { existsSync as existsSync45, readFileSync as readFileSync43, writeFileSync as writeFileSync36, mkdirSync as mkdirSync38 } from "fs";
35384
- import { join as join51, dirname as dirname17 } from "path";
35399
+ import { join as join51, dirname as dirname18 } from "path";
35385
35400
  function defaultCachePath() {
35386
35401
  return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join51(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35387
35402
  }
@@ -35418,7 +35433,7 @@ function writeQuotaCache(result, opts = {}) {
35418
35433
  result
35419
35434
  };
35420
35435
  try {
35421
- mkdirSync38(dirname17(path3), { recursive: true });
35436
+ mkdirSync38(dirname18(path3), { recursive: true });
35422
35437
  writeFileSync36(path3, JSON.stringify(entry, null, 2), { mode: 384 });
35423
35438
  } catch {}
35424
35439
  }
@@ -36291,7 +36306,7 @@ var init_boot_probes = __esm(() => {
36291
36306
 
36292
36307
  // gateway/boot-issue-cache.ts
36293
36308
  import { existsSync as existsSync47, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync39, renameSync as renameSync21 } from "fs";
36294
- import { dirname as dirname18 } from "path";
36309
+ import { dirname as dirname19 } from "path";
36295
36310
  function fingerprintProbe(key, r) {
36296
36311
  if (r.status === "ok")
36297
36312
  return `${key}:ok`;
@@ -36417,7 +36432,7 @@ function applyAndSave(path3, cache, diff) {
36417
36432
  }
36418
36433
  }
36419
36434
  try {
36420
- mkdirSync39(dirname18(path3), { recursive: true });
36435
+ mkdirSync39(dirname19(path3), { recursive: true });
36421
36436
  const tmp = `${path3}.tmp`;
36422
36437
  writeFileSync37(tmp, JSON.stringify(next), { mode: 384 });
36423
36438
  renameSync21(tmp, path3);
@@ -36434,7 +36449,7 @@ var init_boot_issue_cache = __esm(() => {
36434
36449
  // gateway/config-snapshot.ts
36435
36450
  import { createHash as createHash6 } from "crypto";
36436
36451
  import { existsSync as existsSync48, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, renameSync as renameSync22 } from "fs";
36437
- import { dirname as dirname19 } from "path";
36452
+ import { dirname as dirname20 } from "path";
36438
36453
  function hashStringArray(items) {
36439
36454
  if (!items || items.length === 0)
36440
36455
  return null;
@@ -36532,7 +36547,7 @@ function loadSnapshot(path3, now = Date.now) {
36532
36547
  }
36533
36548
  function persistSnapshot(path3, snapshot) {
36534
36549
  try {
36535
- mkdirSync40(dirname19(path3), { recursive: true });
36550
+ mkdirSync40(dirname20(path3), { recursive: true });
36536
36551
  const tmp = `${path3}.tmp`;
36537
36552
  writeFileSync38(tmp, JSON.stringify(snapshot), { mode: 384 });
36538
36553
  renameSync22(tmp, path3);
@@ -38714,7 +38729,7 @@ import {
38714
38729
  unlinkSync as unlinkSync28,
38715
38730
  appendFileSync as appendFileSync9
38716
38731
  } from "fs";
38717
- import { homedir as homedir20 } from "os";
38732
+ import { homedir as homedir19 } from "os";
38718
38733
  import { join as join65, sep as sep4, basename as basename17 } from "path";
38719
38734
 
38720
38735
  // plugin-logger.ts
@@ -43330,13 +43345,27 @@ async function interceptVault(p, deps) {
43330
43345
  deps.vaultPassphraseCache.set(p.chat_id, { passphrase, expiresAt: Date.now() + deps.vaultPassphraseTtlMs });
43331
43346
  if (p.msgId != null)
43332
43347
  await deps.deleteSensitiveMessage(p.chat_id, p.msgId, "vault passphrase");
43348
+ const mismatched = [];
43349
+ let mismatchMsg = "";
43333
43350
  for (const item of pendingVault.items) {
43334
43351
  const stagedAccess = deps.pendingVaultRequestAccesses.get(item.stageId);
43335
43352
  if (!stagedAccess) {
43336
43353
  await p.ctx.api.editMessageText(item.cardChatId, item.cardMessageId, richMessage(`\u231b _Vault unlocked, but this access-request card expired or was denied before you replied. Ask the agent to re-issue if still needed._`), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
43337
43354
  continue;
43338
43355
  }
43339
- await deps.callbackQueryHandlers().performVaultAccessApproval(p.ctx, stagedAccess, item.stageId, item.senderId, { kind: "passphrase", passphrase });
43356
+ const outcome = await deps.callbackQueryHandlers().performVaultAccessApproval(p.ctx, stagedAccess, item.stageId, item.senderId, { kind: "passphrase", passphrase });
43357
+ if (outcome?.kind === "passphrase-mismatch") {
43358
+ mismatched.push(item);
43359
+ mismatchMsg = outcome.msg;
43360
+ }
43361
+ }
43362
+ if (mismatched.length > 0) {
43363
+ await deps.callbackQueryHandlers().resolveAccessApprovalPassphraseMismatch(p.ctx, {
43364
+ chat_id: p.chat_id,
43365
+ failed: mismatched,
43366
+ priorAttempts: pendingVault.attempts ?? 0,
43367
+ brokerMsg: mismatchMsg
43368
+ });
43340
43369
  }
43341
43370
  } else if (pendingVault.kind === "grant-wizard" && pendingVault.awaitingCustomDuration) {
43342
43371
  const input = p.text.trim();
@@ -43965,13 +43994,27 @@ async function interceptVault2(p, deps) {
43965
43994
  deps.vaultPassphraseCache.set(p.chat_id, { passphrase, expiresAt: Date.now() + deps.vaultPassphraseTtlMs });
43966
43995
  if (p.msgId != null)
43967
43996
  await deps.deleteSensitiveMessage(p.chat_id, p.msgId, "vault passphrase");
43997
+ const mismatched = [];
43998
+ let mismatchMsg = "";
43968
43999
  for (const item of pendingVault.items) {
43969
44000
  const stagedAccess = deps.pendingVaultRequestAccesses.get(item.stageId);
43970
44001
  if (!stagedAccess) {
43971
44002
  await p.ctx.api.editMessageText(item.cardChatId, item.cardMessageId, richMessage(`\u231b _Vault unlocked, but this access-request card expired or was denied before you replied. Ask the agent to re-issue if still needed._`), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
43972
44003
  continue;
43973
44004
  }
43974
- await deps.callbackQueryHandlers().performVaultAccessApproval(p.ctx, stagedAccess, item.stageId, item.senderId, { kind: "passphrase", passphrase });
44005
+ const outcome = await deps.callbackQueryHandlers().performVaultAccessApproval(p.ctx, stagedAccess, item.stageId, item.senderId, { kind: "passphrase", passphrase });
44006
+ if (outcome?.kind === "passphrase-mismatch") {
44007
+ mismatched.push(item);
44008
+ mismatchMsg = outcome.msg;
44009
+ }
44010
+ }
44011
+ if (mismatched.length > 0) {
44012
+ await deps.callbackQueryHandlers().resolveAccessApprovalPassphraseMismatch(p.ctx, {
44013
+ chat_id: p.chat_id,
44014
+ failed: mismatched,
44015
+ priorAttempts: pendingVault.attempts ?? 0,
44016
+ brokerMsg: mismatchMsg
44017
+ });
43975
44018
  }
43976
44019
  } else if (pendingVault.kind === "grant-wizard" && pendingVault.awaitingCustomDuration) {
43977
44020
  const input = p.text.trim();
@@ -46435,6 +46478,7 @@ var WORKER_HISTORY_MAX = 6;
46435
46478
  var STATUS_LINE_MAX = 200;
46436
46479
  var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
46437
46480
  var NESTED_PREFIX = " \u21b3 ";
46481
+ var WORKER_STEP_INDENT = "\u2800\u2800\u2800";
46438
46482
 
46439
46483
  // hooks/tool-label-pretool.mjs
46440
46484
  import { readFileSync as readFileSync9, mkdirSync as mkdirSync10, appendFileSync as appendFileSync2, existsSync as existsSync9 } from "node:fs";
@@ -46858,16 +46902,16 @@ function escapeStepLine(raw) {
46858
46902
  const cleaned = stripMarkdown(raw).replace(/\s+/g, " ").trim();
46859
46903
  return escapeMarkdown(truncate(cleaned, STATUS_LINE_MAX));
46860
46904
  }
46861
- function renderStepFeed(out, steps, allDone, liveSuffix = "", window2 = STATUS_ROLLING_LINES) {
46905
+ function renderStepFeed(out, steps, allDone, liveSuffix = "", window2 = STATUS_ROLLING_LINES, indent = "") {
46862
46906
  if (steps.length === 0)
46863
46907
  return;
46864
46908
  const shown = steps.slice(-Math.max(1, window2));
46865
46909
  const hidden = steps.length - shown.length;
46866
46910
  if (hidden > 0)
46867
- out.push(`_\u2713 +${hidden} earlier\u2026_`);
46911
+ out.push(`${indent}_\u2713 +${hidden} earlier\u2026_`);
46868
46912
  const lastIdx = shown.length - 1;
46869
46913
  shown.forEach((s, i) => {
46870
- out.push(!allDone && i === lastIdx ? `**\u2192 ${s}${liveSuffix}**` : `~~_\u2713 ${s}_~~`);
46914
+ out.push(!allDone && i === lastIdx ? `${indent}**\u2192 ${s}${liveSuffix}**` : `${indent}~~_\u2713 ${s}_~~`);
46871
46915
  });
46872
46916
  }
46873
46917
  function renderStatusCard(opts) {
@@ -46907,7 +46951,7 @@ function renderStatusCard(opts) {
46907
46951
  }
46908
46952
  if (out.length === 0)
46909
46953
  return null;
46910
- const joined = stackCardLines(out);
46954
+ const joined = stackCardLines(out, { collapseSafe: true });
46911
46955
  if (joined.length <= STATUS_CARD_CHAR_BUDGET)
46912
46956
  return joined;
46913
46957
  return fitCardToBudget(opts, headerLines);
@@ -46942,7 +46986,7 @@ function fitCardToBudget(opts, headerLines) {
46942
46986
  const lastIdx = shown.length - 1;
46943
46987
  shown.forEach((esc, i) => lines2.push(buildBullet(esc, i === lastIdx)));
46944
46988
  lines2.push(...footerLines);
46945
- const candidate = stackCardLines(lines2);
46989
+ const candidate = stackCardLines(lines2, { collapseSafe: true });
46946
46990
  if (candidate.length <= STATUS_CARD_CHAR_BUDGET)
46947
46991
  return candidate;
46948
46992
  }
@@ -46963,7 +47007,7 @@ function fitCardToBudget(opts, headerLines) {
46963
47007
  lines.push(parentMarker);
46964
47008
  lines.push(newestLine);
46965
47009
  lines.push(...footerLines);
46966
- return stackCardLines(lines);
47010
+ return stackCardLines(lines, { collapseSafe: true });
46967
47011
  }
46968
47012
  function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, header) {
46969
47013
  if (lines.length === 0 && header == null)
@@ -47016,6 +47060,13 @@ function combinedHistoryDepth(w) {
47016
47060
  return Math.min(WORKER_HISTORY_MAX, Math.max(MIN_WORKER_DEPTH, 7 - w));
47017
47061
  }
47018
47062
  var workerHistoryDepth = combinedHistoryDepth;
47063
+ function glanceLine(rows) {
47064
+ const oldestMs = rows.reduce((m, r) => Math.max(m, r.elapsedMs), 0);
47065
+ const tools = rows.reduce((n, r) => n + r.toolCount, 0);
47066
+ const tok = rows.reduce((n, r) => n + (r.totalTokens ?? 0), 0);
47067
+ const toolWord = tools === 1 ? "tool" : "tools";
47068
+ return `\uD83D\uDEE0 **Workers** \u00b7 _${rows.length} running \u00b7 oldest ${formatFeedElapsed(oldestMs)}` + ` \u00b7 ${tools} ${toolWord}${tokenSegment(tok)}_`;
47069
+ }
47019
47070
  function renderCombinedWorkerFeed(rows, opts) {
47020
47071
  if (rows.length === 0)
47021
47072
  return null;
@@ -47038,22 +47089,22 @@ function renderCombinedWorkerFeed(rows, opts) {
47038
47089
  const shown = rows.slice(0, visibleCount);
47039
47090
  const hidden = rows.length - shown.length;
47040
47091
  const depth = combinedHistoryDepth(shown.length);
47041
- const chrome = [`\uD83D\uDEE0 **Workers** \u00b7 _${rows.length} running_`];
47092
+ const chrome = [glanceLine(rows)];
47042
47093
  const bodyOut = [];
47043
47094
  for (const r of shown) {
47044
47095
  bodyOut.push(rowHeader(r));
47045
47096
  const hist = rowHistory(r);
47046
47097
  if (hist.length === 0) {
47047
- bodyOut.push("\u2192 _starting\u2026_");
47098
+ bodyOut.push(`${WORKER_STEP_INDENT}\u2192 _starting\u2026_`);
47048
47099
  continue;
47049
47100
  }
47050
47101
  const esc = hist.slice(-depth).map(escapeStepLine);
47051
- renderStepFeed(bodyOut, esc, false, "", depth);
47102
+ renderStepFeed(bodyOut, esc, false, "", depth, WORKER_STEP_INDENT);
47052
47103
  }
47053
47104
  const out = [...chrome, ...bodyOut];
47054
47105
  if (hidden > 0)
47055
47106
  out.push(`_+${hidden} more working\u2026_`);
47056
- return { body: stackCardLines(out), bodyLines: bodyOut.length };
47107
+ return { body: stackCardLines(out, { collapseSafe: true }), bodyLines: bodyOut.length };
47057
47108
  };
47058
47109
  let visible = Math.min(rows.length, maxRows);
47059
47110
  let { body, bodyLines } = compose(visible);
@@ -47074,6 +47125,10 @@ function appendActivityLabel(lines, label) {
47074
47125
  }
47075
47126
 
47076
47127
  // send-gate.ts
47128
+ var systemClock = {
47129
+ now: () => Date.now(),
47130
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms))
47131
+ };
47077
47132
  var SEND_GATE_SHED = Symbol.for("switchroom.send-gate.shed");
47078
47133
  function isSendGateShed(value) {
47079
47134
  return value === SEND_GATE_SHED;
@@ -47131,7 +47186,7 @@ function renderWorkerActivity(v, liveSuffix = "") {
47131
47186
  return `\uD83D\uDEE0 **Worker** \u00b7 _starting\u2026_`;
47132
47187
  }
47133
47188
  if (!finished && steps.length === 0) {
47134
- return `${card}
47189
+ return `${card}${COLLAPSE_SAFE_SEPARATOR}
47135
47190
  _starting\u2026_`;
47136
47191
  }
47137
47192
  return card;
@@ -48032,6 +48087,16 @@ function errorDescription(err) {
48032
48087
  function isPinRightsError(err) {
48033
48088
  return errorDescription(err).includes("not enough rights");
48034
48089
  }
48090
+ function isUnpinTerminalError(err) {
48091
+ if (isPinRightsError(err))
48092
+ return true;
48093
+ if (err != null && typeof err === "object") {
48094
+ const code = err.error_code;
48095
+ if (typeof code === "number")
48096
+ return code >= 400 && code < 500 && code !== 429;
48097
+ }
48098
+ return false;
48099
+ }
48035
48100
 
48036
48101
  class PinRightsCache {
48037
48102
  blocked = new Set;
@@ -48055,18 +48120,20 @@ async function reconcilePin(args) {
48055
48120
  if (action.kind === "noop")
48056
48121
  return args.prevState;
48057
48122
  if (action.kind === "unpin") {
48058
- if (!args.rightsCache?.isBlocked(args.chatId)) {
48059
- try {
48060
- await args.api.unpinChatMessage(args.chatId, action.messageId);
48061
- } catch (err) {
48062
- if (args.rightsCache && isPinRightsError(err)) {
48063
- const firstTime = args.rightsCache.block(args.chatId);
48064
- if (firstTime)
48065
- args.onPinRightsDisabled?.(args.chatId);
48066
- } else {
48067
- args.onError?.("unpin", err);
48068
- }
48123
+ if (args.rightsCache?.isBlocked(args.chatId))
48124
+ return null;
48125
+ try {
48126
+ await args.api.unpinChatMessage(args.chatId, action.messageId);
48127
+ } catch (err) {
48128
+ if (args.rightsCache && isPinRightsError(err)) {
48129
+ const firstTime = args.rightsCache.block(args.chatId);
48130
+ if (firstTime)
48131
+ args.onPinRightsDisabled?.(args.chatId);
48132
+ } else {
48133
+ args.onError?.("unpin", err);
48069
48134
  }
48135
+ if (!isUnpinTerminalError(err))
48136
+ return args.prevState;
48070
48137
  }
48071
48138
  return null;
48072
48139
  }
@@ -48498,8 +48565,7 @@ init_rich_send();
48498
48565
  var import_grammy4 = __toESM(require_mod2(), 1);
48499
48566
  import { execFileSync as execFileSync3 } from "child_process";
48500
48567
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync9 } from "fs";
48501
- import { homedir as homedir5 } from "os";
48502
- import { join as join13 } from "path";
48568
+ import { dirname as dirname5 } from "path";
48503
48569
  init_client();
48504
48570
 
48505
48571
  // gateway/vault-grant-inbound-builders.ts
@@ -49532,6 +49598,48 @@ var _deprecationSeen = new Set;
49532
49598
 
49533
49599
  // gateway/callback-query-handlers.ts
49534
49600
  var AUTH_REFRESH_THROTTLE_MS = 5000;
49601
+ var MAX_VAULT_PASSPHRASE_ATTEMPTS = 3;
49602
+ function isPassphraseMismatchBrokerError(msg) {
49603
+ const m = msg.toLowerCase();
49604
+ if (!m.includes("passphrase"))
49605
+ return false;
49606
+ return m.includes("does not match") || m.includes("mismatch");
49607
+ }
49608
+ function buildAccessPassphrasePromptText(opts) {
49609
+ const header = `**\uD83D\uDEA8\uD83D\uDD10 ACTION NEEDED: passphrase required**`;
49610
+ if (opts.kind === "retry") {
49611
+ const plural = opts.retryRemaining === 1 ? "attempt" : "attempts";
49612
+ return `${header}
49613
+
49614
+ ` + `Wrong passphrase. ${opts.retryRemaining} ${plural} remaining.
49615
+ ` + `Type your vault passphrase again as your **next message**.
49616
+ ` + (opts.itemCount > 1 ? `One entry covers **${opts.itemCount}** pending approvals in this chat.
49617
+ ` : ``) + `
49618
+ _We delete the passphrase message the moment we read it._`;
49619
+ }
49620
+ if (opts.variant === "batch") {
49621
+ return `${header}
49622
+
49623
+ ` + `Type your vault passphrase as your **next message**.
49624
+ ` + `One entry covers **${opts.itemCount}** pending approvals in this chat, no re-type per card.
49625
+
49626
+ ` + `_We delete the passphrase message the moment we read it._`;
49627
+ }
49628
+ if (opts.variant === "admin-only") {
49629
+ return `${header}
49630
+
49631
+ ` + `\`${opts.key}\` is an **admin-only credential**.
49632
+ ` + `Type your vault passphrase as your **next message** to mint the grant for **${opts.agentEscaped}**.
49633
+
49634
+ ` + `_The passphrase is what proves it's you. An agent can never mint this key on its own. We delete the passphrase message the moment we read it._`;
49635
+ }
49636
+ return `${header}
49637
+
49638
+ ` + `Your vault is locked.
49639
+ ` + `Reply with your passphrase as your **next message** to unlock and mint the grant for **${opts.agentEscaped}**.
49640
+
49641
+ ` + `_Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches. We delete the passphrase message the moment we read it._`;
49642
+ }
49535
49643
  function createCallbackQueryHandlers(deps) {
49536
49644
  const {
49537
49645
  loadAccess,
@@ -49574,6 +49682,10 @@ function createCallbackQueryHandlers(deps) {
49574
49682
  } = deps;
49575
49683
  const bot = deps.bot;
49576
49684
  const lockedBot = deps.lockedBot;
49685
+ const mintGrantViaBroker2 = deps.brokerMintGrant ?? mintGrantViaBroker;
49686
+ const listViaBroker2 = deps.brokerList ?? listViaBroker;
49687
+ const listGrantsViaBroker2 = deps.brokerListGrants ?? listGrantsViaBroker;
49688
+ const vaultTokenFilePath2 = deps.brokerVaultTokenFilePath ?? vaultTokenFilePath;
49577
49689
  async function handleVaultRecentDenialCallback(ctx, data) {
49578
49690
  const senderId = String(ctx.from?.id ?? "");
49579
49691
  const access = loadAccess();
@@ -49596,7 +49708,7 @@ function createCallbackQueryHandlers(deps) {
49596
49708
  return;
49597
49709
  }
49598
49710
  await ctx.answerCallbackQuery({ text: "\u23f3 Minting 30-day read grant\u2026" }).catch(() => {});
49599
- const result = await mintGrantViaBroker({
49711
+ const result = await mintGrantViaBroker2({
49600
49712
  agent: agentName,
49601
49713
  keys: [keyName],
49602
49714
  ttl_seconds: 30 * 24 * 60 * 60,
@@ -49611,9 +49723,9 @@ function createCallbackQueryHandlers(deps) {
49611
49723
  return;
49612
49724
  }
49613
49725
  const { token, id } = result;
49614
- const tokenPath = join13(homedir5(), ".switchroom", "agents", agentName, ".vault-token");
49726
+ const tokenPath = vaultTokenFilePath2(agentName);
49615
49727
  try {
49616
- mkdirSync13(join13(homedir5(), ".switchroom", "agents", agentName), { recursive: true });
49728
+ mkdirSync13(dirname5(tokenPath), { recursive: true });
49617
49729
  writeFileSync9(tokenPath, token, { mode: 384 });
49618
49730
  } catch (err) {
49619
49731
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
@@ -49630,25 +49742,93 @@ function createCallbackQueryHandlers(deps) {
49630
49742
  newText: baseText ? `${baseText}${statusLine}` : statusLine
49631
49743
  });
49632
49744
  }
49745
+ async function editResolvedCard(ctx, target, messageId, markdown, label) {
49746
+ if (messageId > 0) {
49747
+ try {
49748
+ await robustApiCall(() => ctx.api.editMessageText(target.chat_id, messageId, richMessage(markdown), {
49749
+ reply_markup: { inline_keyboard: [] }
49750
+ }), { chat_id: target.chat_id, verb: `vault_request_access.${label}_edit` });
49751
+ return;
49752
+ } catch (err) {
49753
+ process.stderr.write(`telegram gateway: vault card resolution edit FAILED (${label}) ` + `chat=${target.chat_id} msg=${messageId}: ${String(err)} \u2014 sending fallback message
49754
+ `);
49755
+ }
49756
+ }
49757
+ try {
49758
+ await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendRichMessage(target.chat_id, richMessage(markdown), {
49759
+ ...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
49760
+ }), {
49761
+ threadId: target.threadId,
49762
+ chat_id: target.chat_id,
49763
+ verb: `vault_request_access.${label}_fallback`
49764
+ });
49765
+ } catch (err) {
49766
+ process.stderr.write(`telegram gateway: vault card resolution FALLBACK SEND failed (${label}) ` + `chat=${target.chat_id}: ${String(err)}
49767
+ `);
49768
+ }
49769
+ }
49770
+ async function sendAccessPassphrasePrompt(target, spec) {
49771
+ const promptText = buildAccessPassphrasePromptText(spec);
49772
+ await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendRichMessage(target.chat_id, richMessage(promptText), {
49773
+ ...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
49774
+ }), {
49775
+ threadId: target.threadId,
49776
+ chat_id: target.chat_id,
49777
+ verb: "vault_request_access.passphrase_prompt"
49778
+ }).catch((err) => {
49779
+ process.stderr.write(`telegram gateway: vault passphrase prompt send FAILED chat=${target.chat_id} ` + `kind=${spec.kind}: ${String(err)}
49780
+ `);
49781
+ });
49782
+ }
49783
+ async function resolveAccessApprovalPassphraseMismatch(ctx, args) {
49784
+ const { chat_id, failed, priorAttempts, brokerMsg } = args;
49785
+ if (failed.length === 0)
49786
+ return;
49787
+ vaultPassphraseCache.delete(chat_id);
49788
+ const attempts = priorAttempts + 1;
49789
+ const remaining = MAX_VAULT_PASSPHRASE_ATTEMPTS - attempts;
49790
+ const threadId = failed.find((it) => it.threadId != null)?.threadId;
49791
+ process.stderr.write(`telegram gateway: vault_request_access passphrase mismatch chat=${chat_id} ` + `stages=${failed.map((f) => f.stageId).join(",")} attempts=${attempts}/${MAX_VAULT_PASSPHRASE_ATTEMPTS}
49792
+ `);
49793
+ if (remaining > 0) {
49794
+ const open = pendingVaultOps.get(chat_id);
49795
+ const carried = open?.kind === "passphrase-for-access-approve" ? open.items.filter((it) => !failed.some((f) => f.stageId === it.stageId)) : [];
49796
+ const requeued = [...failed, ...carried];
49797
+ pendingVaultOps.set(chat_id, {
49798
+ kind: "passphrase-for-access-approve",
49799
+ items: requeued,
49800
+ attempts,
49801
+ startedAt: Date.now()
49802
+ });
49803
+ await sendAccessPassphrasePrompt({ chat_id, ...threadId != null ? { threadId } : {} }, { kind: "retry", retryRemaining: remaining, itemCount: requeued.length });
49804
+ return;
49805
+ }
49806
+ for (const item of failed) {
49807
+ pendingVaultRequestAccesses.delete(item.stageId);
49808
+ pendingCardStore.remove(item.stageId);
49809
+ await editResolvedCard(ctx, { chat_id: item.cardChatId, ...item.threadId != null ? { threadId: item.threadId } : {} }, item.cardMessageId, `\u274c **Too many wrong passphrase attempts** (${MAX_VAULT_PASSPHRASE_ATTEMPTS}). ` + `This request was cancelled \u2014 ask the agent to re-issue it.
49810
+ ` + `_Broker: ${escapeHtmlForTg2(brokerMsg)}_`, "passphrase_lockout");
49811
+ }
49812
+ }
49633
49813
  async function performVaultAccessApproval(ctx, pending, stageId, senderId, attestation) {
49634
49814
  const brokerAuthOpts = attestation.kind === "passphrase" ? { passphrase: attestation.passphrase } : { attest_via_posture: true };
49635
49815
  if (pending.scope === "read") {
49636
49816
  try {
49637
- const visible = await listViaBroker();
49817
+ const visible = await listViaBroker2();
49638
49818
  if (visible !== null && visible.includes(pending.key)) {
49639
49819
  pendingVaultRequestAccesses.delete(stageId);
49640
49820
  pendingCardStore.remove(stageId);
49641
49821
  if (pending.card_message_id != null) {
49642
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\u2139\ufe0f **${escapeHtmlForTg2(pending.agent)}** already has standing-ACL access to ` + `\`${pending.key}\` (schedule.secrets[]). ` + `**No grant minted** \u2014 a token would shadow the standing ACL. ` + richMessage(`The agent can read it directly.`), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
49822
+ await editResolvedCard(ctx, pending, pending.card_message_id, `\u2139\ufe0f **${escapeHtmlForTg2(pending.agent)}** already has standing-ACL access to ` + `\`${pending.key}\` (schedule.secrets[]). ` + `**No grant minted** \u2014 a token would shadow the standing ACL. ` + `The agent can read it directly.`, "standing_acl");
49643
49823
  }
49644
- return;
49824
+ return { kind: "ok" };
49645
49825
  }
49646
49826
  } catch {}
49647
49827
  }
49648
49828
  let existingReadKeys = [];
49649
49829
  let existingWriteKeys = [];
49650
49830
  if (pending.scope === "read" || pending.scope === "write") {
49651
- const list = await listGrantsViaBroker(pending.agent, brokerAuthOpts);
49831
+ const list = await listGrantsViaBroker2(pending.agent, brokerAuthOpts);
49652
49832
  if (list.kind === "ok") {
49653
49833
  const now = Math.floor(Date.now() / 1000);
49654
49834
  const active = list.grants.filter((g) => g.expires_at === null || g.expires_at > now).sort((a, b) => {
@@ -49677,28 +49857,31 @@ function createCallbackQueryHandlers(deps) {
49677
49857
  ...writeKeys.size > 0 ? { write_keys: Array.from(writeKeys) } : {},
49678
49858
  ...brokerAuthOpts
49679
49859
  };
49680
- const result = await mintGrantViaBroker(mintArgs);
49860
+ const result = await mintGrantViaBroker2(mintArgs);
49681
49861
  if (result.kind === "unreachable") {
49682
49862
  await switchroomReply(ctx, `\uD83D\uDD34 Broker unreachable: ${escapeHtmlForTg2(result.msg)}`, { html: true });
49683
- return;
49863
+ return { kind: "failed", msg: result.msg };
49684
49864
  }
49685
49865
  if (result.kind === "error") {
49866
+ if (attestation.kind === "passphrase" && isPassphraseMismatchBrokerError(result.msg)) {
49867
+ return { kind: "passphrase-mismatch", msg: result.msg };
49868
+ }
49686
49869
  pendingVaultRequestAccesses.delete(stageId);
49687
49870
  pendingCardStore.remove(stageId);
49688
49871
  if (pending.card_message_id != null) {
49689
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, richMessage(`**mint_grant failed:** ${escapeHtmlForTg2(result.msg)}`), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
49872
+ await editResolvedCard(ctx, pending, pending.card_message_id, `**mint_grant failed:** ${escapeHtmlForTg2(result.msg)}`, "mint_failed");
49690
49873
  }
49691
- return;
49874
+ return { kind: "failed", msg: result.msg };
49692
49875
  }
49693
49876
  const { token, id } = result;
49694
- const tokenPath = join13(homedir5(), ".switchroom", "agents", pending.agent, ".vault-token");
49877
+ const tokenPath = vaultTokenFilePath2(pending.agent);
49695
49878
  try {
49696
- mkdirSync13(join13(homedir5(), ".switchroom", "agents", pending.agent), { recursive: true });
49879
+ mkdirSync13(dirname5(tokenPath), { recursive: true });
49697
49880
  writeFileSync9(tokenPath, token, { mode: 384 });
49698
49881
  } catch (err) {
49699
49882
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
49700
49883
  ` + `_Recover with: \`switchroom vault grant ${escapeHtmlForTg2(pending.agent)} ` + `--keys ${escapeHtmlForTg2(pending.key)} --duration ${Math.round(pending.ttl_seconds / 86400)}d\` on the host._`, { html: true });
49701
- return;
49884
+ return { kind: "failed", msg: String(err) };
49702
49885
  }
49703
49886
  pendingVaultRequestAccesses.delete(stageId);
49704
49887
  pendingCardStore.remove(stageId);
@@ -49707,7 +49890,7 @@ function createCallbackQueryHandlers(deps) {
49707
49890
  const reasonNormalized = normalizeGrantReason(pending.reason);
49708
49891
  const footer = getVaultApprovalAuthMode() === "telegram-id" ? `
49709
49892
  _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._` : "";
49710
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, richMessage(buildVaultGrantApprovedCardText({
49893
+ await editResolvedCard(ctx, pending, pending.card_message_id, buildVaultGrantApprovedCardText({
49711
49894
  agentEscaped: escapeHtmlForTg2(pending.agent),
49712
49895
  scope: pending.scope,
49713
49896
  key: pending.key,
@@ -49715,7 +49898,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
49715
49898
  grantId: id,
49716
49899
  reasonEscaped: reasonNormalized.length > 0 ? escapeHtmlForTg2(reasonNormalized) : undefined,
49717
49900
  footer
49718
- })), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
49901
+ }), "grant_approved");
49719
49902
  }
49720
49903
  const synthetic = buildVaultGrantApprovedInbound({
49721
49904
  ctx: {
@@ -49733,6 +49916,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
49733
49916
  const delivered = deliverResumeSyntheticOrBuffer(pending.agent, synthetic);
49734
49917
  process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending.agent} ` + `key=${pending.key} stage=${stageId} delivered=${delivered}
49735
49918
  `);
49919
+ return { kind: "ok" };
49736
49920
  }
49737
49921
  async function handleSkillProposalCallback(ctx, data) {
49738
49922
  const senderId = String(ctx.from?.id ?? "");
@@ -50025,40 +50209,53 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
50025
50209
  stageId,
50026
50210
  cardChatId: pending.chat_id,
50027
50211
  cardMessageId: pending.card_message_id,
50028
- senderId
50212
+ senderId,
50213
+ ...pending.threadId != null ? { threadId: pending.threadId } : {}
50029
50214
  };
50030
50215
  const items = existing?.kind === "passphrase-for-access-approve" ? [...existing.items.filter((it) => it.stageId !== stageId), newItem] : [newItem];
50031
50216
  pendingVaultOps.set(pending.chat_id, {
50032
50217
  kind: "passphrase-for-access-approve",
50033
50218
  items,
50219
+ ...existing?.kind === "passphrase-for-access-approve" && existing.attempts ? { attempts: existing.attempts } : {},
50034
50220
  startedAt: existing?.kind === "passphrase-for-access-approve" ? existing.startedAt : Date.now()
50035
50221
  });
50036
50222
  const joiningBatch = items.length > 1;
50037
50223
  await ctx.answerCallbackQuery({ text: joiningBatch ? `\uD83D\uDD10 Queued \u2014 one passphrase covers ${items.length} cards` : "\uD83D\uDD10 Send your passphrase\u2026" }).catch(() => {});
50038
50224
  await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, richMessage(`\uD83D\uDD10 _Approved \u2014 waiting for your vault passphrase. See the prompt below._`), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
50039
- const promptText = joiningBatch ? `**\u26a0\ufe0f\uD83D\uDD10 ACTION NEEDED: passphrase required**
50040
-
50041
- ` + `Type your vault passphrase as your **next message**.
50042
- ` + `One entry covers **${items.length}** pending approvals in this chat, no re-type per card.
50043
-
50044
- ` + `_We delete the passphrase message the moment we read it._` : isAdminOnly ? `**\u26a0\ufe0f\uD83D\uDD10 ACTION NEEDED: passphrase required**
50045
-
50046
- ` + `\`${pending.key}\` is an **admin-only credential**.
50047
- ` + `Type your vault passphrase as your **next message** to mint the grant for **${escapeHtmlForTg2(pending.agent)}**.
50048
-
50049
- ` + `_The passphrase is what proves it's you. An agent can never mint this key on its own. We delete the passphrase message the moment we read it._` : `**\u26a0\ufe0f\uD83D\uDD10 ACTION NEEDED: passphrase required**
50050
-
50051
- ` + `Your vault is locked.
50052
- ` + `Reply with your passphrase as your **next message** to unlock and mint the grant for **${escapeHtmlForTg2(pending.agent)}**.
50053
-
50054
- ` + `_Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches. We delete the passphrase message the moment we read it._`;
50055
- await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendRichMessage(pending.chat_id, richMessage(promptText), {
50056
- ...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
50057
- }), { threadId: pending.threadId, chat_id: pending.chat_id, verb: "vault_request_access.passphrase_prompt" }).catch(() => {});
50225
+ await sendAccessPassphrasePrompt({
50226
+ chat_id: pending.chat_id,
50227
+ ...pending.threadId != null ? { threadId: pending.threadId } : {}
50228
+ }, {
50229
+ kind: "first",
50230
+ variant: joiningBatch ? "batch" : isAdminOnly ? "admin-only" : "locked",
50231
+ itemCount: items.length,
50232
+ agentEscaped: escapeHtmlForTg2(pending.agent),
50233
+ key: pending.key
50234
+ });
50058
50235
  return;
50059
50236
  }
50060
50237
  await ctx.answerCallbackQuery({ text: "\u23f3 Minting grant\u2026" }).catch(() => {});
50061
- await performVaultAccessApproval(ctx, pending, stageId, senderId, { kind: "passphrase", passphrase: cached.passphrase });
50238
+ const outcome = await performVaultAccessApproval(ctx, pending, stageId, senderId, {
50239
+ kind: "passphrase",
50240
+ passphrase: cached.passphrase
50241
+ });
50242
+ if (outcome.kind === "passphrase-mismatch") {
50243
+ const openOp = pendingVaultOps.get(pending.chat_id);
50244
+ await resolveAccessApprovalPassphraseMismatch(ctx, {
50245
+ chat_id: pending.chat_id,
50246
+ failed: [
50247
+ {
50248
+ stageId,
50249
+ cardChatId: pending.chat_id,
50250
+ cardMessageId: pending.card_message_id ?? 0,
50251
+ senderId,
50252
+ ...pending.threadId != null ? { threadId: pending.threadId } : {}
50253
+ }
50254
+ ],
50255
+ priorAttempts: openOp?.kind === "passphrase-for-access-approve" ? openOp.attempts ?? 0 : 0,
50256
+ brokerMsg: outcome.msg
50257
+ });
50258
+ }
50062
50259
  return;
50063
50260
  }
50064
50261
  await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
@@ -50399,7 +50596,7 @@ Which agent?`, { html: true, reply_markup: kb });
50399
50596
  });
50400
50597
  }
50401
50598
  async function grantWizardStep2(ctx, chatId, agent, wizardMsgId) {
50402
- const keys = await listViaBroker();
50599
+ const keys = await listViaBroker2();
50403
50600
  if (!keys) {
50404
50601
  await switchroomReply(ctx, "\uD83D\uDD34 Broker is not running (or unreachable). Cannot list vault keys.", { html: true });
50405
50602
  pendingVaultOps.delete(chatId);
@@ -50490,7 +50687,7 @@ ${keyList}`,
50490
50687
  } catch {
50491
50688
  return;
50492
50689
  }
50493
- const result = await mintGrantViaBroker({
50690
+ const result = await mintGrantViaBroker2({
50494
50691
  agent: state.agent,
50495
50692
  keys: state.selectedKeys,
50496
50693
  ttl_seconds: state.ttlSeconds ?? null,
@@ -50505,9 +50702,9 @@ ${keyList}`,
50505
50702
  return;
50506
50703
  }
50507
50704
  const { token, id } = result;
50508
- const tokenPath = join13(homedir5(), ".switchroom", "agents", state.agent, ".vault-token");
50705
+ const tokenPath = vaultTokenFilePath2(state.agent);
50509
50706
  try {
50510
- mkdirSync13(join13(homedir5(), ".switchroom", "agents", state.agent), { recursive: true });
50707
+ mkdirSync13(dirname5(tokenPath), { recursive: true });
50511
50708
  writeFileSync9(tokenPath, token, { mode: 384 });
50512
50709
  } catch (err) {
50513
50710
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
@@ -50534,7 +50731,7 @@ ${keyList}`,
50534
50731
  const revokeMatch = /^vg:revoke:(.+)$/.exec(data);
50535
50732
  if (revokeMatch) {
50536
50733
  const grantId = revokeMatch[1];
50537
- const result = await listGrantsViaBroker(undefined);
50734
+ const result = await listGrantsViaBroker2(undefined);
50538
50735
  if (result.kind !== "ok") {
50539
50736
  await ctx.answerCallbackQuery({ text: "Broker unreachable." }).catch(() => {});
50540
50737
  return;
@@ -50983,6 +51180,7 @@ ${trimmed.replace(/```/g, "`\u200b``")}
50983
51180
  return {
50984
51181
  handleVaultRecentDenialCallback,
50985
51182
  performVaultAccessApproval,
51183
+ resolveAccessApprovalPassphraseMismatch,
50986
51184
  handleSkillProposalCallback,
50987
51185
  handleMentalModelProposeCallback,
50988
51186
  handleVaultRequestAccessCallback,
@@ -51955,7 +52153,7 @@ function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
51955
52153
 
51956
52154
  // gateway/approval-hold.ts
51957
52155
  import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync9, chmodSync as chmodSync4, lstatSync as lstatSync2 } from "node:fs";
51958
- import { join as join16, dirname as dirname5 } from "node:path";
52156
+ import { join as join16, dirname as dirname6 } from "node:path";
51959
52157
  var BLOCKED_APPROVAL_FILE_MODE = 420;
51960
52158
  var BLOCKED_APPROVAL_DIR_MODE = 1023;
51961
52159
  var HELD_RETRY_BACKOFF_MS = 60000;
@@ -51988,7 +52186,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
51988
52186
  const fallback = fallbackDir != null ? join16(fallbackDir, "blocked-approval.json") : null;
51989
52187
  let active = primary;
51990
52188
  function tryWrite(target, body, dirMode) {
51991
- const parent = dirname5(target);
52189
+ const parent = dirname6(target);
51992
52190
  try {
51993
52191
  mkdirSync14(parent, { recursive: true });
51994
52192
  try {
@@ -62658,9 +62856,249 @@ async function retryWithThreadFallback2(retry, send, opts) {
62658
62856
  }
62659
62857
  }
62660
62858
 
62859
+ // edit-flood-fuse.ts
62860
+ var EDIT_METHODS = new Set([
62861
+ "editMessageText",
62862
+ "editMessageCaption",
62863
+ "editMessageMedia",
62864
+ "editMessageReplyMarkup",
62865
+ "editMessageLiveLocation",
62866
+ "editMessageChecklist"
62867
+ ]);
62868
+ var SEND_METHODS = new Set([
62869
+ "sendMessage",
62870
+ "sendPhoto",
62871
+ "sendDocument",
62872
+ "sendMediaGroup",
62873
+ "sendAnimation",
62874
+ "sendVideo",
62875
+ "sendVoice",
62876
+ "sendAudio",
62877
+ "sendSticker",
62878
+ "sendLocation",
62879
+ "forwardMessage",
62880
+ "forwardMessages",
62881
+ "copyMessage",
62882
+ "copyMessages",
62883
+ "sendRichMessage"
62884
+ ]);
62885
+ var EDIT_FLOOD_FUSE_DEFAULTS = {
62886
+ perMessageMaxPerWindow: 20,
62887
+ perMessageWindowMs: 60000,
62888
+ perChatEditMaxPerWindow: 30,
62889
+ perChatSendMaxPerWindow: 25,
62890
+ perChatWindowMs: 60000,
62891
+ maxDeferMs: 30000,
62892
+ tightenFactor: 0.5,
62893
+ tightenMs: 600000
62894
+ };
62895
+ var DROPPED_RESULT = true;
62896
+ function createEditFloodFuse(config = {}) {
62897
+ const enabled2 = config.enabled ?? true;
62898
+ const clock = config.clock ?? systemClock;
62899
+ const D = EDIT_FLOOD_FUSE_DEFAULTS;
62900
+ const perMessageMax = config.perMessageMaxPerWindow ?? D.perMessageMaxPerWindow;
62901
+ const perMessageWindowMs = config.perMessageWindowMs ?? D.perMessageWindowMs;
62902
+ const perChatEditMax = config.perChatEditMaxPerWindow ?? D.perChatEditMaxPerWindow;
62903
+ const perChatSendMax = config.perChatSendMaxPerWindow ?? D.perChatSendMaxPerWindow;
62904
+ const perChatWindowMs = config.perChatWindowMs ?? D.perChatWindowMs;
62905
+ const maxDeferMs = config.maxDeferMs ?? D.maxDeferMs;
62906
+ const tightenFactor = config.tightenFactor ?? D.tightenFactor;
62907
+ const tightenMs = config.tightenMs ?? D.tightenMs;
62908
+ const onTrip = config.onTrip;
62909
+ const windows = new Map;
62910
+ const counters = { deferred: 0, dropped: 0, superseded: 0, floodObserved: 0 };
62911
+ let tightenedUntil = 0;
62912
+ function isTightened(now) {
62913
+ return tightenedUntil > now;
62914
+ }
62915
+ function ceiling(base, now) {
62916
+ if (!isTightened(now))
62917
+ return base;
62918
+ return Math.max(1, Math.floor(base * tightenFactor));
62919
+ }
62920
+ function win(key) {
62921
+ let w = windows.get(key);
62922
+ if (w === undefined) {
62923
+ w = { ts: [], waiter: null, inflight: 0 };
62924
+ windows.set(key, w);
62925
+ }
62926
+ return w;
62927
+ }
62928
+ function prune(w, now, windowMs) {
62929
+ const cutoff = now - windowMs;
62930
+ while (w.ts.length > 0 && w.ts[0] <= cutoff)
62931
+ w.ts.shift();
62932
+ }
62933
+ let sinceEvict = 0;
62934
+ function evict(now) {
62935
+ if (windows.size < 4096)
62936
+ return;
62937
+ if (++sinceEvict < 1024)
62938
+ return;
62939
+ sinceEvict = 0;
62940
+ const widest = Math.max(perMessageWindowMs, perChatWindowMs);
62941
+ for (const [k, w] of windows) {
62942
+ if (w.waiter === null && w.inflight === 0 && (w.ts.length === 0 || w.ts[w.ts.length - 1] <= now - widest)) {
62943
+ windows.delete(k);
62944
+ }
62945
+ }
62946
+ }
62947
+ function waitFor(w, now, windowMs, max) {
62948
+ prune(w, now, windowMs);
62949
+ if (w.ts.length < max)
62950
+ return 0;
62951
+ return Math.max(1, w.ts[0] + windowMs - now);
62952
+ }
62953
+ function payloadKeys(payload) {
62954
+ const p = payload ?? {};
62955
+ const chat = p.chat_id != null ? String(p.chat_id) : null;
62956
+ const msg = p.message_id != null ? String(p.message_id) : null;
62957
+ return { chat, msg };
62958
+ }
62959
+ function noteFlood(now) {
62960
+ counters.floodObserved++;
62961
+ tightenedUntil = now + tightenMs;
62962
+ }
62963
+ function looksLikeFlood(err) {
62964
+ const e = err;
62965
+ if (e != null && typeof e === "object") {
62966
+ if (e.error_code === 429)
62967
+ return true;
62968
+ if (e.parameters != null && typeof e.parameters.retry_after === "number")
62969
+ return true;
62970
+ }
62971
+ const msg = err instanceof Error ? err.message : String(err ?? "");
62972
+ return /too many requests/i.test(msg) || /retry[ _-]?after/i.test(msg);
62973
+ }
62974
+ function unreserve(key, at) {
62975
+ const w = windows.get(key);
62976
+ if (w === undefined)
62977
+ return;
62978
+ const i = w.ts.lastIndexOf(at);
62979
+ if (i >= 0)
62980
+ w.ts.splice(i, 1);
62981
+ }
62982
+ async function awaitRoom(key, windowMs, max, method, mode, dropGuard) {
62983
+ const w = win(key);
62984
+ const deadline = clock.now() + maxDeferMs;
62985
+ let counted = false;
62986
+ for (;; ) {
62987
+ const now = clock.now();
62988
+ const wait = waitFor(w, now, windowMs, ceiling(max, now));
62989
+ if (wait === 0) {
62990
+ w.ts.push(now);
62991
+ return now;
62992
+ }
62993
+ if (now >= deadline) {
62994
+ if (mode !== "release" && (dropGuard === undefined || dropGuard())) {
62995
+ counters.dropped++;
62996
+ onTrip?.({ method, key, action: "dropped" });
62997
+ return null;
62998
+ }
62999
+ w.ts.push(now);
63000
+ return now;
63001
+ }
63002
+ if (!counted) {
63003
+ counters.deferred++;
63004
+ counted = true;
63005
+ onTrip?.({ method, key, action: "deferred" });
63006
+ }
63007
+ let killed = false;
63008
+ if (mode === "supersede") {
63009
+ w.waiter?.kill();
63010
+ const superseded = new Promise((resolve6) => {
63011
+ w.waiter = { kill: () => {
63012
+ killed = true;
63013
+ resolve6();
63014
+ } };
63015
+ });
63016
+ await Promise.race([clock.sleep(Math.min(wait, deadline - now)), superseded]);
63017
+ if (killed) {
63018
+ counters.superseded++;
63019
+ onTrip?.({ method, key, action: "superseded" });
63020
+ return null;
63021
+ }
63022
+ w.waiter = null;
63023
+ } else {
63024
+ await clock.sleep(Math.min(wait, deadline - now));
63025
+ }
63026
+ }
63027
+ }
63028
+ async function apply(method, payload, next) {
63029
+ if (!enabled2)
63030
+ return next();
63031
+ const isEdit = EDIT_METHODS.has(method);
63032
+ const isSend = SEND_METHODS.has(method);
63033
+ if (!isEdit && !isSend)
63034
+ return runObserved(next);
63035
+ const { chat, msg } = payloadKeys(payload);
63036
+ if (chat == null)
63037
+ return runObserved(next);
63038
+ const now = clock.now();
63039
+ evict(now);
63040
+ if (isEdit) {
63041
+ if (msg == null)
63042
+ return runObserved(next);
63043
+ const msgKey = `m:${chat}:${msg}`;
63044
+ const mw = win(msgKey);
63045
+ mw.inflight++;
63046
+ try {
63047
+ const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, perMessageMax, method, "supersede");
63048
+ if (msgSlot === null)
63049
+ return DROPPED_RESULT;
63050
+ const chatKey3 = `ce:${chat}`;
63051
+ const chatSlot = await awaitRoom(chatKey3, perChatWindowMs, perChatEditMax, method, "drop", () => mw.inflight > 1);
63052
+ if (chatSlot === null) {
63053
+ unreserve(msgKey, msgSlot);
63054
+ return DROPPED_RESULT;
63055
+ }
63056
+ return runObserved(next);
63057
+ } finally {
63058
+ mw.inflight--;
63059
+ }
63060
+ }
63061
+ const chatKey2 = `cs:${chat}`;
63062
+ await awaitRoom(chatKey2, perChatWindowMs, perChatSendMax, method, "release");
63063
+ return runObserved(next);
63064
+ }
63065
+ async function runObserved(next) {
63066
+ try {
63067
+ const res = await next();
63068
+ const r = res;
63069
+ if (r != null && typeof r === "object" && r.ok === false && r.error_code === 429) {
63070
+ noteFlood(clock.now());
63071
+ }
63072
+ return res;
63073
+ } catch (err) {
63074
+ if (looksLikeFlood(err))
63075
+ noteFlood(clock.now());
63076
+ throw err;
63077
+ }
63078
+ }
63079
+ function stats() {
63080
+ const now = clock.now();
63081
+ return {
63082
+ enabled: enabled2,
63083
+ deferred: counters.deferred,
63084
+ dropped: counters.dropped,
63085
+ superseded: counters.superseded,
63086
+ floodObserved: counters.floodObserved,
63087
+ tightened: isTightened(now),
63088
+ perMessageCeiling: ceiling(perMessageMax, now)
63089
+ };
63090
+ }
63091
+ return { apply, stats };
63092
+ }
63093
+ function installEditFloodFuse(bot, config = {}) {
63094
+ const fuse = createEditFloodFuse(config);
63095
+ bot.api.config.use(async (prev, method, payload, signal) => fuse.apply(method, payload, () => prev(method, payload, signal)));
63096
+ return fuse;
63097
+ }
63098
+
62661
63099
  // send-gate.ts
62662
63100
  import { createHash } from "node:crypto";
62663
- var systemClock = {
63101
+ var systemClock2 = {
62664
63102
  now: () => Date.now(),
62665
63103
  sleep: (ms) => new Promise((r) => setTimeout(r, ms))
62666
63104
  };
@@ -62761,7 +63199,7 @@ var SEND_GATE_DEFAULTS = {
62761
63199
  };
62762
63200
  function createSendGate(config) {
62763
63201
  const enabled2 = config.enabled;
62764
- const clock = config.clock ?? systemClock;
63202
+ const clock = config.clock ?? systemClock2;
62765
63203
  const globalPerSec = config.globalPerSec ?? SEND_GATE_DEFAULTS.globalPerSec;
62766
63204
  const globalBurst = config.globalBurst ?? SEND_GATE_DEFAULTS.globalBurst;
62767
63205
  const perChatPerSec = config.perChatPerSec ?? SEND_GATE_DEFAULTS.perChatPerSec;
@@ -63491,7 +63929,7 @@ import {
63491
63929
  unlinkSync as unlinkSync10,
63492
63930
  renameSync as renameSync9
63493
63931
  } from "node:fs";
63494
- import { dirname as dirname6, join as join17 } from "node:path";
63932
+ import { dirname as dirname7, join as join17 } from "node:path";
63495
63933
  var FLOOD_STATE_FILE = "flood-wait.json";
63496
63934
  var FLOOD_STATE_MODE = 420;
63497
63935
  function floodStatePath(stateDir) {
@@ -63544,7 +63982,7 @@ function readFloodState(path2) {
63544
63982
  function writeFloodState(path2, state, log = (l) => process.stderr.write(l)) {
63545
63983
  const payload = JSON.stringify(state);
63546
63984
  try {
63547
- mkdirSync15(dirname6(path2), { recursive: true });
63985
+ mkdirSync15(dirname7(path2), { recursive: true });
63548
63986
  } catch {}
63549
63987
  try {
63550
63988
  writeFileSync12(path2, payload, { mode: FLOOD_STATE_MODE });
@@ -63675,7 +64113,7 @@ function readFloodWindows(path2, now, log = (l) => process.stderr.write(l)) {
63675
64113
  }
63676
64114
  function writeFloodWindow(path2, record, now) {
63677
64115
  try {
63678
- mkdirSync15(dirname6(path2), { recursive: true });
64116
+ mkdirSync15(dirname7(path2), { recursive: true });
63679
64117
  const existing = readFloodWindows(path2, now);
63680
64118
  const byScope = new Map;
63681
64119
  for (const r of existing)
@@ -63850,7 +64288,7 @@ function __resetAllForTests() {
63850
64288
  }
63851
64289
 
63852
64290
  // ../node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
63853
- import { dirname as dirname7, posix, sep as sep3 } from "path";
64291
+ import { dirname as dirname8, posix, sep as sep3 } from "path";
63854
64292
  function createModulerModifier() {
63855
64293
  const getModuleFromFileName = createGetModuleFromFilename();
63856
64294
  return async (frames) => {
@@ -63859,7 +64297,7 @@ function createModulerModifier() {
63859
64297
  return frames;
63860
64298
  };
63861
64299
  }
63862
- function createGetModuleFromFilename(basePath = process.argv[1] ? dirname7(process.argv[1]) : process.cwd(), isWindows = sep3 === "\\") {
64300
+ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname8(process.argv[1]) : process.cwd(), isWindows = sep3 === "\\") {
63863
64301
  const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
63864
64302
  return (filename) => {
63865
64303
  if (!filename)
@@ -68291,7 +68729,7 @@ class PostHog extends PostHogBackendClient {
68291
68729
 
68292
68730
  // analytics-posthog.ts
68293
68731
  import { existsSync as existsSync13, mkdirSync as mkdirSync16, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "node:fs";
68294
- import { dirname as dirname8, join as join19 } from "node:path";
68732
+ import { dirname as dirname9, join as join19 } from "node:path";
68295
68733
  import { randomUUID as randomUUID2 } from "node:crypto";
68296
68734
  var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
68297
68735
  var DEFAULT_HOST = "https://us.i.posthog.com";
@@ -68330,7 +68768,7 @@ function getDistinctId() {
68330
68768
  const id = randomUUID2();
68331
68769
  cachedDistinctId = id;
68332
68770
  try {
68333
- mkdirSync16(dirname8(fallbackPath), { recursive: true });
68771
+ mkdirSync16(dirname9(fallbackPath), { recursive: true });
68334
68772
  writeFileSync13(fallbackPath, id, "utf-8");
68335
68773
  } catch {}
68336
68774
  return id;
@@ -68403,11 +68841,11 @@ function installGlobalErrorHandlers() {
68403
68841
 
68404
68842
  // runtime-metrics.ts
68405
68843
  import { mkdirSync as mkdirSync18, appendFileSync as appendFileSync3 } from "node:fs";
68406
- import { dirname as dirname10, join as join21 } from "node:path";
68844
+ import { dirname as dirname11, join as join21 } from "node:path";
68407
68845
 
68408
68846
  // analytics-posthog.ts
68409
68847
  import { existsSync as existsSync14, mkdirSync as mkdirSync17, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "node:fs";
68410
- import { dirname as dirname9, join as join20 } from "node:path";
68848
+ import { dirname as dirname10, join as join20 } from "node:path";
68411
68849
  import { randomUUID as randomUUID3 } from "node:crypto";
68412
68850
  var DEFAULT_KEY2 = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
68413
68851
  var DEFAULT_HOST2 = "https://us.i.posthog.com";
@@ -68445,7 +68883,7 @@ function getDistinctId2() {
68445
68883
  const id = randomUUID3();
68446
68884
  cachedDistinctId2 = id;
68447
68885
  try {
68448
- mkdirSync17(dirname9(fallbackPath), { recursive: true });
68886
+ mkdirSync17(dirname10(fallbackPath), { recursive: true });
68449
68887
  writeFileSync14(fallbackPath, id, "utf-8");
68450
68888
  } catch {}
68451
68889
  return id;
@@ -68500,7 +68938,7 @@ function resolveJsonlPath() {
68500
68938
  function appendJsonl(line) {
68501
68939
  const path2 = resolveJsonlPath();
68502
68940
  try {
68503
- mkdirSync18(dirname10(path2), { recursive: true });
68941
+ mkdirSync18(dirname11(path2), { recursive: true });
68504
68942
  appendFileSync3(path2, line + `
68505
68943
  `, "utf-8");
68506
68944
  } catch (err) {
@@ -69169,8 +69607,8 @@ function __resetAllForTests3() {
69169
69607
 
69170
69608
  // silent-end.ts
69171
69609
  import { existsSync as existsSync16, readFileSync as readFileSync17, writeFileSync as writeFileSync15, unlinkSync as unlinkSync11, mkdirSync as mkdirSync19 } from "node:fs";
69172
- import { dirname as dirname11, join as join22 } from "node:path";
69173
- import { homedir as homedir6 } from "node:os";
69610
+ import { dirname as dirname12, join as join22 } from "node:path";
69611
+ import { homedir as homedir5 } from "node:os";
69174
69612
  var SILENT_END_MAX_RETRIES = 2;
69175
69613
  var SILENT_END_STALE_RECORD_MAX_AGE_MS = 30 * 60000;
69176
69614
  function resolveStateDir(deps) {
@@ -69179,7 +69617,7 @@ function resolveStateDir(deps) {
69179
69617
  const env = process.env.TELEGRAM_STATE_DIR;
69180
69618
  if (env != null && env !== "")
69181
69619
  return env;
69182
- const home2 = process.env.HOME ?? homedir6();
69620
+ const home2 = process.env.HOME ?? homedir5();
69183
69621
  return join22(home2, ".claude", "channels", "telegram");
69184
69622
  }
69185
69623
  function resolveStatePath2(deps) {
@@ -69212,7 +69650,7 @@ function writeSilentEndState(args, deps) {
69212
69650
  timestamp: Date.now()
69213
69651
  };
69214
69652
  try {
69215
- mkdirSync19(dirname11(statePath), { recursive: true });
69653
+ mkdirSync19(dirname12(statePath), { recursive: true });
69216
69654
  writeFileSync15(statePath, JSON.stringify(state3), "utf8");
69217
69655
  emitLog(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
69218
69656
  `);
@@ -69301,7 +69739,7 @@ function resolveAnswerLaneConfig(input) {
69301
69739
  }
69302
69740
 
69303
69741
  // session-tail.ts
69304
- import { homedir as homedir7 } from "os";
69742
+ import { homedir as homedir6 } from "os";
69305
69743
  import { basename as basename8, join as join24 } from "path";
69306
69744
 
69307
69745
  // operator-events.ts
@@ -69704,7 +70142,7 @@ var OPERATOR_ACTIONABLE_KINDS = new Set([
69704
70142
  function sanitizeCwdToProjectName(cwd) {
69705
70143
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
69706
70144
  }
69707
- function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ?? join24(homedir7(), ".claude")) {
70145
+ function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ?? join24(homedir6(), ".claude")) {
69708
70146
  return join24(claudeHome, "projects", sanitizeCwdToProjectName(cwd));
69709
70147
  }
69710
70148
  function parseChannelMeta(content3) {
@@ -71048,7 +71486,7 @@ function defaultAddAccount(label, credentials, opts) {
71048
71486
  // ../src/auth/broker/client.ts
71049
71487
  init_protocol2();
71050
71488
  import * as net3 from "node:net";
71051
- import { homedir as homedir8 } from "node:os";
71489
+ import { homedir as homedir7 } from "node:os";
71052
71490
  import { randomUUID as randomUUID4 } from "node:crypto";
71053
71491
  import { join as join26 } from "node:path";
71054
71492
  var DEFAULT_TIMEOUT_MS3 = 5000;
@@ -71060,7 +71498,7 @@ function reviveDate2(v) {
71060
71498
  const d = new Date(v);
71061
71499
  return Number.isNaN(d.getTime()) ? null : d;
71062
71500
  }
71063
- function operatorSocketPath2(home2 = homedir8()) {
71501
+ function operatorSocketPath2(home2 = homedir7()) {
71064
71502
  return join26(home2, ".switchroom", "state", "auth-broker-operator", "sock");
71065
71503
  }
71066
71504
  function resolveAuthBrokerSocketPath2(opts) {
@@ -71629,7 +72067,7 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
71629
72067
  // gateway/auth-add-flow.ts
71630
72068
  import { execFileSync as execFileSync4 } from "node:child_process";
71631
72069
  import { existsSync as existsSync22, mkdirSync as mkdirSync23, readFileSync as readFileSync21, readdirSync as readdirSync6, rmSync as rmSync5, statSync as statSync9, writeFileSync as writeFileSync19 } from "node:fs";
71632
- import { homedir as homedir9 } from "node:os";
72070
+ import { homedir as homedir8 } from "node:os";
71633
72071
  import { join as join27 } from "node:path";
71634
72072
  import { randomBytes as randomBytes8 } from "node:crypto";
71635
72073
 
@@ -71735,7 +72173,7 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
71735
72173
  };
71736
72174
  }
71737
72175
  var pendingAuthAddFlows = new Map;
71738
- function pickScratchDir(label, home2 = homedir9()) {
72176
+ function pickScratchDir(label, home2 = homedir8()) {
71739
72177
  const suffix = randomBytes8(8).toString("hex");
71740
72178
  return join27(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
71741
72179
  }
@@ -71786,7 +72224,7 @@ async function startAccountAuthSession(label, opts = {}) {
71786
72224
  if (process.env.SWITCHROOM_TMUX_SUPERVISOR !== "1" && !opts.tmuxOps) {
71787
72225
  throw new Error('tmux supervisor required for /auth add: SWITCHROOM_TMUX_SUPERVISOR is not set to "1". ' + "Legacy pipe-based setup-token is unsupported (setup-token writes to /dev/tty, not stdout/stderr).");
71788
72226
  }
71789
- const home2 = opts.home ?? homedir9();
72227
+ const home2 = opts.home ?? homedir8();
71790
72228
  const urlTimeoutMs = opts.urlTimeoutMs ?? 30000;
71791
72229
  const agentName3 = opts.agentName ?? process.env.SWITCHROOM_AGENT_NAME ?? "gateway";
71792
72230
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
@@ -74756,7 +75194,7 @@ import {
74756
75194
  appendFileSync as appendFileSync4
74757
75195
  } from "node:fs";
74758
75196
  import { join as join29 } from "node:path";
74759
- import { homedir as homedir10 } from "node:os";
75197
+ import { homedir as homedir9 } from "node:os";
74760
75198
  function sha256Hex(s) {
74761
75199
  return createHash3("sha256").update(s, "utf8").digest("hex");
74762
75200
  }
@@ -74766,7 +75204,7 @@ function resolveStateDir2(explicit) {
74766
75204
  const env = process.env.TELEGRAM_STATE_DIR;
74767
75205
  if (env != null && env !== "")
74768
75206
  return env;
74769
- const home2 = process.env.HOME ?? homedir10();
75207
+ const home2 = process.env.HOME ?? homedir9();
74770
75208
  return join29(home2, ".claude", "channels", "telegram");
74771
75209
  }
74772
75210
  function resolveOutboxDir(stateDir) {
@@ -75620,8 +76058,8 @@ function monotonicNowMs2() {
75620
76058
 
75621
76059
  // silent-end.ts
75622
76060
  import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync22, unlinkSync as unlinkSync13, mkdirSync as mkdirSync27 } from "node:fs";
75623
- import { dirname as dirname12, join as join31 } from "node:path";
75624
- import { homedir as homedir11 } from "node:os";
76061
+ import { dirname as dirname13, join as join31 } from "node:path";
76062
+ import { homedir as homedir10 } from "node:os";
75625
76063
  var SILENT_END_MAX_RETRIES2 = 2;
75626
76064
  var SILENT_END_STALE_RECORD_MAX_AGE_MS2 = 30 * 60000;
75627
76065
  function silentEndFallbackText(turnDurationMs) {
@@ -75634,7 +76072,7 @@ function resolveStateDir3(deps) {
75634
76072
  const env = process.env.TELEGRAM_STATE_DIR;
75635
76073
  if (env != null && env !== "")
75636
76074
  return env;
75637
- const home2 = process.env.HOME ?? homedir11();
76075
+ const home2 = process.env.HOME ?? homedir10();
75638
76076
  return join31(home2, ".claude", "channels", "telegram");
75639
76077
  }
75640
76078
  function resolveStatePath3(deps) {
@@ -75667,7 +76105,7 @@ function writeSilentEndState2(args, deps) {
75667
76105
  timestamp: Date.now()
75668
76106
  };
75669
76107
  try {
75670
- mkdirSync27(dirname12(statePath), { recursive: true });
76108
+ mkdirSync27(dirname13(statePath), { recursive: true });
75671
76109
  writeFileSync22(statePath, JSON.stringify(state3), "utf8");
75672
76110
  emitLog2(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
75673
76111
  `);
@@ -75939,7 +76377,9 @@ function createBackstopReadBack(w) {
75939
76377
  };
75940
76378
  try {
75941
76379
  const r = await w.gate(() => w.editMessageText(messageId, body, editApiOpts), gateOpts);
75942
- return w.isShed(r) ? "ambiguous" : "exists";
76380
+ if (w.isShed(r) || r === undefined)
76381
+ return "ambiguous";
76382
+ return "exists";
75943
76383
  } catch (err) {
75944
76384
  return classifyReadBackError(err);
75945
76385
  }
@@ -77261,7 +77701,7 @@ init_rich_send();
77261
77701
 
77262
77702
  // runtime-metrics.ts
77263
77703
  import { mkdirSync as mkdirSync29, appendFileSync as appendFileSync6 } from "node:fs";
77264
- import { dirname as dirname13, join as join33 } from "node:path";
77704
+ import { dirname as dirname14, join as join33 } from "node:path";
77265
77705
  function resolveJsonlPath2() {
77266
77706
  const override = process.env.SWITCHROOM_RUNTIME_METRICS_PATH;
77267
77707
  if (override && override.trim() !== "")
@@ -77272,7 +77712,7 @@ function resolveJsonlPath2() {
77272
77712
  function appendJsonl2(line) {
77273
77713
  const path2 = resolveJsonlPath2();
77274
77714
  try {
77275
- mkdirSync29(dirname13(path2), { recursive: true });
77715
+ mkdirSync29(dirname14(path2), { recursive: true });
77276
77716
  appendFileSync6(path2, line + `
77277
77717
  `, "utf-8");
77278
77718
  } catch (err) {
@@ -78687,6 +79127,7 @@ function handleSessionEvent(deps, ev) {
78687
79127
  sentIds = delivery.sentIds;
78688
79128
  chunkCount = delivery.chunkCount;
78689
79129
  delivered = delivery.delivered;
79130
+ turn.landedUnconfirmed = delivery.landedUnconfirmed;
78690
79131
  outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), getCurrentTurn()?.registryKey ?? null);
78691
79132
  if (sentIds.length > 0) {
78692
79133
  flushedTurnSupersede.record(backstopChatId, backstopThreadId, { turnId: turn.turnId, messageIds: sentIds, text: capturedText }, Date.now());
@@ -79230,7 +79671,16 @@ function createNarrativeLane(deps) {
79230
79671
  reconcileStatusPin(`fg:${statusKey(chat, thread)}`, chat, { pinned: true, messageId: sent.message_id });
79231
79672
  } else {
79232
79673
  const id = turn.activityMessageId;
79233
- await robustApiCall(() => bot.api.editMessageText(chat, id, richMessage(html), {}), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.edit" });
79674
+ const editRes = await robustApiCall(() => bot.api.editMessageText(chat, id, richMessage(html), {}), {
79675
+ chat_id: chat,
79676
+ ...thread != null ? { threadId: thread } : {},
79677
+ verb: "activity-summary.edit",
79678
+ priorityClass: "cosmetic",
79679
+ messageId: id,
79680
+ editPayload: html
79681
+ });
79682
+ if (isSendGateShed(editRes))
79683
+ break;
79234
79684
  }
79235
79685
  turn.activityLastSentRender = target;
79236
79686
  } catch (err) {
@@ -79431,7 +79881,14 @@ function createNarrativeLane(deps) {
79431
79881
  if (finalHtml == null)
79432
79882
  return;
79433
79883
  try {
79434
- await robustApiCall(() => bot.api.editMessageText(chat, id, richMessage(finalHtml), {}), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.finalize" });
79884
+ await robustApiCall(() => bot.api.editMessageText(chat, id, richMessage(finalHtml), {}), {
79885
+ chat_id: chat,
79886
+ ...thread != null ? { threadId: thread } : {},
79887
+ verb: "activity-summary.finalize",
79888
+ priorityClass: "useful",
79889
+ messageId: id,
79890
+ editPayload: finalHtml
79891
+ });
79435
79892
  } catch (err) {
79436
79893
  const msg = err instanceof Error ? err.message : String(err);
79437
79894
  const low = msg.toLowerCase();
@@ -79738,12 +80195,12 @@ class AnswerReadyFlushController {
79738
80195
  }
79739
80196
 
79740
80197
  // agent-dir.ts
79741
- import { dirname as dirname14 } from "node:path";
80198
+ import { dirname as dirname15 } from "node:path";
79742
80199
  function resolveAgentDirFromEnv() {
79743
80200
  const state6 = process.env.TELEGRAM_STATE_DIR;
79744
80201
  if (!state6 || state6.trim().length === 0)
79745
80202
  return null;
79746
- return dirname14(state6);
80203
+ return dirname15(state6);
79747
80204
  }
79748
80205
 
79749
80206
  // active-reactions.ts
@@ -82450,7 +82907,7 @@ async function discoverModels(agentName3, opts = {}) {
82450
82907
  }
82451
82908
 
82452
82909
  // ../src/agents/scaffold.ts
82453
- import { dirname as dirname15, isAbsolute, join as join42, relative, resolve as resolve8 } from "node:path";
82910
+ import { dirname as dirname16, isAbsolute, join as join42, relative, resolve as resolve8 } from "node:path";
82454
82911
  init_atomic();
82455
82912
 
82456
82913
  // ../src/agents/agent-uid.ts
@@ -82469,7 +82926,7 @@ init_timezone();
82469
82926
 
82470
82927
  // ../src/cli/agent-config.ts
82471
82928
  import { join as join40 } from "node:path";
82472
- import { homedir as homedir12 } from "node:os";
82929
+ import { homedir as homedir11 } from "node:os";
82473
82930
 
82474
82931
  // ../src/cli/helpers.ts
82475
82932
  init_loader();
@@ -82489,7 +82946,7 @@ var WEBKITE_VAULT_KEYS = new Set([
82489
82946
  init_overlay_loader();
82490
82947
 
82491
82948
  // ../src/cli/agent-config.ts
82492
- var AUDIT_ROOT = join40(homedir12(), ".switchroom", "audit");
82949
+ var AUDIT_ROOT = join40(homedir11(), ".switchroom", "audit");
82493
82950
 
82494
82951
  // ../src/agents/profiles.ts
82495
82952
  var import_handlebars = __toESM(require_lib(), 1);
@@ -82527,6 +82984,116 @@ for (const name of SHARED_FRAGMENTS) {
82527
82984
  }
82528
82985
  }
82529
82986
 
82987
+ // ../src/litellm/timeout-budget.ts
82988
+ var LITELLM_ROUTER_MARGIN_S = 10;
82989
+ var LITELLM_TIMEOUT_TIERS = {
82990
+ interactive: {
82991
+ group: "gpt-oss-20b",
82992
+ localTimeoutS: 90,
82993
+ fallbackGroup: "gpt-oss-20b-openrouter",
82994
+ fallbackTimeoutS: 60
82995
+ },
82996
+ retain: {
82997
+ group: "gpt-oss-20b-retain",
82998
+ localTimeoutS: 200,
82999
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
83000
+ fallbackTimeoutS: 90
83001
+ },
83002
+ consolidation: {
83003
+ group: "gpt-oss-20b-consolidation",
83004
+ localTimeoutS: 200,
83005
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
83006
+ fallbackTimeoutS: 90
83007
+ }
83008
+ };
83009
+ function litellmChainSeconds(tier) {
83010
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
83011
+ if (tier.fallbackGroup === null)
83012
+ return tier.localTimeoutS;
83013
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
83014
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
83015
+ }
83016
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
83017
+ return litellmChainSeconds(tier) + marginS;
83018
+ }
83019
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
83020
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
83021
+ }
83022
+ function assertPositive(value, label) {
83023
+ if (!Number.isFinite(value) || value <= 0) {
83024
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
83025
+ }
83026
+ }
83027
+
83028
+ // ../src/setup/host-capabilities.ts
83029
+ init_paths();
83030
+
83031
+ // ../src/setup/hindsight-perf-defaults.ts
83032
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150;
83033
+ var HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
83034
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50;
83035
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
83036
+ var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
83037
+ var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
83038
+ var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
83039
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = 1;
83040
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
83041
+ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
83042
+ [
83043
+ "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
83044
+ String(HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE)
83045
+ ],
83046
+ [
83047
+ "HINDSIGHT_API_LINK_EXPANSION_PER_ENTITY_LIMIT",
83048
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT)
83049
+ ],
83050
+ [
83051
+ "HINDSIGHT_API_LINK_EXPANSION_TIMEOUT",
83052
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S)
83053
+ ],
83054
+ ["HINDSIGHT_API_LLM_REASONING_EFFORT", HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT]
83055
+ ];
83056
+ var HINDSIGHT_PERF_DEFAULTS_GPU = [
83057
+ ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16]
83058
+ ];
83059
+ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
83060
+ ["HINDSIGHT_API_LLM_MAX_CONCURRENT", String(HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT)],
83061
+ [
83062
+ "HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT",
83063
+ String(HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT)
83064
+ ],
83065
+ [
83066
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT",
83067
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
83068
+ ]
83069
+ ];
83070
+ var HINDSIGHT_PERF_ENV_KEYS = new Set([
83071
+ ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
83072
+ ...HINDSIGHT_PERF_DEFAULTS_GPU,
83073
+ ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
83074
+ ].map(([k]) => k));
83075
+
83076
+ // ../src/setup/hindsight-pg-defaults.ts
83077
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
83078
+ var HINDSIGHT_PG_APP_ANON_MIB = 2560;
83079
+ var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
83080
+ var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
83081
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 1536;
83082
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 4096;
83083
+ function pgMib(mib) {
83084
+ return `${mib}MB`;
83085
+ }
83086
+ var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
83087
+ var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
83088
+ var HINDSIGHT_PG_DEFAULTS = [
83089
+ [
83090
+ HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
83091
+ pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
83092
+ ],
83093
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
83094
+ ];
83095
+ var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
83096
+
82530
83097
  // ../src/setup/hindsight.ts
82531
83098
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
82532
83099
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -82536,6 +83103,18 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
82536
83103
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
82537
83104
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
82538
83105
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
83106
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
83107
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
83108
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
83109
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
83110
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
83111
+ }
83112
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
83113
+ }
83114
+ function hindsightRetainClientTimeoutSeconds() {
83115
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
83116
+ }
83117
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
82539
83118
  var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
82540
83119
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
82541
83120
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
@@ -83149,7 +83728,7 @@ init_merge();
83149
83728
  init_timezone();
83150
83729
  var import_yaml4 = __toESM(require_dist(), 1);
83151
83730
  import { readFileSync as readFileSync32, existsSync as existsSync35 } from "node:fs";
83152
- import { homedir as homedir13 } from "node:os";
83731
+ import { homedir as homedir12 } from "node:os";
83153
83732
  import { resolve as resolve9 } from "node:path";
83154
83733
 
83155
83734
  class ConfigError2 extends Error {
@@ -83206,7 +83785,7 @@ function coerceLegacyGoogleWorkspaceKeys2(parsed, filePath) {
83206
83785
  }
83207
83786
  function findConfigFile2(startDir) {
83208
83787
  const envPath = process.env.SWITCHROOM_CONFIG;
83209
- const home2 = homedir13();
83788
+ const home2 = homedir12();
83210
83789
  const userDir = resolve9(home2, ".switchroom");
83211
83790
  const searchPaths = [
83212
83791
  envPath ? resolve9(envPath) : null,
@@ -84156,6 +84735,94 @@ function shouldSweepChatAtBoot(chatId) {
84156
84735
  return n < 0;
84157
84736
  }
84158
84737
 
84738
+ // gateway/boot-sweep-gate.ts
84739
+ function createBootSweepGate(args) {
84740
+ let armed = false;
84741
+ let ready = false;
84742
+ let started = false;
84743
+ const maybeRun = () => {
84744
+ if (started || !armed || !ready)
84745
+ return;
84746
+ started = true;
84747
+ (async () => {
84748
+ try {
84749
+ await args.run();
84750
+ } catch (err) {
84751
+ args.onError?.(err);
84752
+ }
84753
+ })();
84754
+ };
84755
+ return {
84756
+ arm() {
84757
+ armed = true;
84758
+ maybeRun();
84759
+ },
84760
+ botReady() {
84761
+ ready = true;
84762
+ maybeRun();
84763
+ },
84764
+ hasRun() {
84765
+ return started;
84766
+ }
84767
+ };
84768
+ }
84769
+ async function step(name, fn, log) {
84770
+ try {
84771
+ await fn();
84772
+ } catch (err) {
84773
+ const msg = err instanceof Error ? err.message : String(err);
84774
+ try {
84775
+ log(`telegram gateway: boot pin sweep step '${name}' failed: ${msg}
84776
+ `);
84777
+ } catch {}
84778
+ }
84779
+ }
84780
+ async function runBootPinSweepSteps(deps) {
84781
+ const log = deps.log ?? ((l) => process.stderr.write(l));
84782
+ let dmChatIds = [];
84783
+ await step("dm-chat-scan", async () => {
84784
+ dmChatIds = deps.scanDmChatIds();
84785
+ }, log);
84786
+ await step("status-pin-cleanup", deps.statusPinCleanup, log);
84787
+ await step("activity-card-reaper", deps.activityCardReaper, log);
84788
+ await step("queued-card-reaper", deps.queuedCardReaper, log);
84789
+ await step("enable-dm-sweep", async () => deps.enableDmSweep(), log);
84790
+ for (const id of dmChatIds)
84791
+ await step(`dm-sweep:${id}`, () => deps.sweepDm(id), log);
84792
+ }
84793
+
84794
+ // gateway/status-pin-api.ts
84795
+ class BotNotReadyError extends Error {
84796
+ constructor(what) {
84797
+ super(`STATUS_PIN_BOT_NOT_READY: ${what} used before initGatewayBot() assigned ` + `lockedBot \u2014 the boot sweep must run behind the boot-sweep gate`);
84798
+ this.name = "BotNotReadyError";
84799
+ }
84800
+ }
84801
+ function assertBotReady(bot, what) {
84802
+ if (bot == null)
84803
+ throw new BotNotReadyError(what);
84804
+ return bot;
84805
+ }
84806
+
84807
+ class SendGateShedError extends Error {
84808
+ constructor(verb) {
84809
+ super(`STATUS_PIN_SEND_SHED: ${verb} was shed by the send gate and never reached Telegram`);
84810
+ this.name = "SendGateShedError";
84811
+ }
84812
+ }
84813
+ function assertLanded(result, verb) {
84814
+ if (result === SEND_GATE_SHED)
84815
+ throw new SendGateShedError(verb);
84816
+ return result;
84817
+ }
84818
+ function createStatusPinApi(getBot, robust) {
84819
+ const call = (verb, fn, chatId) => robust(() => fn(assertBotReady(getBot(), verb)), { chat_id: chatId, verb }).then((r) => assertLanded(r, verb));
84820
+ return {
84821
+ pinChatMessage: (chat_id, message_id, opts) => call("status-pin.pin", (bot) => bot.api.pinChatMessage(chat_id, message_id, opts), String(chat_id)),
84822
+ unpinChatMessage: (chat_id, message_id) => call("status-pin.unpin", (bot) => bot.api.unpinChatMessage(chat_id, message_id), String(chat_id))
84823
+ };
84824
+ }
84825
+
84159
84826
  // gateway/dm-pin-sweep.ts
84160
84827
  function isDmChatId(chatId) {
84161
84828
  const n = Number(chatId);
@@ -84339,7 +85006,7 @@ function startWebhookIngestServer(opts) {
84339
85006
  // ../src/web/webhook-gateway-record.ts
84340
85007
  import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync35 } from "fs";
84341
85008
  import { join as join47 } from "path";
84342
- import { homedir as homedir15 } from "os";
85009
+ import { homedir as homedir14 } from "os";
84343
85010
 
84344
85011
  // ../src/web/webhook-handler.ts
84345
85012
  import { appendFileSync as appendFileSync7, existsSync as existsSync40, mkdirSync as mkdirSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "fs";
@@ -84493,21 +85160,21 @@ function sweepStaleParks(lockPath) {
84493
85160
  } catch {}
84494
85161
  }
84495
85162
  function fsyncAndMeasure(snapshotPath, logPath, tag) {
84496
- let step = "open";
85163
+ let step2 = "open";
84497
85164
  try {
84498
85165
  const fd = fs2.openSync(snapshotPath, "r");
84499
85166
  try {
84500
- step = "fsync";
85167
+ step2 = "fsync";
84501
85168
  fs2.fsyncSync(fd);
84502
- step = "measure";
85169
+ step2 = "measure";
84503
85170
  const size = fs2.fstatSync(fd).size;
84504
- step = "close";
85171
+ step2 = "close";
84505
85172
  return size;
84506
85173
  } finally {
84507
85174
  fs2.closeSync(fd);
84508
85175
  }
84509
85176
  } catch (err) {
84510
- process.stderr.write(`[${tag}] ERROR: could not ${step} snapshot ${snapshotPath}; leaving active log ${logPath} intact to avoid data loss: ${err.message}
85177
+ process.stderr.write(`[${tag}] ERROR: could not ${step2} snapshot ${snapshotPath}; leaving active log ${logPath} intact to avoid data loss: ${err.message}
84511
85178
  `);
84512
85179
  return null;
84513
85180
  }
@@ -84729,7 +85396,7 @@ var throttleIssueWindow = new Map;
84729
85396
  // ../src/web/webhook-dispatch.ts
84730
85397
  import { existsSync as existsSync41, mkdirSync as mkdirSync34, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
84731
85398
  import { join as join46 } from "path";
84732
- import { homedir as homedir14 } from "os";
85399
+ import { homedir as homedir13 } from "os";
84733
85400
 
84734
85401
  // ../src/agent-scheduler/ipc-client.ts
84735
85402
  import { createConnection as createConnection2 } from "node:net";
@@ -85106,7 +85773,7 @@ async function defaultInject(socketPath, agentName3, inbound) {
85106
85773
  }
85107
85774
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
85108
85775
  const log = deps.log ?? ((s) => process.stderr.write(s));
85109
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir14(), ".switchroom", "agents", a));
85776
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir13(), ".switchroom", "agents", a));
85110
85777
  const now = (deps.now ?? Date.now)();
85111
85778
  const socketPath = join46(resolveAgentDir(agent), "telegram", "gateway.sock");
85112
85779
  const inbound = {
@@ -85180,7 +85847,7 @@ function evaluateDispatch(args, deps = {}) {
85180
85847
  const log = deps.log ?? ((s) => process.stderr.write(s));
85181
85848
  const now = (deps.now ?? Date.now)();
85182
85849
  const nowDate = deps.nowDate ?? (() => new Date(now));
85183
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir14(), ".switchroom", "agents", a));
85850
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir13(), ".switchroom", "agents", a));
85184
85851
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
85185
85852
  if (!DISPATCH_SOURCES.includes(args.source))
85186
85853
  return 0;
@@ -85258,7 +85925,7 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
85258
85925
  function recordWebhookEvent(rec, deps = {}) {
85259
85926
  const log = deps.log ?? ((s) => process.stderr.write(s));
85260
85927
  const now = rec.ts || (deps.now ?? Date.now)();
85261
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(homedir15(), ".switchroom", "agents", a));
85928
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(homedir14(), ".switchroom", "agents", a));
85262
85929
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
85263
85930
  const agent = rec.agent;
85264
85931
  const telegramDir = join47(resolveAgentDir(agent), "telegram");
@@ -86999,10 +87666,10 @@ function createCapturedResumeDispatcher(ports) {
86999
87666
  if (delivered) {
87000
87667
  ports.obligationLedger.close(o.originTurnId);
87001
87668
  ports.backstopDeliveryLedger.clear(o.originTurnId);
87002
- stderr(`telegram gateway: captured-answer resume delivered \u2014 origin=${o.originTurnId} ` + `${sentIds.length} chunk(s) confirmed; obligation closed
87669
+ stderr(`telegram gateway: captured-answer resume delivered \u2014 origin=${o.originTurnId} ` + `${sentIds.length} message id(s) landed; obligation closed
87003
87670
  `);
87004
87671
  } else {
87005
- stderr(`telegram gateway: captured-answer resume partial \u2014 origin=${o.originTurnId} ` + `tail still not confirmed; left OPEN for retry
87672
+ stderr(`telegram gateway: captured-answer resume partial \u2014 origin=${o.originTurnId} ` + `tail still not landed; left OPEN for retry
87006
87673
  `);
87007
87674
  }
87008
87675
  } catch (err) {
@@ -87875,6 +88542,14 @@ function resolveAnswerThreadId(input) {
87875
88542
 
87876
88543
  // gateway/turns-jsonl-rotate.ts
87877
88544
  var TURNS_JSONL_MAX_BYTES = 5 * 1024 * 1024;
88545
+ var DEFAULT_AGENT_STATE_DIR = "/state/agent";
88546
+ function resolveAgentStateDir(env = process.env) {
88547
+ const dir = env.SWITCHROOM_AGENT_STATE_DIR?.trim();
88548
+ return dir != null && dir !== "" ? dir.replace(/\/+$/, "") : DEFAULT_AGENT_STATE_DIR;
88549
+ }
88550
+ function resolveTurnsJsonlPath(env = process.env) {
88551
+ return `${resolveAgentStateDir(env)}/turns.jsonl`;
88552
+ }
87878
88553
  function maybeRotate(path3, fs3, maxBytes = TURNS_JSONL_MAX_BYTES) {
87879
88554
  const size = fs3.statSize(path3);
87880
88555
  if (size == null || size < maxBytes)
@@ -87903,7 +88578,8 @@ function buildTurnRecord(turn, endedAt) {
87903
88578
  duration_ms: turn.startedAt > 0 ? endedAt - turn.startedAt : 0,
87904
88579
  tools: turn.toolCallCount ?? 0,
87905
88580
  status: computeTurnStatus(turn),
87906
- turn_id: turn.turnId
88581
+ turn_id: turn.turnId,
88582
+ ...turn.landedUnconfirmed != null && turn.landedUnconfirmed > 0 ? { landed_unconfirmed: turn.landedUnconfirmed } : {}
87907
88583
  };
87908
88584
  }
87909
88585
 
@@ -88085,8 +88761,16 @@ async function runBackstopDelivery(ledger, turnId, chunks, cardMessageId, deps,
88085
88761
  break;
88086
88762
  }
88087
88763
  const sentIds = ledger.sentIds(turnId);
88088
- const delivered = ledger.allConfirmed(turnId, chunkCount) && backstopReceiptIds(ledger.confirmedIds(turnId), cardMessageId).length > 0;
88764
+ const confirmed = ledger.allConfirmed(turnId, chunkCount);
88765
+ const allLanded = chunkCount > 0 && ledger.unsentIndices(turnId, chunkCount).length === 0;
88766
+ const delivered = allLanded && backstopReceiptIds(sentIds, cardMessageId).length > 0;
88089
88767
  const exhausted = !delivered;
88768
+ const confirmedSet = new Set(ledger.confirmedIds(turnId));
88769
+ const landedUnconfirmedIds = sentIds.filter((id) => !confirmedSet.has(id));
88770
+ if (delivered && !confirmed) {
88771
+ stderr(`telegram gateway: backstop delivery landed-unconfirmed for turn ${turnId} \u2014 ` + `every chunk returned a fresh message id but the read-back probe was ` + `inconclusive for ${landedUnconfirmedIds.length} of ${sentIds.length} landed ` + `id(s); counting it delivered (an ambiguous probe is not a failure)
88772
+ `);
88773
+ }
88090
88774
  if (deps.recordOutbound && sentIds.length > 0) {
88091
88775
  const texts = [];
88092
88776
  const ids = [];
@@ -88098,7 +88782,7 @@ async function runBackstopDelivery(ledger, turnId, chunks, cardMessageId, deps,
88098
88782
  }
88099
88783
  deps.recordOutbound(ids, texts);
88100
88784
  }
88101
- return { sentIds, chunkCount, delivered, attempts, exhausted };
88785
+ return { sentIds, chunkCount, delivered, confirmed, landedUnconfirmedIds, attempts, exhausted };
88102
88786
  }
88103
88787
 
88104
88788
  // gateway/inbound-delivery-confirm.ts
@@ -93053,9 +93737,9 @@ import {
93053
93737
  renameSync as renameSync20
93054
93738
  } from "node:fs";
93055
93739
  import { join as join50, resolve as resolve10 } from "node:path";
93056
- import { homedir as homedir16 } from "node:os";
93740
+ import { homedir as homedir15 } from "node:os";
93057
93741
  function registryDir() {
93058
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join50(homedir16(), ".switchroom", "worktrees"));
93742
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join50(homedir15(), ".switchroom", "worktrees"));
93059
93743
  }
93060
93744
  function recordPath(id) {
93061
93745
  return join50(registryDir(), `${id}.json`);
@@ -93238,17 +93922,17 @@ init_boot_card();
93238
93922
  // gateway/update-announce.ts
93239
93923
  import { existsSync as existsSync49, mkdirSync as mkdirSync41, openSync as openSync10, closeSync as closeSync10, readFileSync as readFileSync49 } from "node:fs";
93240
93924
  import { join as join55 } from "node:path";
93241
- import { homedir as homedir18 } from "node:os";
93925
+ import { homedir as homedir17 } from "node:os";
93242
93926
 
93243
93927
  // ../src/host-control/audit-reader.ts
93244
- import { homedir as homedir17 } from "node:os";
93928
+ import { homedir as homedir16 } from "node:os";
93245
93929
  import { join as join54 } from "node:path";
93246
93930
 
93247
93931
  // ../src/host-control/audit-rotation-config.ts
93248
93932
  var DEFAULT_HOSTD_AUDIT_MAX_BYTES = 32 * 1024 * 1024;
93249
93933
 
93250
93934
  // ../src/host-control/audit-reader.ts
93251
- function defaultAuditLogPath(home2 = homedir17()) {
93935
+ function defaultAuditLogPath(home2 = homedir16()) {
93252
93936
  return join54(home2, ".switchroom", "host-control-audit.log");
93253
93937
  }
93254
93938
  var AUDIT_READ_WINDOW_BYTES = 4 * 1024 * 1024;
@@ -93435,7 +94119,7 @@ function renderUpdateOutcomeLine(entry) {
93435
94119
  `);
93436
94120
  }
93437
94121
  function claimUpdateAnnouncement(requestId, opts = {}) {
93438
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join55(homedir18(), ".switchroom");
94122
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join55(homedir17(), ".switchroom");
93439
94123
  const dir = join55(stateDir, "update-announced");
93440
94124
  try {
93441
94125
  mkdirSync41(dir, { recursive: true });
@@ -95737,10 +96421,10 @@ function startOutboxSweep(deps) {
95737
96421
  }
95738
96422
 
95739
96423
  // ../src/build-info.ts
95740
- var VERSION2 = "0.19.19";
95741
- var COMMIT_SHA = "0bdb34ec";
95742
- var COMMIT_DATE = "2026-07-26T08:27:14+10:00";
95743
- var LATEST_PR = 3618;
96424
+ var VERSION2 = "0.19.23";
96425
+ var COMMIT_SHA = "56e3c01e";
96426
+ var COMMIT_DATE = "2026-07-26T13:24:39Z";
96427
+ var LATEST_PR = 3711;
95744
96428
  var COMMITS_AHEAD_OF_TAG = 0;
95745
96429
 
95746
96430
  // gateway/boot-version.ts
@@ -95817,12 +96501,12 @@ init_protocol();
95817
96501
  init_peercred();
95818
96502
  import * as net5 from "node:net";
95819
96503
  import * as fs3 from "node:fs";
95820
- import { homedir as homedir19 } from "node:os";
96504
+ import { homedir as homedir18 } from "node:os";
95821
96505
  import { join as join63 } from "node:path";
95822
96506
  var DEFAULT_TIMEOUT_MS4 = 2000;
95823
96507
  var UNLOCK_TIMEOUT_MS = 30000;
95824
- var LEGACY_SOCKET_PATH2 = join63(homedir19(), ".switchroom", "vault-broker.sock");
95825
- var OPERATOR_SOCKET_PATH2 = join63(homedir19(), ".switchroom", "broker-operator", "sock");
96508
+ var LEGACY_SOCKET_PATH2 = join63(homedir18(), ".switchroom", "vault-broker.sock");
96509
+ var OPERATOR_SOCKET_PATH2 = join63(homedir18(), ".switchroom", "broker-operator", "sock");
95826
96510
  function defaultBrokerSocketPath2() {
95827
96511
  if (fs3.existsSync(OPERATOR_SOCKET_PATH2))
95828
96512
  return OPERATOR_SOCKET_PATH2;
@@ -97596,11 +98280,11 @@ if (isGatewayMain) {
97596
98280
  shutdownAnalytics();
97597
98281
  });
97598
98282
  }
97599
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join65(homedir20(), ".claude", "channels", "telegram");
98283
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join65(homedir19(), ".claude", "channels", "telegram");
97600
98284
  var permCardStore = createPermissionCardStore(STATE_DIR);
97601
98285
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
97602
98286
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
97603
- var blockedApprovalStore = createBlockedApprovalStore(BLOCKED_APPROVALS_DIR, AGENT_NAME, process.env.SWITCHROOM_AGENT_STATE_DIR ?? "/state/agent");
98287
+ var blockedApprovalStore = createBlockedApprovalStore(BLOCKED_APPROVALS_DIR, AGENT_NAME, resolveAgentStateDir());
97604
98288
  function reconcileBlockedApprovals() {
97605
98289
  const oldest = selectOldestHeld(pendingPermissions);
97606
98290
  if (oldest == null) {
@@ -98381,7 +99065,8 @@ async function deliverAnswer(args) {
98381
99065
  sentIds: result.sentIds,
98382
99066
  chunkCount: result.chunkCount,
98383
99067
  delivered: result.delivered,
98384
- exhausted: result.exhausted
99068
+ exhausted: result.exhausted,
99069
+ landedUnconfirmed: result.landedUnconfirmedIds.length
98385
99070
  };
98386
99071
  }
98387
99072
  var chatAvailableReactions = new Map;
@@ -98853,9 +99538,9 @@ function maybePostBusyAck(gateDecision, chatId, threadId) {
98853
99538
  const inFlight = currentTurn;
98854
99539
  const inFlightKey = inFlight != null ? statusKey(inFlight.sessionChatId, inFlight.sessionThreadId) : key;
98855
99540
  const now = Date.now();
98856
- const step = longestInFlightTool(inFlightKey, now);
99541
+ const step2 = longestInFlightTool(inFlightKey, now);
98857
99542
  const midToolCall = toolFlightTracker.isMidToolCall();
98858
- const stepAgeMs = step?.durationMs ?? null;
99543
+ const stepAgeMs = step2?.durationMs ?? null;
98859
99544
  const alreadyAcked = queuedStatusMsgIds.has(key) || busyAckPostedKeys.has(key);
98860
99545
  const fire = shouldPostBusyAck({ gateDecision, midToolCall, stepAgeMs, alreadyAcked });
98861
99546
  if (!fire) {
@@ -98879,10 +99564,10 @@ function maybePostBusyAck(gateDecision, chatId, threadId) {
98879
99564
  busyAckPostedKeys.add(key);
98880
99565
  const text5 = formatBusyAckText({
98881
99566
  gateDecision,
98882
- toolName: step?.name ?? null,
98883
- toolLabel: step?.label ?? null
99567
+ toolName: step2?.name ?? null,
99568
+ toolLabel: step2?.label ?? null
98884
99569
  });
98885
- process.stderr.write(`telegram gateway: mid-flight busy ack chat=${chatId} thread=${threadId ?? "-"} decision=${gateDecision} step=${step?.name ?? "-"} step_age_ms=${step?.durationMs ?? "-"}
99570
+ process.stderr.write(`telegram gateway: mid-flight busy ack chat=${chatId} thread=${threadId ?? "-"} decision=${gateDecision} step=${step2?.name ?? "-"} step_age_ms=${step2?.durationMs ?? "-"}
98886
99571
  `);
98887
99572
  postBusyAck(chatId, threadId, text5);
98888
99573
  }
@@ -99208,7 +99893,7 @@ function snapshotContextOccupancy() {
99208
99893
  } catch {
99209
99894
  cap = null;
99210
99895
  }
99211
- const stateDir = process.env.SWITCHROOM_AGENT_STATE_DIR ?? "/state/agent";
99896
+ const stateDir = resolveAgentStateDir();
99212
99897
  writeContextOccupancySnapshot(stateDir, buildContextOccupancy(occupancy, cap, Date.now()));
99213
99898
  } catch {}
99214
99899
  }
@@ -99220,10 +99905,11 @@ function emitTurnRecord(turn, endedAt) {
99220
99905
  toolCallCount: turn.toolCallCount ?? 0,
99221
99906
  turnId: turn.turnId,
99222
99907
  finalAnswerDelivered: turn.finalAnswerDelivered,
99223
- deliveryOutcome: turn.deliveryOutcome
99908
+ deliveryOutcome: turn.deliveryOutcome,
99909
+ landedUnconfirmed: turn.landedUnconfirmed
99224
99910
  }, endedAt)) + `
99225
99911
  `;
99226
- const turnsPath = "/state/agent/turns.jsonl";
99912
+ const turnsPath = resolveTurnsJsonlPath();
99227
99913
  maybeRotate(turnsPath, {
99228
99914
  statSize: (p) => {
99229
99915
  try {
@@ -100842,10 +101528,7 @@ var TOOL_PIN_TTL_MS = (() => {
100842
101528
  return Number.isFinite(v) && v > 0 ? v : 604800000;
100843
101529
  })();
100844
101530
  function statusPinApi() {
100845
- return {
100846
- pinChatMessage: (chat_id, message_id, opts) => robustApiCall(() => lockedBot.api.pinChatMessage(chat_id, message_id, opts), { chat_id: String(chat_id), verb: "status-pin.pin" }),
100847
- unpinChatMessage: (chat_id, message_id) => robustApiCall(() => lockedBot.api.unpinChatMessage(chat_id, message_id), { chat_id: String(chat_id), verb: "status-pin.unpin" })
100848
- };
101531
+ return createStatusPinApi(() => lockedBot, robustApiCall);
100849
101532
  }
100850
101533
  async function statusPinBootCleanup() {
100851
101534
  if (!statusPinPersistEnabled && !bannerPinPersistEnabled && !toolPinPersistEnabled)
@@ -101165,25 +101848,25 @@ var dmPinSweeper = createDmPinSweeper({
101165
101848
  eligible: () => dmPinSweepEligible,
101166
101849
  log: (line) => process.stderr.write(line)
101167
101850
  });
101168
- async function runBootPinCleanupAndDmSweep() {
101169
- let dmChatIds = [];
101170
- try {
101171
- dmChatIds = collectDmChatIdsFromStores({
101851
+ function runBootPinCleanupAndDmSweep() {
101852
+ return runBootPinSweepSteps({
101853
+ scanDmChatIds: () => collectDmChatIdsFromStores({
101172
101854
  statusPins: statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled ? loadStatusPins(STATUS_PIN_STORE_PATH, statusPinStoreFs) : [],
101173
101855
  activityCards: activityCardPersistEnabled ? loadActivityCards2(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs) : [],
101174
101856
  queuedCards: queuedCardPersistEnabled ? loadQueuedCards(QUEUED_CARD_STORE_PATH, queuedCardStoreFs) : []
101175
- });
101176
- } catch (err) {
101177
- process.stderr.write(`telegram gateway: dm-pin-sweep: store scan failed: ${err.message}
101178
- `);
101179
- }
101180
- await statusPinBootCleanup();
101181
- await activityCardBootReaper();
101182
- await queuedCardBootReaper();
101183
- dmPinSweepEligible = true;
101184
- for (const id of dmChatIds)
101185
- await dmPinSweeper.sweep(id);
101857
+ }),
101858
+ statusPinCleanup: statusPinBootCleanup,
101859
+ activityCardReaper: activityCardBootReaper,
101860
+ queuedCardReaper: queuedCardBootReaper,
101861
+ enableDmSweep: () => {
101862
+ dmPinSweepEligible = true;
101863
+ },
101864
+ sweepDm: (id) => dmPinSweeper.sweep(id),
101865
+ log: (line) => process.stderr.write(line)
101866
+ });
101186
101867
  }
101868
+ var bootPinSweepGate = createBootSweepGate({ run: runBootPinCleanupAndDmSweep, onError: (err) => process.stderr.write(`telegram gateway: boot pin cleanup / DM sweep failed: ${err.message}
101869
+ `) });
101187
101870
  var progressDriver = null;
101188
101871
  var unpinProgressCardForChat = null;
101189
101872
  var getPinnedProgressCardMessageId = null;
@@ -101255,7 +101938,7 @@ if (isGatewayMain) {
101255
101938
  if (carrierAgentDir != null)
101256
101939
  consumeSessionModelCarrierOnHealthyBoot(carrierAgentDir);
101257
101940
  }
101258
- runBootPinCleanupAndDmSweep();
101941
+ bootPinSweepGate.arm();
101259
101942
  } catch (err) {
101260
101943
  process.stderr.write(`telegram gateway: boot.lock_acquire_failed err=${err.message} agent=${SWITCHROOM_AGENT_NAME}
101261
101944
  `);
@@ -101268,7 +101951,7 @@ if (isGatewayMain) {
101268
101951
  if (carrierAgentDir != null)
101269
101952
  consumeSessionModelCarrierOnHealthyBoot(carrierAgentDir);
101270
101953
  }
101271
- runBootPinCleanupAndDmSweep();
101954
+ bootPinSweepGate.arm();
101272
101955
  } catch (writeErr) {
101273
101956
  process.stderr.write(`telegram gateway: writePidFile failed: ${writeErr}
101274
101957
  `);
@@ -105097,7 +105780,7 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
105097
105780
  }
105098
105781
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
105099
105782
  try {
105100
- const auditPath = join65(homedir20(), ".switchroom", "vault-audit.log");
105783
+ const auditPath = join65(homedir19(), ".switchroom", "vault-audit.log");
105101
105784
  if (!existsSync58(auditPath))
105102
105785
  return [];
105103
105786
  const raw = readFileSync58(auditPath, "utf8");
@@ -108608,6 +109291,11 @@ async function initGatewayBot() {
108608
109291
  bot = new import_grammy16.Bot(TOKEN);
108609
109292
  installTgPostLogger(bot);
108610
109293
  installRichMarkdownGuard(bot);
109294
+ installEditFloodFuse(bot, {
109295
+ enabled: process.env.SWITCHROOM_EDIT_FUSE !== "0",
109296
+ onTrip: (i) => process.stderr.write(`edit-flood-fuse ${i.action} method=${i.method} key=${i.key}
109297
+ `)
109298
+ });
108611
109299
  installUpdateTap(bot, (line) => process.stderr.write(line));
108612
109300
  bot.api.config.use(async (prev, method, payload, signal) => {
108613
109301
  try {
@@ -108698,6 +109386,7 @@ async function initGatewayBot() {
108698
109386
  MENTAL_MODEL_PROPOSE_MAX_PER_WINDOW
108699
109387
  });
108700
109388
  registerGatewayHandlers(bot, {});
109389
+ bootPinSweepGate.botReady();
108701
109390
  }
108702
109391
  var didOneTimeSetup = false;
108703
109392
  async function startGateway() {
@@ -108960,7 +109649,7 @@ async function startGateway() {
108960
109649
  return;
108961
109650
  }
108962
109651
  })();
108963
- const resolvedAgentDirForBootCard = agentDir ?? join65(homedir20(), ".switchroom", "agents", agentSlug);
109652
+ const resolvedAgentDirForBootCard = agentDir ?? join65(homedir19(), ".switchroom", "agents", agentSlug);
108964
109653
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
108965
109654
  agentName: agentDisplayName,
108966
109655
  agentSlug,