switchroom 0.19.41 → 0.19.43

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 (90) hide show
  1. package/dist/agent-scheduler/index.js +23 -3
  2. package/dist/auth-broker/index.js +54 -9
  3. package/dist/cli/notion-write-pretool.mjs +23 -3
  4. package/dist/cli/switchroom.js +1956 -900
  5. package/dist/host-control/main.js +227 -30
  6. package/dist/vault/approvals/kernel-server.js +54 -9
  7. package/dist/vault/broker/server.js +54 -9
  8. package/package.json +3 -3
  9. package/profiles/_base/start.sh.hbs +82 -4
  10. package/telegram-plugin/bridge/bridge.ts +1 -1
  11. package/telegram-plugin/card-layout.ts +63 -1
  12. package/telegram-plugin/dist/bridge/bridge.js +4 -1
  13. package/telegram-plugin/dist/gateway/gateway.js +1318 -693
  14. package/telegram-plugin/dist/server.js +5 -2
  15. package/telegram-plugin/flood-429-ledger.ts +5 -3
  16. package/telegram-plugin/flood-circuit-breaker.ts +11 -3
  17. package/telegram-plugin/format.ts +223 -13
  18. package/telegram-plugin/gateway/approval-callback-consume-record.test.ts +132 -0
  19. package/telegram-plugin/gateway/boot-card.ts +7 -2
  20. package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
  21. package/telegram-plugin/gateway/gateway.ts +56 -58
  22. package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
  23. package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
  24. package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
  25. package/telegram-plugin/gateway/ipc-server.ts +13 -4
  26. package/telegram-plugin/gateway/liveness-wiring.ts +12 -0
  27. package/telegram-plugin/gateway/model-command.ts +21 -109
  28. package/telegram-plugin/gateway/obligation-store.ts +55 -0
  29. package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
  30. package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
  31. package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
  32. package/telegram-plugin/gateway/stream-render.ts +62 -0
  33. package/telegram-plugin/gateway/update-announce.ts +11 -4
  34. package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
  35. package/telegram-plugin/hooks/audience-classify.mjs +364 -0
  36. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
  37. package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
  38. package/telegram-plugin/outbox.ts +103 -2
  39. package/telegram-plugin/render/rich-render.ts +10 -2
  40. package/telegram-plugin/reply-quote.ts +150 -0
  41. package/telegram-plugin/retry-api-call.ts +254 -11
  42. package/telegram-plugin/shared/bot-runtime.ts +69 -34
  43. package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
  44. package/telegram-plugin/silent-end.ts +34 -2
  45. package/telegram-plugin/status-no-truncate.ts +31 -16
  46. package/telegram-plugin/stream-controller.ts +44 -9
  47. package/telegram-plugin/stream-reply-handler.ts +5 -3
  48. package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
  49. package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
  50. package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
  51. package/telegram-plugin/tests/card-variants.golden.txt +9 -9
  52. package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
  53. package/telegram-plugin/tests/format-consistency.test.ts +160 -14
  54. package/telegram-plugin/tests/framework-fallback-drains-parked.test.ts +134 -0
  55. package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
  56. package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
  57. package/telegram-plugin/tests/helpers/liveness-wiring-fixture.ts +3 -0
  58. package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
  59. package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
  60. package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
  61. package/telegram-plugin/tests/obligation-store.test.ts +51 -0
  62. package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
  63. package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
  64. package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
  65. package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
  66. package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
  67. package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
  68. package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
  69. package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
  70. package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
  71. package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
  72. package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
  73. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
  74. package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
  75. package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
  76. package/telegram-plugin/tool-activity-summary.ts +58 -30
  77. package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
  78. package/vendor/hindsight-memory/CHANGELOG.md +28 -0
  79. package/vendor/hindsight-memory/docs/measurements/subagent-volume-gate-3994.md +84 -0
  80. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +254 -40
  81. package/vendor/hindsight-memory/scripts/lib/content.py +11 -2
  82. package/vendor/hindsight-memory/scripts/recall.py +18 -3
  83. package/vendor/hindsight-memory/scripts/subagent_retain.py +59 -38
  84. package/vendor/hindsight-memory/scripts/tests/data/replay_volume_gate_3994.py +295 -0
  85. package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +109 -0
  86. package/vendor/hindsight-memory/scripts/tests/test_overlap_tokens.py +135 -0
  87. package/vendor/hindsight-memory/scripts/tests/test_recall_no_lexical_gate.py +20 -23
  88. package/vendor/hindsight-memory/scripts/tests/test_recall_query_shaping.py +48 -0
  89. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +94 -1
  90. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain_learnings.py +98 -42
@@ -6965,7 +6965,9 @@ function stripExcessBold(text, onStrip) {
6965
6965
  const ratio = boldChars / visible.length;
6966
6966
  const blocks = masked.split(/\n{2,}/);
6967
6967
  if (ratio > 0.3) {
6968
- const rebuilt2 = blocks.map((block) => isPseudoHeadingBlock(block) ? block : unbold(block));
6968
+ const contentBlocks = blocks.filter((b) => stripPlaceholders(b).trim() !== "");
6969
+ const allHeadings = contentBlocks.length > 0 && contentBlocks.every(isPseudoHeadingBlock);
6970
+ const rebuilt2 = blocks.map((block) => !allHeadings && isPseudoHeadingBlock(block) ? block : unbold(block));
6969
6971
  const out2 = rejoinBlocks(masked, rebuilt2);
6970
6972
  if (out2 !== masked)
6971
6973
  onStrip?.({ rule: "global", ratio });
@@ -7114,6 +7116,25 @@ function hardSliceToCap(text, cap = RICH_MESSAGE_MAX_CHARS) {
7114
7116
  }
7115
7117
  return out;
7116
7118
  }
7119
+ function splitPlainTextToCap(text, cap = PLAIN_TEXT_MAX_CHARS) {
7120
+ if (cap <= 0)
7121
+ return [text];
7122
+ if (text.length <= cap)
7123
+ return [text];
7124
+ const out = [];
7125
+ for (const piece of splitMarkdownChunks(text, cap)) {
7126
+ if (piece.length <= cap) {
7127
+ if (piece.length > 0)
7128
+ out.push(piece);
7129
+ continue;
7130
+ }
7131
+ for (const slice of hardSliceToCap(piece, cap)) {
7132
+ if (slice.length > 0)
7133
+ out.push(slice);
7134
+ }
7135
+ }
7136
+ return out.length > 0 ? out : [text];
7137
+ }
7117
7138
  function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS) {
7118
7139
  if (text.length <= maxLen)
7119
7140
  return [text];
@@ -7138,9 +7159,7 @@ function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS) {
7138
7159
  } else if (spaceIdx > 0) {
7139
7160
  cut = spaceIdx;
7140
7161
  }
7141
- cut = backOffOpenFence(rest, cut);
7142
- cut = backOffTableRow(rest, cut);
7143
- cut = backOffOpenInline(rest, cut);
7162
+ cut = safeMarkdownCut(rest, cut);
7144
7163
  if (cut <= 0) {
7145
7164
  const sliced = hardSliceToCap(rest, maxLen);
7146
7165
  chunks.push(stripBoundarySpacers(sliced[0], "trailing"));
@@ -7186,6 +7205,55 @@ function backOffTableRow(text, cut) {
7186
7205
  }
7187
7206
  return cut;
7188
7207
  }
7208
+ function safeMarkdownCut(text, cut) {
7209
+ if (cut <= 0)
7210
+ return 0;
7211
+ if (cut >= text.length)
7212
+ return text.length;
7213
+ let safe = backOffOpenFence(text, cut);
7214
+ safe = backOffTableRow(text, safe);
7215
+ safe = backOffOpenInline(text, safe);
7216
+ while (safe > 0 && DELIMITER_RUN_CHARS.has(text[safe]) && text[safe - 1] === text[safe])
7217
+ safe--;
7218
+ if (safe <= 0)
7219
+ return 0;
7220
+ const prev = text.charCodeAt(safe - 1);
7221
+ const here = text.charCodeAt(safe);
7222
+ if (prev >= 55296 && prev <= 56319 && here >= 56320 && here <= 57343)
7223
+ safe -= 1;
7224
+ return safe;
7225
+ }
7226
+ function truncateMarkdownSafe(text, budget) {
7227
+ if (budget <= 0)
7228
+ return "";
7229
+ if (text.length <= budget)
7230
+ return text;
7231
+ let t = text;
7232
+ let end = budget;
7233
+ for (let pass = 0;pass <= MAX_OPENER_REPAIRS; pass++) {
7234
+ const safe = safeMarkdownCut(t, end);
7235
+ if (safe * 2 >= end)
7236
+ return t.slice(0, safe);
7237
+ const repaired = dropLeadingOpener(t.slice(safe));
7238
+ if (repaired == null)
7239
+ return t.slice(0, safe);
7240
+ end -= t.length - safe - repaired.length;
7241
+ t = t.slice(0, safe) + repaired;
7242
+ }
7243
+ return t.slice(0, Math.max(0, safeMarkdownCut(t, end)));
7244
+ }
7245
+ function dropLeadingOpener(rest) {
7246
+ const fence = FENCE_OPENER_AT_HEAD.exec(rest);
7247
+ if (fence != null)
7248
+ return fence[1] + rest.slice(fence[0].length);
7249
+ const inline = INLINE_OPENER_AT_HEAD.exec(rest);
7250
+ if (inline != null)
7251
+ return rest.slice(inline[0].length);
7252
+ const row = TABLE_ROW_OPENER_AT_HEAD.exec(rest);
7253
+ if (row != null)
7254
+ return row[1] + rest.slice(row[0].length);
7255
+ return null;
7256
+ }
7189
7257
  function backOffOpenInline(text, cut) {
7190
7258
  if (cut <= 0 || cut >= text.length)
7191
7259
  return cut;
@@ -7206,7 +7274,7 @@ function backOffOpenInline(text, cut) {
7206
7274
  }
7207
7275
  return earliest;
7208
7276
  }
7209
- var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", PSEUDO_HEADING_MAX_CHARS = 48, INLINE_SPAN_PATTERNS;
7277
+ var RICH_MESSAGE_MAX_CHARS = 32768, PLAIN_TEXT_MAX_CHARS = 4096, PARAGRAPH_SPACER = "\u00a0", PSEUDO_HEADING_MAX_CHARS = 64, INLINE_SPAN_PATTERNS, DELIMITER_RUN_CHARS, FENCE_OPENER_AT_HEAD, INLINE_OPENER_AT_HEAD, TABLE_ROW_OPENER_AT_HEAD, MAX_OPENER_REPAIRS = 8;
7210
7278
  var init_format = __esm(() => {
7211
7279
  INLINE_SPAN_PATTERNS = [
7212
7280
  /`[^`\n]+`/g,
@@ -7215,8 +7283,14 @@ var init_format = __esm(() => {
7215
7283
  /\*\*[^*\n]+\*\*/g,
7216
7284
  /__[^_\n]+__/g,
7217
7285
  /(?<![\w*])_[^_\n]+_(?![\w*])/g,
7218
- /\[[^\]\n]*\]\([^)\n]*\)/g
7286
+ /\[[^\]\n]*\]\([^)\n]*\)/g,
7287
+ /~~[^~\n]+~~/g,
7288
+ /\|\|[^|\n]+\|\|/g
7219
7289
  ];
7290
+ DELIMITER_RUN_CHARS = new Set(["*", "_", "`", "~", "|"]);
7291
+ FENCE_OPENER_AT_HEAD = /^(\n?)(?:`{3,}|~{3,})[^\n]*/;
7292
+ INLINE_OPENER_AT_HEAD = /^(?:\*{1,3}|_{1,3}|`+|~~|\|\||\[)/;
7293
+ TABLE_ROW_OPENER_AT_HEAD = /^(\n?[ \t]*)\|/;
7220
7294
  });
7221
7295
 
7222
7296
  // ../src/auth/quota.ts
@@ -13686,276 +13760,6 @@ var require_mod4 = __commonJS((exports) => {
13686
13760
  __exportStar(require_worker(), exports);
13687
13761
  });
13688
13762
 
13689
- // flood-429-ledger.ts
13690
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync, unlinkSync as unlinkSync2 } from "node:fs";
13691
- import { dirname as dirname4, join as join4 } from "node:path";
13692
- function flood429LedgerPathFromFloodState(floodStatePath) {
13693
- return join4(dirname4(floodStatePath), FLOOD_429_LEDGER_FILE);
13694
- }
13695
- function belongsToEpisode(ep, obs) {
13696
- const impliedUntil = obs.ts + Math.max(0, obs.retryAfterSec) * 1000;
13697
- if (Math.abs(impliedUntil - ep.untilTs) <= EPISODE_MERGE_MS)
13698
- return true;
13699
- return obs.ts >= ep.firstTs && obs.ts <= ep.untilTs;
13700
- }
13701
- function foldFlood429Observation(episodes, obs, now = obs.ts) {
13702
- const retryAfterSec = Math.max(0, Math.floor(obs.retryAfterSec));
13703
- const impliedUntil = obs.ts + retryAfterSec * 1000;
13704
- const next = episodes.slice();
13705
- const last = next[next.length - 1];
13706
- if (last && belongsToEpisode(last, obs)) {
13707
- next[next.length - 1] = {
13708
- firstTs: Math.min(last.firstTs, obs.ts),
13709
- lastTs: Math.max(last.lastTs, obs.ts),
13710
- peakRetryAfterSec: Math.max(last.peakRetryAfterSec, retryAfterSec),
13711
- untilTs: Math.max(last.untilTs, impliedUntil),
13712
- count: last.count + 1
13713
- };
13714
- } else {
13715
- next.push({
13716
- firstTs: obs.ts,
13717
- lastTs: obs.ts,
13718
- peakRetryAfterSec: retryAfterSec,
13719
- untilTs: impliedUntil,
13720
- count: 1
13721
- });
13722
- }
13723
- return pruneFlood429Episodes(next, now);
13724
- }
13725
- function pruneFlood429Episodes(episodes, now) {
13726
- const floor = now - FLOOD_429_LEDGER_RETENTION_MS;
13727
- const kept = episodes.filter((e) => e.lastTs >= floor);
13728
- return kept.length > FLOOD_429_LEDGER_MAX_EPISODES ? kept.slice(kept.length - FLOOD_429_LEDGER_MAX_EPISODES) : kept;
13729
- }
13730
- function parseEpisode(raw) {
13731
- if (typeof raw !== "object" || raw === null)
13732
- return null;
13733
- const r = raw;
13734
- const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
13735
- const firstTs = num(r.firstTs);
13736
- const untilTs = num(r.untilTs);
13737
- const peak = num(r.peakRetryAfterSec);
13738
- if (firstTs === null || untilTs === null || peak === null)
13739
- return null;
13740
- return {
13741
- firstTs,
13742
- lastTs: num(r.lastTs) ?? firstTs,
13743
- peakRetryAfterSec: peak,
13744
- untilTs,
13745
- count: num(r.count) ?? 1
13746
- };
13747
- }
13748
- function readFlood429LedgerResult(path, readFile = (p) => readFileSync3(p, "utf-8")) {
13749
- let text;
13750
- try {
13751
- text = readFile(path);
13752
- } catch (err) {
13753
- const code = err?.code;
13754
- if (code === "ENOENT" || code === "ENOTDIR")
13755
- return { status: "absent", episodes: [] };
13756
- return {
13757
- status: "unreadable",
13758
- episodes: [],
13759
- error: `${code ?? "EUNKNOWN"}: ${err?.message ?? String(err)}`
13760
- };
13761
- }
13762
- let parsed;
13763
- try {
13764
- parsed = JSON.parse(text);
13765
- } catch (err) {
13766
- return { status: "corrupt", episodes: [], error: err?.message ?? String(err) };
13767
- }
13768
- if (!Array.isArray(parsed)) {
13769
- return { status: "corrupt", episodes: [], error: "ledger is not a JSON array" };
13770
- }
13771
- const out = [];
13772
- let dropped = 0;
13773
- for (const raw of parsed) {
13774
- const ep = parseEpisode(raw);
13775
- if (ep)
13776
- out.push(ep);
13777
- else
13778
- dropped += 1;
13779
- }
13780
- if (dropped > 0 && out.length === 0) {
13781
- return { status: "corrupt", episodes: [], error: `${dropped} unparseable entr(ies)` };
13782
- }
13783
- return { status: "ok", episodes: out };
13784
- }
13785
- function readFlood429Ledger(path, readFile = (p) => readFileSync3(p, "utf-8")) {
13786
- return readFlood429LedgerResult(path, readFile).episodes;
13787
- }
13788
- function writeFlood429Ledger(path, episodes, log = (l) => process.stderr.write(l)) {
13789
- const payload = JSON.stringify(episodes);
13790
- try {
13791
- mkdirSync5(dirname4(path), { recursive: true });
13792
- } catch {}
13793
- try {
13794
- writeFileSync4(path, payload, { mode: FLOOD_429_LEDGER_MODE });
13795
- try {
13796
- chmodSync(path, FLOOD_429_LEDGER_MODE);
13797
- } catch {}
13798
- return;
13799
- } catch (err) {
13800
- const code = err?.code;
13801
- if (code !== "EACCES" && code !== "EPERM") {
13802
- log(`telegram gateway: 429-ledger: could not persist ${path} (${code ?? "error"})
13803
- `);
13804
- return;
13805
- }
13806
- try {
13807
- unlinkSync2(path);
13808
- writeFileSync4(path, payload, { mode: FLOOD_429_LEDGER_MODE });
13809
- } catch (err2) {
13810
- log(`telegram gateway: 429-ledger: cannot persist the 429 pressure ledger to ${path} ` + `(${code}, recreate failed: ${err2?.message ?? String(err2)}). ` + `Flood-pressure history will be missing from \`switchroom doctor\`
13811
- `);
13812
- }
13813
- }
13814
- }
13815
- function recordFlood429(path, obs, log = (l) => process.stderr.write(l)) {
13816
- const folded = foldFlood429Observation(readFlood429Ledger(path), obs, obs.ts);
13817
- writeFlood429Ledger(path, folded, log);
13818
- }
13819
- var FLOOD_429_LEDGER_FILE = "429-ledger.json", FLOOD_429_LEDGER_MODE = 420, EPISODE_MERGE_MS = 60000, FLOOD_429_LEDGER_MAX_EPISODES = 400, FLOOD_429_LEDGER_RETENTION_MS, PENALTY_WINDOW_MS, TRIVIAL_PRESSURE_WINDOW_MS;
13820
- var init_flood_429_ledger = __esm(() => {
13821
- FLOOD_429_LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
13822
- PENALTY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
13823
- TRIVIAL_PRESSURE_WINDOW_MS = 24 * 60 * 60 * 1000;
13824
- });
13825
-
13826
- // flood-circuit-breaker.ts
13827
- import {
13828
- existsSync as existsSync2,
13829
- readFileSync as readFileSync4,
13830
- writeFileSync as writeFileSync5,
13831
- mkdirSync as mkdirSync6,
13832
- chmodSync as chmodSync2,
13833
- unlinkSync as unlinkSync3,
13834
- renameSync as renameSync6
13835
- } from "node:fs";
13836
- import { dirname as dirname5, join as join5 } from "node:path";
13837
- function floodStatePath(stateDir) {
13838
- return join5(stateDir, FLOOD_STATE_FILE);
13839
- }
13840
- function computeFloodWait(prior, retryAfterSec, now) {
13841
- const candidate = now + Math.max(0, retryAfterSec) * 1000;
13842
- const untilTs = prior && prior.untilTs > candidate ? prior.untilTs : candidate;
13843
- return { untilTs, retryAfterSec, recordedTs: now };
13844
- }
13845
- function floodWaitRemainingMs(state, now) {
13846
- if (!state)
13847
- return 0;
13848
- return Math.max(0, state.untilTs - now);
13849
- }
13850
- function readFloodStateResult(path) {
13851
- let text;
13852
- try {
13853
- text = readFileSync4(path, "utf-8");
13854
- } catch (err) {
13855
- const code = err?.code;
13856
- if (code === "ENOENT" || code === "ENOTDIR")
13857
- return { status: "absent", state: null };
13858
- return {
13859
- status: "unreadable",
13860
- state: null,
13861
- error: `${code ?? "EUNKNOWN"}: ${err?.message ?? String(err)}`
13862
- };
13863
- }
13864
- try {
13865
- const raw = JSON.parse(text);
13866
- if (typeof raw.untilTs !== "number" || !Number.isFinite(raw.untilTs)) {
13867
- return { status: "corrupt", state: null, error: "untilTs is not a finite number" };
13868
- }
13869
- return {
13870
- status: "ok",
13871
- state: {
13872
- untilTs: raw.untilTs,
13873
- retryAfterSec: typeof raw.retryAfterSec === "number" ? raw.retryAfterSec : 0,
13874
- recordedTs: typeof raw.recordedTs === "number" ? raw.recordedTs : 0
13875
- }
13876
- };
13877
- } catch (err) {
13878
- return { status: "corrupt", state: null, error: err?.message ?? String(err) };
13879
- }
13880
- }
13881
- function readFloodState(path) {
13882
- return readFloodStateResult(path).state;
13883
- }
13884
- function writeFloodState(path, state, log = (l) => process.stderr.write(l)) {
13885
- const payload = JSON.stringify(state);
13886
- try {
13887
- mkdirSync6(dirname5(path), { recursive: true });
13888
- } catch {}
13889
- try {
13890
- writeFileSync5(path, payload, { mode: FLOOD_STATE_MODE });
13891
- try {
13892
- chmodSync2(path, FLOOD_STATE_MODE);
13893
- } catch {}
13894
- return;
13895
- } catch (err) {
13896
- const code = err?.code;
13897
- if (code !== "EACCES" && code !== "EPERM") {
13898
- log(`telegram gateway: flood-breaker: could not persist ${path} (${code ?? "error"})
13899
- `);
13900
- return;
13901
- }
13902
- try {
13903
- unlinkSync3(path);
13904
- writeFileSync5(path, payload, { mode: FLOOD_STATE_MODE });
13905
- log(`telegram gateway: flood-breaker: recreated ${path} \u2014 it was owned by another uid ` + `and could not be updated (${code}); the breaker was blind (issue #3106)
13906
- `);
13907
- } catch (err2) {
13908
- log(`telegram gateway: flood-breaker: BLIND \u2014 cannot persist the flood-wait window to ${path} ` + `(${code}, and recreate failed: ${err2?.message ?? String(err2)}). ` + `Flood bans will NOT be recorded. Fix the file's ownership/mode (issue #3106)
13909
- `);
13910
- }
13911
- }
13912
- }
13913
- function makeFloodWaitRecorder(path, now = Date.now, log = (l) => process.stderr.write(l)) {
13914
- return (retryAfterSec) => {
13915
- const t = now();
13916
- const next = computeFloodWait(readFloodState(path), retryAfterSec, t);
13917
- writeFloodState(path, next, log);
13918
- try {
13919
- recordFlood429(flood429LedgerPathFromFloodState(path), { ts: t, retryAfterSec }, log);
13920
- } catch {}
13921
- };
13922
- }
13923
- function warnBlind(path, error, now, log) {
13924
- const last = blindLoggedAt.get(path);
13925
- if (last !== undefined && now - last < BLIND_LOG_INTERVAL_MS)
13926
- return;
13927
- blindLoggedAt.set(path, now);
13928
- log(`telegram gateway: flood-breaker: BLIND \u2014 cannot read the flood-wait marker ${path} ` + `(${error ?? "unknown error"}). The breaker cannot tell whether a Telegram flood ban is ` + `open. Essential sends PROCEED (fail-open); non-essential sends (boot card, typing) are ` + `SUPPRESSED. Fix the file's ownership/mode (issue #3106)
13929
- `);
13930
- }
13931
- function makeFloodWaitProbe(path, now = Date.now, log = (l) => process.stderr.write(l)) {
13932
- return () => {
13933
- const t = now();
13934
- const res = readFloodStateResult(path);
13935
- if (res.status === "unreadable") {
13936
- warnBlind(path, res.error, t, log);
13937
- return 0;
13938
- }
13939
- return floodWaitRemainingMs(res.state, t);
13940
- };
13941
- }
13942
- function nonEssentialSendSuppression(path, now) {
13943
- const res = readFloodStateResult(path);
13944
- if (res.status === "unreadable") {
13945
- return { suppress: true, reason: "blind", error: res.error ?? "unknown error" };
13946
- }
13947
- const remainingMs = floodWaitRemainingMs(res.state, now);
13948
- if (remainingMs > 0)
13949
- return { suppress: true, reason: "flood_wait", remainingMs };
13950
- return { suppress: false };
13951
- }
13952
- var FLOOD_STATE_FILE = "flood-wait.json", FLOOD_STATE_MODE = 420, BLIND_LOG_INTERVAL_MS = 60000, blindLoggedAt, FLOOD_WINDOWS_CORRUPT_SUPPRESS_MS;
13953
- var init_flood_circuit_breaker = __esm(() => {
13954
- init_flood_429_ledger();
13955
- blindLoggedAt = new Map;
13956
- FLOOD_WINDOWS_CORRUPT_SUPPRESS_MS = 5 * 60000;
13957
- });
13958
-
13959
13763
  // ../src/vault/broker/peercred-ffi.ts
13960
13764
  function getPeerCred(fd) {
13961
13765
  if (process.platform !== "linux")
@@ -14354,11 +14158,11 @@ var init_protocol2 = __esm(() => {
14354
14158
  OkRevokeGrantResponseSchema,
14355
14159
  OkApprovalRequestResponseSchema,
14356
14160
  OkApprovalLookupResponseSchema,
14161
+ OkApprovalConsumeRecordResponseSchema,
14357
14162
  OkApprovalConsumeResponseSchema,
14358
14163
  OkApprovalRevokeResponseSchema,
14359
14164
  OkApprovalListResponseSchema,
14360
14165
  OkApprovalRecordResponseSchema,
14361
- OkApprovalConsumeRecordResponseSchema,
14362
14166
  ErrorResponseSchema2
14363
14167
  ]);
14364
14168
  });
@@ -14372,7 +14176,7 @@ function isDockerRuntime() {
14372
14176
  import * as net2 from "node:net";
14373
14177
  import * as fs from "node:fs";
14374
14178
  import { homedir as homedir3 } from "node:os";
14375
- import { join as join6 } from "node:path";
14179
+ import { join as join4 } from "node:path";
14376
14180
  function defaultBrokerSocketPath() {
14377
14181
  if (fs.existsSync(OPERATOR_SOCKET_PATH))
14378
14182
  return OPERATOR_SOCKET_PATH;
@@ -14381,8 +14185,8 @@ function defaultBrokerSocketPath() {
14381
14185
  return LEGACY_SOCKET_PATH;
14382
14186
  }
14383
14187
  function vaultTokenFilePath(agentSlug) {
14384
- const base = process.env.SWITCHROOM_AGENTS_DIR || join6(homedir3(), ".switchroom", "agents");
14385
- return join6(base, agentSlug, ".vault-token");
14188
+ const base = process.env.SWITCHROOM_AGENTS_DIR || join4(homedir3(), ".switchroom", "agents");
14189
+ return join4(base, agentSlug, ".vault-token");
14386
14190
  }
14387
14191
  function readVaultTokenFile(agentSlug) {
14388
14192
  const filePath = vaultTokenFilePath(agentSlug);
@@ -14605,8 +14409,8 @@ var DEFAULT_TIMEOUT_MS2 = 2000, LEGACY_SOCKET_PATH, OPERATOR_SOCKET_PATH;
14605
14409
  var init_client2 = __esm(() => {
14606
14410
  init_protocol2();
14607
14411
  init_peercred();
14608
- LEGACY_SOCKET_PATH = join6(homedir3(), ".switchroom", "vault-broker.sock");
14609
- OPERATOR_SOCKET_PATH = join6(homedir3(), ".switchroom", "broker-operator", "sock");
14412
+ LEGACY_SOCKET_PATH = join4(homedir3(), ".switchroom", "vault-broker.sock");
14413
+ OPERATOR_SOCKET_PATH = join4(homedir3(), ".switchroom", "broker-operator", "sock");
14610
14414
  });
14611
14415
 
14612
14416
  // ../src/agents/tmux.ts
@@ -14617,7 +14421,7 @@ __export(exports_tmux, {
14617
14421
  captureAgentPane: () => captureAgentPane
14618
14422
  });
14619
14423
  import { execFileSync as execFileSync2 } from "node:child_process";
14620
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync7, readdirSync as readdirSync2, statSync as statSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "node:fs";
14424
+ import { chmodSync, mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
14621
14425
  import { resolve } from "node:path";
14622
14426
  function captureAgentPane(opts) {
14623
14427
  const { agentName, agentDir, reason } = opts;
@@ -14629,14 +14433,14 @@ function captureAgentPane(opts) {
14629
14433
  const reasonSlug = sanitizeReason(reason);
14630
14434
  const outPath = resolve(outDir, `${ts}-${reasonSlug}.txt`);
14631
14435
  try {
14632
- mkdirSync7(outDir, { recursive: true, mode: 448 });
14436
+ mkdirSync5(outDir, { recursive: true, mode: 448 });
14633
14437
  } catch (err) {
14634
14438
  const msg = `mkdir crash-reports failed: ${err.message}`;
14635
14439
  console.error(`[tmux-capture] ${agentName}: ${msg}`);
14636
14440
  return { error: msg };
14637
14441
  }
14638
14442
  try {
14639
- chmodSync3(outDir, 448);
14443
+ chmodSync(outDir, 448);
14640
14444
  } catch (err) {
14641
14445
  console.error(`[tmux-capture] ${agentName}: chmod crash-reports 0700 failed: ${err.message}`);
14642
14446
  }
@@ -14668,7 +14472,7 @@ function captureAgentPane(opts) {
14668
14472
  ` + `
14669
14473
  `;
14670
14474
  try {
14671
- writeFileSync6(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
14475
+ writeFileSync4(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
14672
14476
  mode: 384
14673
14477
  });
14674
14478
  } catch (err) {
@@ -14752,7 +14556,7 @@ function pruneOldReports(dir, retain) {
14752
14556
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
14753
14557
  for (const stale of files.slice(retain)) {
14754
14558
  try {
14755
- unlinkSync4(stale.full);
14559
+ unlinkSync2(stale.full);
14756
14560
  } catch {}
14757
14561
  }
14758
14562
  }
@@ -21865,6 +21669,7 @@ var init_schema = __esm(() => {
21865
21669
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
21866
21670
  file: exports_external.boolean().default(true).describe("Maintain a curated workspace MEMORY.md file (seeded once, " + "auto-loaded every turn). Set false for hindsight-only memory: " + "the file is not seeded or re-created, so once migrated into " + "Hindsight and deleted it stays gone. Recall + directives carry " + "the memory instead. Cascade: override (per-agent wins over default)."),
21867
21671
  isolation: exports_external.enum(["default", "strict"]).default("default").describe("strict = never shared cross-agent, default = eligible for reflect"),
21672
+ profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off \u2014 " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile \u2192 extends \u2192 DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset \u21d2 byte-identical to keying off `extends`."),
21868
21673
  bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
21869
21674
  reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
21870
21675
  retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
@@ -22158,11 +21963,13 @@ var init_schema = __esm(() => {
22158
21963
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
22159
21964
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
22160
21965
  context_window: exports_external.number().int().positive().optional().describe("GLOBAL context window (tokens) of the backend serving hindsight's " + "LLM ops \u2014 the declared size switchroom derives every token budget " + "from. Set this to the real window of whatever you point " + "`hindsight.llm` at (e.g. 32768 for a llama.cpp slot launched with " + "`-c 65536 -np 2`, 131072 for a large-window OpenRouter model). " + "Absent \u2192 a per-provider default: 200000 for `claude-code`, a " + "conservative 32768 for everything else. Overflowing a local " + "backend's window does NOT error \u2014 llama.cpp context-shift silently " + "drops the system prompt and the model answers conversationally " + "with HTTP 200 \u2014 so this value is what makes the failure " + "detectable at setup time instead of never."),
21966
+ base_url: exports_external.string().min(1).optional().describe("GLOBAL LLM base URL (upstream `HINDSIGHT_API_LLM_BASE_URL`). The " + "default endpoint every op inherits absent a per-op " + "`hindsight.llm.<op>.base_url`. Optional passthrough; unset \u2192 the " + "engine's provider default. When this points at host loopback the " + "hindsight container is forced onto host networking, same as a " + "per-op base URL, so the endpoint stays reachable (#3687)."),
21967
+ api_key: exports_external.string().min(1).optional().describe("GLOBAL LLM API key (upstream `HINDSIGHT_API_LLM_API_KEY`). Literal " + "or `vault:` reference. The default credential every op inherits " + "absent a per-op `hindsight.llm.<op>.api_key`; the engine reads it as " + "a plain env fallback. Optional passthrough (#3687)."),
22161
21968
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent \u2192 uses the global model/provider."),
22162
21969
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
22163
21970
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
22164
21971
  }).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."),
22165
- 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, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_MAX_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool), 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).")
21972
+ 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, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1), 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).")
22166
21973
  });
22167
21974
  MicrosoftWorkspaceConfigSchema = exports_external.object({
22168
21975
  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)."),
@@ -22294,6 +22101,7 @@ var init_schema = __esm(() => {
22294
22101
  auto_recall: exports_external.boolean().optional(),
22295
22102
  file: exports_external.boolean().optional(),
22296
22103
  isolation: exports_external.enum(["default", "strict"]).optional(),
22104
+ profile: exports_external.string().optional(),
22297
22105
  directive_capture_nudge: exports_external.boolean().optional(),
22298
22106
  observation_scopes: ObservationScopesSchema,
22299
22107
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -22335,7 +22143,23 @@ var init_schema = __esm(() => {
22335
22143
  max_query_chars: exports_external.number().int().min(1).optional(),
22336
22144
  parallel: exports_external.boolean().optional(),
22337
22145
  additional_banks: exports_external.array(exports_external.string()).optional(),
22338
- sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
22146
+ sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional(),
22147
+ types: exports_external.array(exports_external.string()).optional(),
22148
+ skip_trivial: exports_external.boolean().optional(),
22149
+ topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional()
22150
+ }).optional(),
22151
+ retain: exports_external.object({
22152
+ every_n_turns: exports_external.number().int().min(1).optional(),
22153
+ overlap_turns: exports_external.number().int().min(0).optional()
22154
+ }).optional(),
22155
+ bank_mission: exports_external.string().optional(),
22156
+ reflect_mission: exports_external.string().optional(),
22157
+ retain_mission: exports_external.string().optional(),
22158
+ observations_mission: exports_external.string().optional(),
22159
+ disposition: exports_external.object({
22160
+ skepticism: exports_external.number().int().min(1).max(5).optional(),
22161
+ literalism: exports_external.number().int().min(1).max(5).optional(),
22162
+ empathy: exports_external.number().int().min(1).max(5).optional()
22339
22163
  }).optional()
22340
22164
  }).optional(),
22341
22165
  schedule: exports_external.array(ScheduleEntrySchema).optional(),
@@ -22678,7 +22502,7 @@ var init_schema = __esm(() => {
22678
22502
  });
22679
22503
 
22680
22504
  // ../src/config/paths.ts
22681
- import { existsSync as existsSync4 } from "node:fs";
22505
+ import { existsSync as existsSync3 } from "node:fs";
22682
22506
  import { resolve as resolve3 } from "node:path";
22683
22507
  function home() {
22684
22508
  return process.env.HOME ?? "/root";
@@ -22694,7 +22518,7 @@ function resolveStatePath(fragment) {
22694
22518
  const h = home();
22695
22519
  const primary = resolve3(h, DEFAULT_STATE_DIR, fragment);
22696
22520
  const legacy = resolve3(h, LEGACY_STATE_DIR, fragment);
22697
- if (!existsSync4(primary) && existsSync4(legacy)) {
22521
+ if (!existsSync3(primary) && existsSync3(legacy)) {
22698
22522
  warnLegacyStateOnce(legacy);
22699
22523
  return legacy;
22700
22524
  }
@@ -22707,9 +22531,9 @@ function resolveDualPath(pathStr) {
22707
22531
  const absolute = resolve3(h, rest);
22708
22532
  if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
22709
22533
  const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
22710
- if (!existsSync4(absolute)) {
22534
+ if (!existsSync3(absolute)) {
22711
22535
  const legacy = resolve3(h, LEGACY_STATE_DIR, frag);
22712
- if (existsSync4(legacy)) {
22536
+ if (existsSync3(legacy)) {
22713
22537
  warnLegacyStateOnce(legacy);
22714
22538
  return legacy;
22715
22539
  }
@@ -22734,7 +22558,7 @@ var init_overlay_schema = __esm(() => {
22734
22558
  });
22735
22559
 
22736
22560
  // ../src/config/overlay-loader.ts
22737
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
22561
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
22738
22562
  import { basename as basename4, resolve as resolve4 } from "node:path";
22739
22563
  function deriveOverlayTitle(raw, fileName) {
22740
22564
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
@@ -22750,7 +22574,7 @@ function overlayDirFor(agentName, subdir) {
22750
22574
  return resolve4(base);
22751
22575
  }
22752
22576
  function listYamlFiles(dir) {
22753
- if (!existsSync5(dir))
22577
+ if (!existsSync4(dir))
22754
22578
  return [];
22755
22579
  let entries;
22756
22580
  try {
@@ -22798,7 +22622,7 @@ function applyAgentOverlays(config) {
22798
22622
  const merged = [...agentCfg.schedule ?? []];
22799
22623
  for (const file of files) {
22800
22624
  try {
22801
- const raw = readFileSync6(file, "utf-8");
22625
+ const raw = readFileSync4(file, "utf-8");
22802
22626
  const parsed = import_yaml.parse(raw);
22803
22627
  const doc = OverlayDocSchema.parse(parsed);
22804
22628
  const title = deriveOverlayTitle(raw, basename4(file));
@@ -22839,7 +22663,7 @@ function applyAgentOverlays(config) {
22839
22663
  const seen = new Set(merged);
22840
22664
  for (const file of skillFiles) {
22841
22665
  try {
22842
- const raw = readFileSync6(file, "utf-8");
22666
+ const raw = readFileSync4(file, "utf-8");
22843
22667
  const parsed = import_yaml.parse(raw);
22844
22668
  const doc = OverlayDocSchema.parse(parsed);
22845
22669
  for (const skillName of doc.skills ?? []) {
@@ -23309,7 +23133,7 @@ var init_timezone = __esm(() => {
23309
23133
  });
23310
23134
 
23311
23135
  // ../src/config/loader.ts
23312
- import { readFileSync as readFileSync7, existsSync as existsSync6 } from "node:fs";
23136
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
23313
23137
  import { homedir as homedir4 } from "node:os";
23314
23138
  import { resolve as resolve5 } from "node:path";
23315
23139
  function formatZodErrors(error) {
@@ -23376,7 +23200,7 @@ function findConfigFile(startDir) {
23376
23200
  resolve5(userDir, "clerk.yml")
23377
23201
  ].filter(Boolean);
23378
23202
  for (const path of searchPaths) {
23379
- if (existsSync6(path)) {
23203
+ if (existsSync5(path)) {
23380
23204
  return path;
23381
23205
  }
23382
23206
  }
@@ -23384,12 +23208,12 @@ function findConfigFile(startDir) {
23384
23208
  }
23385
23209
  function loadConfig(configPath) {
23386
23210
  const filePath = configPath ?? findConfigFile();
23387
- if (!existsSync6(filePath)) {
23211
+ if (!existsSync5(filePath)) {
23388
23212
  throw new ConfigError(`Config file not found: ${filePath}`);
23389
23213
  }
23390
23214
  let raw;
23391
23215
  try {
23392
- raw = readFileSync7(filePath, "utf-8");
23216
+ raw = readFileSync5(filePath, "utf-8");
23393
23217
  } catch (err) {
23394
23218
  throw new ConfigError(`Failed to read config file: ${filePath}`, [
23395
23219
  ` ${err.message}`
@@ -23503,12 +23327,12 @@ var init_flock = () => {};
23503
23327
  // ../src/vault/vault.ts
23504
23328
  import { randomBytes as randomBytes4, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
23505
23329
  import {
23506
- readFileSync as readFileSync8,
23330
+ readFileSync as readFileSync6,
23507
23331
  writeSync as writeSync2,
23508
- existsSync as existsSync7,
23509
- renameSync as renameSync7,
23510
- mkdirSync as mkdirSync9,
23511
- unlinkSync as unlinkSync5,
23332
+ existsSync as existsSync6,
23333
+ renameSync as renameSync6,
23334
+ mkdirSync as mkdirSync7,
23335
+ unlinkSync as unlinkSync3,
23512
23336
  fsyncSync as fsyncSync2,
23513
23337
  openSync as openSync2,
23514
23338
  closeSync as closeSync2,
@@ -23546,12 +23370,12 @@ function normalizeSecrets(raw) {
23546
23370
  return out;
23547
23371
  }
23548
23372
  function openVault(passphrase, vaultPath) {
23549
- if (!existsSync7(vaultPath)) {
23373
+ if (!existsSync6(vaultPath)) {
23550
23374
  throw new VaultError(`Vault file not found: ${vaultPath}`);
23551
23375
  }
23552
23376
  let vaultFile;
23553
23377
  try {
23554
- vaultFile = JSON.parse(readFileSync8(vaultPath, "utf8"));
23378
+ vaultFile = JSON.parse(readFileSync6(vaultPath, "utf8"));
23555
23379
  } catch {
23556
23380
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
23557
23381
  }
@@ -23596,16 +23420,16 @@ var init_vault = __esm(() => {
23596
23420
 
23597
23421
  // ../src/vault/resolver.ts
23598
23422
  import {
23599
- chmodSync as chmodSync4,
23423
+ chmodSync as chmodSync2,
23600
23424
  closeSync as closeSync3,
23601
- mkdirSync as mkdirSync10,
23425
+ mkdirSync as mkdirSync8,
23602
23426
  mkdtempSync,
23603
23427
  openSync as openSync3,
23604
23428
  rmSync as rmSync2,
23605
23429
  statSync as statSync6,
23606
23430
  writeSync as writeSync3
23607
23431
  } from "node:fs";
23608
- import { join as join8 } from "node:path";
23432
+ import { join as join6 } from "node:path";
23609
23433
  import { tmpdir } from "node:os";
23610
23434
  import { constants as fsConstants } from "node:fs";
23611
23435
  function isVaultReference(value) {
@@ -23657,13 +23481,13 @@ function materializationRoot() {
23657
23481
  return cachedRoot;
23658
23482
  const xdg = process.env.XDG_RUNTIME_DIR;
23659
23483
  if (xdg) {
23660
- const base = join8(xdg, "switchroom", "vault");
23661
- mkdirSync10(base, { recursive: true, mode: 448 });
23662
- cachedRoot = mkdtempSync(join8(base, "run-"));
23484
+ const base = join6(xdg, "switchroom", "vault");
23485
+ mkdirSync8(base, { recursive: true, mode: 448 });
23486
+ cachedRoot = mkdtempSync(join6(base, "run-"));
23663
23487
  } else {
23664
- cachedRoot = mkdtempSync(join8(tmpdir(), "switchroom-vault-"));
23488
+ cachedRoot = mkdtempSync(join6(tmpdir(), "switchroom-vault-"));
23665
23489
  }
23666
- chmodSync4(cachedRoot, 448);
23490
+ chmodSync2(cachedRoot, 448);
23667
23491
  return cachedRoot;
23668
23492
  }
23669
23493
  function writeFileExclusive(filePath, content) {
@@ -23676,14 +23500,14 @@ function writeFileExclusive(filePath, content) {
23676
23500
  }
23677
23501
  }
23678
23502
  function materializeFilesEntry(key, files) {
23679
- const dir = join8(materializationRoot(), key);
23503
+ const dir = join6(materializationRoot(), key);
23680
23504
  if (materializedDirs.has(dir)) {
23681
23505
  try {
23682
23506
  rmSync2(dir, { recursive: true, force: true });
23683
23507
  } catch {}
23684
23508
  }
23685
- mkdirSync10(dir, { recursive: true, mode: 448 });
23686
- chmodSync4(dir, 448);
23509
+ mkdirSync8(dir, { recursive: true, mode: 448 });
23510
+ chmodSync2(dir, 448);
23687
23511
  const st = statSync6(dir);
23688
23512
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
23689
23513
  throw new Error(`Refusing to materialize vault entry: ${dir} not owned by caller`);
@@ -23692,7 +23516,7 @@ function materializeFilesEntry(key, files) {
23692
23516
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
23693
23517
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
23694
23518
  }
23695
- const filePath = join8(dir, filename);
23519
+ const filePath = join6(dir, filename);
23696
23520
  const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
23697
23521
  writeFileExclusive(filePath, content);
23698
23522
  }
@@ -23819,9 +23643,16 @@ var init_resolver = __esm(() => {
23819
23643
  materializedDirs = new Set;
23820
23644
  });
23821
23645
 
23646
+ // status-no-truncate.ts
23647
+ var STATUS_ROLLING_LINES = 5, WORKER_HISTORY_MAX = 6, STATUS_LINE_MAX = 200, STATUS_CARD_CHAR_BUDGET, NESTED_PREFIX = "\u2800\u2800\u2800\u21b3 ", WORKER_STEP_INDENT = "\u2800\u2800\u2800";
23648
+ var init_status_no_truncate = __esm(() => {
23649
+ init_format();
23650
+ STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
23651
+ });
23652
+
23822
23653
  // ../src/util/atomic.ts
23823
23654
  import { randomBytes as randomBytes5 } from "node:crypto";
23824
- import { closeSync as closeSync4, constants as constants2, fchmodSync as fchmodSync2, fchownSync as fchownSync2, fsyncSync as fsyncSync3, openSync as openSync4, renameSync as renameSync8, rmSync as rmSync3, writeSync as writeSync4 } from "node:fs";
23655
+ import { closeSync as closeSync4, constants as constants2, fchmodSync as fchmodSync2, fchownSync as fchownSync2, fsyncSync as fsyncSync3, openSync as openSync4, renameSync as renameSync7, rmSync as rmSync3, writeSync as writeSync4 } from "node:fs";
23825
23656
  function atomicWriteFileSync2(destPath, contents, modeOrOpts = 384) {
23826
23657
  const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
23827
23658
  const mode = opts.mode ?? 384;
@@ -23845,7 +23676,7 @@ function atomicWriteFileSync2(destPath, contents, modeOrOpts = 384) {
23845
23676
  fsyncSync3(fd);
23846
23677
  closeSync4(fd);
23847
23678
  fd = null;
23848
- renameSync8(tmp, destPath);
23679
+ renameSync7(tmp, destPath);
23849
23680
  } catch (err) {
23850
23681
  if (fd !== null) {
23851
23682
  try {
@@ -23911,6 +23742,276 @@ var init_approval_card = __esm(() => {
23911
23742
  import_grammy5 = __toESM(require_mod2(), 1);
23912
23743
  });
23913
23744
 
23745
+ // flood-429-ledger.ts
23746
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync14, chmodSync as chmodSync4, unlinkSync as unlinkSync9 } from "node:fs";
23747
+ import { dirname as dirname7, join as join17 } from "node:path";
23748
+ function flood429LedgerPathFromFloodState(floodStatePath) {
23749
+ return join17(dirname7(floodStatePath), FLOOD_429_LEDGER_FILE);
23750
+ }
23751
+ function belongsToEpisode(ep, obs) {
23752
+ const impliedUntil = obs.ts + Math.max(0, obs.retryAfterSec) * 1000;
23753
+ if (Math.abs(impliedUntil - ep.untilTs) <= EPISODE_MERGE_MS)
23754
+ return true;
23755
+ return obs.ts >= ep.firstTs && obs.ts <= ep.untilTs;
23756
+ }
23757
+ function foldFlood429Observation(episodes, obs, now = obs.ts) {
23758
+ const retryAfterSec = Math.max(0, Math.floor(obs.retryAfterSec));
23759
+ const impliedUntil = obs.ts + retryAfterSec * 1000;
23760
+ const next = episodes.slice();
23761
+ const last = next[next.length - 1];
23762
+ if (last && belongsToEpisode(last, obs)) {
23763
+ next[next.length - 1] = {
23764
+ firstTs: Math.min(last.firstTs, obs.ts),
23765
+ lastTs: Math.max(last.lastTs, obs.ts),
23766
+ peakRetryAfterSec: Math.max(last.peakRetryAfterSec, retryAfterSec),
23767
+ untilTs: Math.max(last.untilTs, impliedUntil),
23768
+ count: last.count + 1
23769
+ };
23770
+ } else {
23771
+ next.push({
23772
+ firstTs: obs.ts,
23773
+ lastTs: obs.ts,
23774
+ peakRetryAfterSec: retryAfterSec,
23775
+ untilTs: impliedUntil,
23776
+ count: 1
23777
+ });
23778
+ }
23779
+ return pruneFlood429Episodes(next, now);
23780
+ }
23781
+ function pruneFlood429Episodes(episodes, now) {
23782
+ const floor = now - FLOOD_429_LEDGER_RETENTION_MS;
23783
+ const kept = episodes.filter((e) => e.lastTs >= floor);
23784
+ return kept.length > FLOOD_429_LEDGER_MAX_EPISODES ? kept.slice(kept.length - FLOOD_429_LEDGER_MAX_EPISODES) : kept;
23785
+ }
23786
+ function parseEpisode(raw) {
23787
+ if (typeof raw !== "object" || raw === null)
23788
+ return null;
23789
+ const r = raw;
23790
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
23791
+ const firstTs = num(r.firstTs);
23792
+ const untilTs = num(r.untilTs);
23793
+ const peak = num(r.peakRetryAfterSec);
23794
+ if (firstTs === null || untilTs === null || peak === null)
23795
+ return null;
23796
+ return {
23797
+ firstTs,
23798
+ lastTs: num(r.lastTs) ?? firstTs,
23799
+ peakRetryAfterSec: peak,
23800
+ untilTs,
23801
+ count: num(r.count) ?? 1
23802
+ };
23803
+ }
23804
+ function readFlood429LedgerResult(path2, readFile = (p) => readFileSync13(p, "utf-8")) {
23805
+ let text4;
23806
+ try {
23807
+ text4 = readFile(path2);
23808
+ } catch (err) {
23809
+ const code2 = err?.code;
23810
+ if (code2 === "ENOENT" || code2 === "ENOTDIR")
23811
+ return { status: "absent", episodes: [] };
23812
+ return {
23813
+ status: "unreadable",
23814
+ episodes: [],
23815
+ error: `${code2 ?? "EUNKNOWN"}: ${err?.message ?? String(err)}`
23816
+ };
23817
+ }
23818
+ let parsed;
23819
+ try {
23820
+ parsed = JSON.parse(text4);
23821
+ } catch (err) {
23822
+ return { status: "corrupt", episodes: [], error: err?.message ?? String(err) };
23823
+ }
23824
+ if (!Array.isArray(parsed)) {
23825
+ return { status: "corrupt", episodes: [], error: "ledger is not a JSON array" };
23826
+ }
23827
+ const out = [];
23828
+ let dropped = 0;
23829
+ for (const raw of parsed) {
23830
+ const ep = parseEpisode(raw);
23831
+ if (ep)
23832
+ out.push(ep);
23833
+ else
23834
+ dropped += 1;
23835
+ }
23836
+ if (dropped > 0 && out.length === 0) {
23837
+ return { status: "corrupt", episodes: [], error: `${dropped} unparseable entr(ies)` };
23838
+ }
23839
+ return { status: "ok", episodes: out };
23840
+ }
23841
+ function readFlood429Ledger(path2, readFile = (p) => readFileSync13(p, "utf-8")) {
23842
+ return readFlood429LedgerResult(path2, readFile).episodes;
23843
+ }
23844
+ function writeFlood429Ledger(path2, episodes, log = (l) => process.stderr.write(l)) {
23845
+ const payload = JSON.stringify(episodes);
23846
+ try {
23847
+ mkdirSync14(dirname7(path2), { recursive: true });
23848
+ } catch {}
23849
+ try {
23850
+ writeFileSync11(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
23851
+ try {
23852
+ chmodSync4(path2, FLOOD_429_LEDGER_MODE);
23853
+ } catch {}
23854
+ return;
23855
+ } catch (err) {
23856
+ const code2 = err?.code;
23857
+ if (code2 !== "EACCES" && code2 !== "EPERM") {
23858
+ log(`telegram gateway: 429-ledger: could not persist ${path2} (${code2 ?? "error"})
23859
+ `);
23860
+ return;
23861
+ }
23862
+ try {
23863
+ unlinkSync9(path2);
23864
+ writeFileSync11(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
23865
+ } catch (err2) {
23866
+ log(`telegram gateway: 429-ledger: cannot persist the 429 pressure ledger to ${path2} ` + `(${code2}, recreate failed: ${err2?.message ?? String(err2)}). ` + `Flood-pressure history will be missing from \`switchroom doctor\`
23867
+ `);
23868
+ }
23869
+ }
23870
+ }
23871
+ function recordFlood429(path2, obs, log = (l) => process.stderr.write(l)) {
23872
+ const folded = foldFlood429Observation(readFlood429Ledger(path2), obs, obs.ts);
23873
+ writeFlood429Ledger(path2, folded, log);
23874
+ }
23875
+ var FLOOD_429_LEDGER_FILE = "429-ledger.json", FLOOD_429_LEDGER_MODE = 420, EPISODE_MERGE_MS = 60000, FLOOD_429_LEDGER_MAX_EPISODES = 400, FLOOD_429_LEDGER_RETENTION_MS, PENALTY_WINDOW_MS, TRIVIAL_PRESSURE_WINDOW_MS;
23876
+ var init_flood_429_ledger = __esm(() => {
23877
+ FLOOD_429_LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
23878
+ PENALTY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
23879
+ TRIVIAL_PRESSURE_WINDOW_MS = 24 * 60 * 60 * 1000;
23880
+ });
23881
+
23882
+ // flood-circuit-breaker.ts
23883
+ import {
23884
+ existsSync as existsSync11,
23885
+ readFileSync as readFileSync14,
23886
+ writeFileSync as writeFileSync12,
23887
+ mkdirSync as mkdirSync15,
23888
+ chmodSync as chmodSync5,
23889
+ unlinkSync as unlinkSync10,
23890
+ renameSync as renameSync9
23891
+ } from "node:fs";
23892
+ import { dirname as dirname8, join as join18 } from "node:path";
23893
+ function floodStatePath(stateDir) {
23894
+ return join18(stateDir, FLOOD_STATE_FILE);
23895
+ }
23896
+ function computeFloodWait(prior, retryAfterSec, now) {
23897
+ const candidate = now + Math.max(0, retryAfterSec) * 1000;
23898
+ const untilTs = prior && prior.untilTs > candidate ? prior.untilTs : candidate;
23899
+ return { untilTs, retryAfterSec, recordedTs: now };
23900
+ }
23901
+ function floodWaitRemainingMs(state, now) {
23902
+ if (!state)
23903
+ return 0;
23904
+ return Math.max(0, state.untilTs - now);
23905
+ }
23906
+ function readFloodStateResult(path2) {
23907
+ let text4;
23908
+ try {
23909
+ text4 = readFileSync14(path2, "utf-8");
23910
+ } catch (err) {
23911
+ const code2 = err?.code;
23912
+ if (code2 === "ENOENT" || code2 === "ENOTDIR")
23913
+ return { status: "absent", state: null };
23914
+ return {
23915
+ status: "unreadable",
23916
+ state: null,
23917
+ error: `${code2 ?? "EUNKNOWN"}: ${err?.message ?? String(err)}`
23918
+ };
23919
+ }
23920
+ try {
23921
+ const raw = JSON.parse(text4);
23922
+ if (typeof raw.untilTs !== "number" || !Number.isFinite(raw.untilTs)) {
23923
+ return { status: "corrupt", state: null, error: "untilTs is not a finite number" };
23924
+ }
23925
+ return {
23926
+ status: "ok",
23927
+ state: {
23928
+ untilTs: raw.untilTs,
23929
+ retryAfterSec: typeof raw.retryAfterSec === "number" ? raw.retryAfterSec : 0,
23930
+ recordedTs: typeof raw.recordedTs === "number" ? raw.recordedTs : 0
23931
+ }
23932
+ };
23933
+ } catch (err) {
23934
+ return { status: "corrupt", state: null, error: err?.message ?? String(err) };
23935
+ }
23936
+ }
23937
+ function readFloodState(path2) {
23938
+ return readFloodStateResult(path2).state;
23939
+ }
23940
+ function writeFloodState(path2, state, log = (l) => process.stderr.write(l)) {
23941
+ const payload = JSON.stringify(state);
23942
+ try {
23943
+ mkdirSync15(dirname8(path2), { recursive: true });
23944
+ } catch {}
23945
+ try {
23946
+ writeFileSync12(path2, payload, { mode: FLOOD_STATE_MODE });
23947
+ try {
23948
+ chmodSync5(path2, FLOOD_STATE_MODE);
23949
+ } catch {}
23950
+ return;
23951
+ } catch (err) {
23952
+ const code2 = err?.code;
23953
+ if (code2 !== "EACCES" && code2 !== "EPERM") {
23954
+ log(`telegram gateway: flood-breaker: could not persist ${path2} (${code2 ?? "error"})
23955
+ `);
23956
+ return;
23957
+ }
23958
+ try {
23959
+ unlinkSync10(path2);
23960
+ writeFileSync12(path2, payload, { mode: FLOOD_STATE_MODE });
23961
+ log(`telegram gateway: flood-breaker: recreated ${path2} \u2014 it was owned by another uid ` + `and could not be updated (${code2}); the breaker was blind (issue #3106)
23962
+ `);
23963
+ } catch (err2) {
23964
+ log(`telegram gateway: flood-breaker: BLIND \u2014 cannot persist the flood-wait window to ${path2} ` + `(${code2}, and recreate failed: ${err2?.message ?? String(err2)}). ` + `Flood bans will NOT be recorded. Fix the file's ownership/mode (issue #3106)
23965
+ `);
23966
+ }
23967
+ }
23968
+ }
23969
+ function makeFloodWaitRecorder(path2, now = Date.now, log = (l) => process.stderr.write(l)) {
23970
+ return (retryAfterSec) => {
23971
+ const t = now();
23972
+ const next = computeFloodWait(readFloodState(path2), retryAfterSec, t);
23973
+ writeFloodState(path2, next, log);
23974
+ try {
23975
+ recordFlood429(flood429LedgerPathFromFloodState(path2), { ts: t, retryAfterSec }, log);
23976
+ } catch {}
23977
+ };
23978
+ }
23979
+ function warnBlind(path2, error, now, log) {
23980
+ const last = blindLoggedAt.get(path2);
23981
+ if (last !== undefined && now - last < BLIND_LOG_INTERVAL_MS)
23982
+ return;
23983
+ blindLoggedAt.set(path2, now);
23984
+ log(`telegram gateway: flood-breaker: BLIND \u2014 cannot read the flood-wait marker ${path2} ` + `(${error ?? "unknown error"}). The breaker cannot tell whether a Telegram flood ban is ` + `open. Essential sends PROCEED (fail-open); non-essential sends (boot card, typing) are ` + `SUPPRESSED. Fix the file's ownership/mode (issue #3106)
23985
+ `);
23986
+ }
23987
+ function makeFloodWaitProbe(path2, now = Date.now, log = (l) => process.stderr.write(l)) {
23988
+ return () => {
23989
+ const t = now();
23990
+ const res = readFloodStateResult(path2);
23991
+ if (res.status === "unreadable") {
23992
+ warnBlind(path2, res.error, t, log);
23993
+ return 0;
23994
+ }
23995
+ return floodWaitRemainingMs(res.state, t);
23996
+ };
23997
+ }
23998
+ function nonEssentialSendSuppression(path2, now) {
23999
+ const res = readFloodStateResult(path2);
24000
+ if (res.status === "unreadable") {
24001
+ return { suppress: true, reason: "blind", error: res.error ?? "unknown error" };
24002
+ }
24003
+ const remainingMs = floodWaitRemainingMs(res.state, now);
24004
+ if (remainingMs > 0)
24005
+ return { suppress: true, reason: "flood_wait", remainingMs };
24006
+ return { suppress: false };
24007
+ }
24008
+ var FLOOD_STATE_FILE = "flood-wait.json", FLOOD_STATE_MODE = 420, BLIND_LOG_INTERVAL_MS = 60000, blindLoggedAt, FLOOD_WINDOWS_CORRUPT_SUPPRESS_MS;
24009
+ var init_flood_circuit_breaker = __esm(() => {
24010
+ init_flood_429_ledger();
24011
+ blindLoggedAt = new Map;
24012
+ FLOOD_WINDOWS_CORRUPT_SUPPRESS_MS = 5 * 60000;
24013
+ });
24014
+
23914
24015
  // quota-check.ts
23915
24016
  import { readFileSync as readFileSync19, existsSync as existsSync17 } from "fs";
23916
24017
  import { join as join25 } from "path";
@@ -37196,7 +37297,7 @@ function renderBootCard(opts) {
37196
37297
  const ageStr = restartAgeMs != null && restartAgeMs > 0 ? ` \u00b7 ${(restartAgeMs / 1000).toFixed(1)}s ago` : "";
37197
37298
  degradedRows.push(`\u26a0\ufe0f **Restart** ${escapeMarkdown(REASON_LABEL.crash)}${ageStr}`);
37198
37299
  const tailCmd = process.env.SWITCHROOM_RUNTIME === "docker" ? `docker logs --tail 100 switchroom-${agentSlug}` : `journalctl --user -u switchroom-${agentSlug} -n 100`;
37199
- degradedRows.push(` \u21b3 Tail logs: \`${tailCmd}\``);
37300
+ degradedRows.push(`${NESTED_PREFIX}Tail logs: \`${tailCmd}\``);
37200
37301
  }
37201
37302
  if (probes) {
37202
37303
  for (const key of PROBE_KEYS) {
@@ -37210,7 +37311,7 @@ function renderBootCard(opts) {
37210
37311
  const dot = DOT[r.status] ?? DOT.fail;
37211
37312
  degradedRows.push(`${dot} **${PROBE_LABELS[key]}** ${escapeMarkdown(r.detail)}`);
37212
37313
  if (r.nextStep) {
37213
- degradedRows.push(` \u21b3 ${renderNextStep(r.nextStep)}`);
37314
+ degradedRows.push(`${NESTED_PREFIX}${renderNextStep(r.nextStep)}`);
37214
37315
  }
37215
37316
  }
37216
37317
  }
@@ -37514,6 +37615,7 @@ var SETTLE_WINDOW_MS = 6000, DOT, PROBE_LABELS, PROBE_KEYS, REASON_EMOJI, REASON
37514
37615
  var init_boot_card = __esm(() => {
37515
37616
  init_boot_probes();
37516
37617
  init_card_format();
37618
+ init_status_no_truncate();
37517
37619
  init_boot_issue_cache();
37518
37620
  init_config_snapshot();
37519
37621
  init_boot_card_msgid();
@@ -38979,7 +39081,7 @@ function buildGrantedKeyboard(scope) {
38979
39081
  const btn = scopeToOpenInDriveButton(scope);
38980
39082
  if (btn === null)
38981
39083
  return;
38982
- return new import_grammy15.InlineKeyboard().url(btn.text, btn.url);
39084
+ return new import_grammy16.InlineKeyboard().url(btn.text, btn.url);
38983
39085
  }
38984
39086
  async function handleApprovalCallback(ctx, data) {
38985
39087
  const parsed = parseApprovalCallback(data);
@@ -39025,11 +39127,11 @@ async function handleApprovalCallback(ctx, data) {
39025
39127
  } catch {}
39026
39128
  await ctx.answerCallbackQuery({ text: granted ? "Approved" : "Denied" });
39027
39129
  }
39028
- var import_grammy15;
39130
+ var import_grammy16;
39029
39131
  var init_approval_callback = __esm(() => {
39030
39132
  init_approval_card();
39031
39133
  init_client3();
39032
- import_grammy15 = __toESM(require_mod2(), 1);
39134
+ import_grammy16 = __toESM(require_mod2(), 1);
39033
39135
  });
39034
39136
 
39035
39137
  // ../src/telegram/materialize-bot-token.ts
@@ -39202,7 +39304,7 @@ var init_approvals_commands = __esm(() => {
39202
39304
  });
39203
39305
 
39204
39306
  // gateway/gateway.ts
39205
- var import_grammy16 = __toESM(require_mod(), 1);
39307
+ var import_grammy17 = __toESM(require_mod(), 1);
39206
39308
  var import_runner3 = __toESM(require_mod3(), 1);
39207
39309
  import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39208
39310
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
@@ -43181,10 +43283,64 @@ init_rich_send();
43181
43283
  // shared/bot-runtime.ts
43182
43284
  var import_grammy3 = __toESM(require_mod2(), 1);
43183
43285
  var import_runner = __toESM(require_mod4(), 1);
43184
- import { AsyncLocalStorage } from "async_hooks";
43286
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
43185
43287
 
43186
43288
  // retry-api-call.ts
43187
43289
  var import_grammy2 = __toESM(require_mod2(), 1);
43290
+ import { AsyncLocalStorage } from "async_hooks";
43291
+ var attemptStore = new AsyncLocalStorage;
43292
+ function _getTgAttemptContext() {
43293
+ return attemptStore.getStore();
43294
+ }
43295
+ function withAttemptContext(ctx, fn) {
43296
+ return attemptStore.run(ctx, fn);
43297
+ }
43298
+ function isTransientNetworkMessage(msg) {
43299
+ return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND");
43300
+ }
43301
+ function isAbortError(err) {
43302
+ const e = err;
43303
+ return e?.name === "AbortError" || e?.code === "ABORT_ERR";
43304
+ }
43305
+ function isTransientTransportError(err) {
43306
+ if (err instanceof import_grammy2.GrammyError)
43307
+ return false;
43308
+ if (isLocalResourceError(err))
43309
+ return false;
43310
+ if (err instanceof import_grammy2.HttpError) {
43311
+ const inner = err.error;
43312
+ if (isLocalResourceError(inner))
43313
+ return false;
43314
+ if (isAbortError(inner))
43315
+ return false;
43316
+ return true;
43317
+ }
43318
+ const msg = err instanceof Error ? err.message : String(err ?? "");
43319
+ if (isTransientNetworkMessage(msg))
43320
+ return true;
43321
+ const cause = err?.cause;
43322
+ if (cause == null || cause === err)
43323
+ return false;
43324
+ if (isLocalResourceError(cause))
43325
+ return false;
43326
+ return isTransientNetworkMessage(cause instanceof Error ? cause.message : String(cause));
43327
+ }
43328
+ function willRetryTelegramFailure(failure, ctx = _getTgAttemptContext()) {
43329
+ if (ctx == null)
43330
+ return false;
43331
+ if (ctx.attempt >= ctx.maxRetries - 1)
43332
+ return false;
43333
+ if (failure.errorCode === 429) {
43334
+ const retryAfter = Number(failure.retryAfterSec ?? 5);
43335
+ const delayMs = (Number.isFinite(retryAfter) ? retryAfter : 5) * 1000;
43336
+ return delayMs <= ctx.maxFloodSleepMs;
43337
+ }
43338
+ if (failure.errorCode != null)
43339
+ return false;
43340
+ if (failure.cause != null)
43341
+ return isTransientTransportError(failure.cause);
43342
+ return isTransientTransportError(new Error(failure.message ?? ""));
43343
+ }
43188
43344
  function classifyBenignTelegram400(errorCode, description) {
43189
43345
  if (errorCode !== 400)
43190
43346
  return null;
@@ -43252,7 +43408,7 @@ function createRetryApiCall(config = {}) {
43252
43408
  throw gated;
43253
43409
  }
43254
43410
  try {
43255
- return await fn();
43411
+ return await withAttemptContext({ attempt, maxRetries, maxFloodSleepMs }, fn);
43256
43412
  } catch (err) {
43257
43413
  const isGrammyErr = err instanceof import_grammy2.GrammyError;
43258
43414
  const msg = err instanceof Error ? err.message : String(err);
@@ -43284,12 +43440,14 @@ function createRetryApiCall(config = {}) {
43284
43440
  const benignKind = isGrammyErr ? classifyBenignTelegram400(err.error_code, desc) : null;
43285
43441
  if (benignKind !== null) {
43286
43442
  observer?.onBenign?.({ kind: benignKind });
43443
+ if (opts?.rethrowBenign400)
43444
+ throw err;
43287
43445
  return;
43288
43446
  }
43289
43447
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
43290
43448
  throw Object.assign(new Error("THREAD_NOT_FOUND"), { original: err });
43291
43449
  }
43292
- if (!isGrammyErr && (msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND"))) {
43450
+ if (isTransientTransportError(err)) {
43293
43451
  if (attempt < maxRetries - 1) {
43294
43452
  const delayMs = Math.pow(2, attempt) * 1000;
43295
43453
  log?.(`telegram gateway: network error, retrying in ${delayMs / 1000}s: ${msg}
@@ -43347,7 +43505,6 @@ function isPhotoDimensionRejectError(err) {
43347
43505
  }
43348
43506
 
43349
43507
  // shared/bot-runtime.ts
43350
- init_flood_circuit_breaker();
43351
43508
  init_format();
43352
43509
 
43353
43510
  // shared/gw-trace-gate.ts
@@ -43360,7 +43517,7 @@ var ZERO_SIGNAL_POLL_METHODS = new Set(["getUpdates", "getMe"]);
43360
43517
  function shouldEmitTgPost(method, status, verbose = gwTraceVerbose) {
43361
43518
  if (verbose)
43362
43519
  return true;
43363
- if (status === "err")
43520
+ if (status === "err" || status === "retry")
43364
43521
  return true;
43365
43522
  return !ZERO_SIGNAL_POLL_METHODS.has(method);
43366
43523
  }
@@ -43377,7 +43534,7 @@ function shouldEmitShadowTrace(eventKind, effectCount, globalKind, verbose = gwT
43377
43534
 
43378
43535
  // shared/bot-runtime.ts
43379
43536
  init_rich_send();
43380
- var tgPostTagStore = new AsyncLocalStorage;
43537
+ var tgPostTagStore = new AsyncLocalStorage2;
43381
43538
  function escapeHtmlForTg(text) {
43382
43539
  return text.replace(/([\\`*_~=\[\]|])/g, "\\$1");
43383
43540
  }
@@ -44985,10 +45142,10 @@ async function handleAnimationMessage(ctx, deps) {
44985
45142
  }
44986
45143
 
44987
45144
  // gateway/photo-message-handler.ts
44988
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
45145
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
44989
45146
 
44990
45147
  // attachment-path.ts
44991
- import { join as join7, basename as basename3, resolve as resolve2, sep } from "node:path";
45148
+ import { join as join5, basename as basename3, resolve as resolve2, sep } from "node:path";
44992
45149
  function sanitizeExtension(ext) {
44993
45150
  if (ext == null)
44994
45151
  return "bin";
@@ -45011,7 +45168,7 @@ function buildAttachmentPath(input) {
45011
45168
  const ext = extractExtension(input.telegramFilePath);
45012
45169
  const uid = sanitizeUniqueId(input.fileUniqueId);
45013
45170
  const filename = `${input.now}-${uid}.${ext}`;
45014
- return join7(input.inboxDir, filename);
45171
+ return join5(input.inboxDir, filename);
45015
45172
  }
45016
45173
  function assertInsideInbox(inboxDir, candidatePath) {
45017
45174
  const inboxReal = resolve2(inboxDir);
@@ -45046,9 +45203,9 @@ async function handlePhotoMessage(ctx, deps) {
45046
45203
  fileUniqueId: best.file_unique_id,
45047
45204
  now: Date.now()
45048
45205
  });
45049
- mkdirSync8(deps.inboxDir, { recursive: true, mode: 448 });
45206
+ mkdirSync6(deps.inboxDir, { recursive: true, mode: 448 });
45050
45207
  assertInsideInbox(deps.inboxDir, dlPath);
45051
- writeFileSync7(dlPath, buf, { mode: 384 });
45208
+ writeFileSync5(dlPath, buf, { mode: 384 });
45052
45209
  return dlPath;
45053
45210
  } catch (err) {
45054
45211
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -45562,12 +45719,42 @@ function assessThinkingEffortRisk(model, effort) {
45562
45719
  };
45563
45720
  }
45564
45721
 
45565
- // gateway/model-command.ts
45566
- var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
45722
+ // ../src/agents/model-aliases.ts
45567
45723
  var CLAUDE_MODEL_ALIASES = {
45568
45724
  opus48: "claude-opus-4-8",
45569
45725
  "opus-4-8": "claude-opus-4-8"
45570
45726
  };
45727
+ var SR_MODEL_ALIASES = {
45728
+ flash: "sr-gemini-2.5-flash",
45729
+ gemini: "sr-gemini-2.5-pro",
45730
+ deepseek: "sr-deepseek-v3",
45731
+ r1: "sr-deepseek-r1",
45732
+ glm: "sr-glm-5",
45733
+ codex: "sr-codex-5.5",
45734
+ grok: "sr-grok-4.5",
45735
+ kimi: "sr-kimi-k3",
45736
+ gpt: "sr-gpt-5.6-sol",
45737
+ sol: "sr-gpt-5.6-sol",
45738
+ terra: "sr-gpt-5.6-terra",
45739
+ luna: "sr-gpt-5.6-luna"
45740
+ };
45741
+ function expandSrAlias(arg) {
45742
+ return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
45743
+ }
45744
+ function expandClaudeAlias(arg) {
45745
+ return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
45746
+ }
45747
+ function canonicalModelToken(arg) {
45748
+ const trimmed = arg.trim();
45749
+ const lower = trimmed.toLowerCase();
45750
+ return lower.startsWith("claude-") ? lower : trimmed;
45751
+ }
45752
+ function expandModelAlias(arg) {
45753
+ return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
45754
+ }
45755
+
45756
+ // gateway/model-command.ts
45757
+ var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
45571
45758
  var MODEL_ARG_RE = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
45572
45759
  function isValidModelArg(arg) {
45573
45760
  return MODEL_ARG_RE.test(arg);
@@ -45767,34 +45954,6 @@ var SR_MODEL_LABELS = {
45767
45954
  "sr-gpt-5.6-terra": "GPT-5.6 Terra",
45768
45955
  "sr-gpt-5.6-luna": "GPT-5.6 Luna"
45769
45956
  };
45770
- var SR_MODEL_ALIASES = {
45771
- flash: "sr-gemini-2.5-flash",
45772
- gemini: "sr-gemini-2.5-pro",
45773
- deepseek: "sr-deepseek-v3",
45774
- r1: "sr-deepseek-r1",
45775
- glm: "sr-glm-5",
45776
- codex: "sr-codex-5.5",
45777
- grok: "sr-grok-4.5",
45778
- kimi: "sr-kimi-k3",
45779
- gpt: "sr-gpt-5.6-sol",
45780
- sol: "sr-gpt-5.6-sol",
45781
- terra: "sr-gpt-5.6-terra",
45782
- luna: "sr-gpt-5.6-luna"
45783
- };
45784
- function expandSrAlias(arg) {
45785
- return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
45786
- }
45787
- function canonicalModelToken(arg) {
45788
- const trimmed = arg.trim();
45789
- const lower = trimmed.toLowerCase();
45790
- return lower.startsWith("claude-") ? lower : trimmed;
45791
- }
45792
- function expandClaudeAlias(arg) {
45793
- return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
45794
- }
45795
- function expandModelAlias(arg) {
45796
- return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
45797
- }
45798
45957
  function srFriendlyLabel(srName) {
45799
45958
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
45800
45959
  }
@@ -46272,7 +46431,7 @@ async function handleAskCallback(ctx, data, deps) {
46272
46431
  }
46273
46432
 
46274
46433
  // gateway/voice-ondemand-callback-handler.ts
46275
- import { readFileSync as readFileSync9 } from "node:fs";
46434
+ import { readFileSync as readFileSync7 } from "node:fs";
46276
46435
 
46277
46436
  // voice-send.ts
46278
46437
  function extractVoiceFileId(sent) {
@@ -46710,14 +46869,14 @@ table omitted.`);
46710
46869
  init_loader();
46711
46870
  init_resolver();
46712
46871
  init_vault();
46713
- import { existsSync as existsSync8 } from "node:fs";
46872
+ import { existsSync as existsSync7 } from "node:fs";
46714
46873
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
46715
46874
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
46716
46875
  function tryDirectVaultRead(ref, config, passphrase) {
46717
46876
  if (!passphrase)
46718
46877
  return null;
46719
46878
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
46720
- if (!existsSync8(vaultPath))
46879
+ if (!existsSync7(vaultPath))
46721
46880
  return null;
46722
46881
  try {
46723
46882
  const secrets = openVault(passphrase, vaultPath);
@@ -46804,7 +46963,7 @@ async function handleVoiceOnDemandCallback(ctx, data, deps) {
46804
46963
  let audio = null;
46805
46964
  if (entry.filePath != null) {
46806
46965
  try {
46807
- audio = readFileSync9(entry.filePath);
46966
+ audio = readFileSync7(entry.filePath);
46808
46967
  } catch {
46809
46968
  audio = null;
46810
46969
  }
@@ -47389,18 +47548,11 @@ class DeferredDoneReactions {
47389
47548
 
47390
47549
  // worker-activity-feed.ts
47391
47550
  init_card_format();
47392
-
47393
- // status-no-truncate.ts
47394
- init_format();
47395
- var STATUS_ROLLING_LINES = 5;
47396
- var WORKER_HISTORY_MAX = 6;
47397
- var STATUS_LINE_MAX = 200;
47398
- var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
47399
- var NESTED_PREFIX = " \u21b3 ";
47400
- var WORKER_STEP_INDENT = "\u2800\u2800\u2800";
47551
+ init_status_no_truncate();
47401
47552
 
47402
47553
  // card-layout.ts
47403
47554
  init_card_format();
47555
+ init_format();
47404
47556
 
47405
47557
  // model-label.ts
47406
47558
  var DATE_SUFFIX = /^\d{8}$/;
@@ -47439,6 +47591,7 @@ function formatModelLabel(model) {
47439
47591
  }
47440
47592
 
47441
47593
  // card-layout.ts
47594
+ init_status_no_truncate();
47442
47595
  function formatFeedElapsed(ms) {
47443
47596
  const s = Math.floor(ms / 1000);
47444
47597
  if (s < 60)
@@ -47537,15 +47690,36 @@ function fitCardToBudget(build, maxLevel) {
47537
47690
  if (candidate.overflow !== true && text.length <= STATUS_CARD_CHAR_BUDGET)
47538
47691
  return text;
47539
47692
  }
47540
- return text;
47693
+ return hardTruncateCard(text);
47694
+ }
47695
+ function hardTruncateCard(text, budget = STATUS_CARD_CHAR_BUDGET) {
47696
+ if (text.length <= budget)
47697
+ return text;
47698
+ let kept = "";
47699
+ for (const line of text.split(`
47700
+ `)) {
47701
+ const next = kept.length === 0 ? line : `${kept}
47702
+ ${line}`;
47703
+ if (trimBreakChrome(next).length > budget)
47704
+ break;
47705
+ kept = next;
47706
+ }
47707
+ const target = kept.length > 0 ? kept.length : Math.min(budget, text.length);
47708
+ let out = trimBreakChrome(truncateMarkdownSafe(text, target));
47709
+ if (out.length > budget)
47710
+ out = trimBreakChrome(truncateMarkdownSafe(text, budget));
47711
+ return /[\uD800-\uDBFF]$/.test(out) ? out.slice(0, -1) : out;
47712
+ }
47713
+ function trimBreakChrome(text) {
47714
+ return text.replace(/[ \t\r\u00A0]+$/, "");
47541
47715
  }
47542
47716
 
47543
47717
  // hooks/tool-label-pretool.mjs
47544
- import { readFileSync as readFileSync10, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, existsSync as existsSync9 } from "node:fs";
47545
- import { join as join9, basename as basename5 } from "node:path";
47718
+ import { readFileSync as readFileSync8, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, existsSync as existsSync8 } from "node:fs";
47719
+ import { join as join7, basename as basename5 } from "node:path";
47546
47720
  function readStdin() {
47547
47721
  try {
47548
- return readFileSync10(0, "utf8");
47722
+ return readFileSync8(0, "utf8");
47549
47723
  } catch {
47550
47724
  return "";
47551
47725
  }
@@ -47851,10 +48025,10 @@ function main() {
47851
48025
  }) + `
47852
48026
  `;
47853
48027
  try {
47854
- if (!existsSync9(stateDir)) {
47855
- mkdirSync11(stateDir, { recursive: true });
48028
+ if (!existsSync8(stateDir)) {
48029
+ mkdirSync9(stateDir, { recursive: true });
47856
48030
  }
47857
- const target = join9(stateDir, `tool-labels-${sessionId}.jsonl`);
48031
+ const target = join7(stateDir, `tool-labels-${sessionId}.jsonl`);
47858
48032
  appendFileSync2(target, line);
47859
48033
  } catch (err) {
47860
48034
  try {
@@ -47876,6 +48050,7 @@ if (isMain)
47876
48050
  main();
47877
48051
 
47878
48052
  // tool-activity-summary.ts
48053
+ init_status_no_truncate();
47879
48054
  init_card_format();
47880
48055
  init_redact();
47881
48056
 
@@ -47953,14 +48128,14 @@ function renderStatusCard(opts) {
47953
48128
  return null;
47954
48129
  const parentMarker = hasChildren && rawSteps.length > 0 ? `_\u2713 +${rawSteps.length} earlier\u2026_` : null;
47955
48130
  const deepest = Math.max(1, escapedBody.length);
48131
+ const markerSection = {
48132
+ steps: [],
48133
+ window: 1,
48134
+ placeholder: parentMarker ?? undefined
48135
+ };
47956
48136
  return fitCardToBudget((level) => {
47957
48137
  if (level === 0)
47958
48138
  return { spec: fullSpec() };
47959
- const markerSection = {
47960
- steps: [],
47961
- window: 1,
47962
- placeholder: parentMarker ?? undefined
47963
- };
47964
48139
  if (level < escapedBody.length) {
47965
48140
  return {
47966
48141
  spec: {
@@ -47989,23 +48164,38 @@ function renderStatusCard(opts) {
47989
48164
  }
47990
48165
  };
47991
48166
  }, deepest);
48167
+ function deepestSpec(bodyLine) {
48168
+ return {
48169
+ chrome,
48170
+ sections: [markerSection, { steps: [], window: 1, placeholder: bodyLine }],
48171
+ footer
48172
+ };
48173
+ }
48174
+ function wrapNewest(escaped) {
48175
+ return final ? `${bodyIndent}_\u2713 ${escaped}_` : `${bodyIndent}**\u2192 ${escaped}${liveSuffix}**`;
48176
+ }
47992
48177
  function truncatedNewestLine() {
47993
- const fixedCost = [...chrome, ...footer].join(`
47994
- `).length;
47995
48178
  const rawNewest = rawBody.length > 0 ? cleanStepLine(rawBody[rawBody.length - 1]) : "";
47996
- const wrapperOverhead = final ? (bodyIndent + "_\u2713 _").length : (bodyIndent + "**\u2192 **").length + liveSuffix.length;
47997
- const headerFooterCost = fixedCost + (fixedCost > 0 ? 1 : 0) + (parentMarker != null ? parentMarker.length + 1 : 0);
47998
- const budget = STATUS_CARD_CHAR_BUDGET - headerFooterCost - wrapperOverhead;
47999
- let raw = rawNewest.slice(0, Math.max(0, budget));
48000
- let newest = escapeMarkdown(raw);
48001
- while (raw.length > 0 && wrapperOverhead + headerFooterCost + newest.length > STATUS_CARD_CHAR_BUDGET) {
48002
- const excess = wrapperOverhead + headerFooterCost + newest.length - STATUS_CARD_CHAR_BUDGET;
48003
- raw = raw.slice(0, Math.max(0, raw.length - excess - 1));
48004
- newest = escapeMarkdown(raw);
48179
+ const empty = wrapNewest("");
48180
+ const fixedCost = renderCardSpec(deepestSpec(empty)).length;
48181
+ const budget = STATUS_CARD_CHAR_BUDGET - fixedCost;
48182
+ if (budget <= 0)
48183
+ return empty;
48184
+ let raw = clipRaw(rawNewest, budget);
48185
+ let line = wrapNewest(escapeMarkdown(raw));
48186
+ for (;; ) {
48187
+ const excess = renderCardSpec(deepestSpec(line)).length - STATUS_CARD_CHAR_BUDGET;
48188
+ if (excess <= 0 || raw.length === 0)
48189
+ return line;
48190
+ raw = clipRaw(raw, Math.max(0, raw.length - excess));
48191
+ line = wrapNewest(escapeMarkdown(raw));
48005
48192
  }
48006
- return final ? `${bodyIndent}_\u2713 ${newest}_` : `${bodyIndent}**\u2192 ${newest}${liveSuffix}**`;
48007
48193
  }
48008
48194
  }
48195
+ function clipRaw(text, n) {
48196
+ const out = text.slice(0, Math.max(0, n));
48197
+ return /[\uD800-\uDBFF]$/.test(out) ? out.slice(0, -1) : out;
48198
+ }
48009
48199
  var WORKER_RESULT_RULE = "\u2500\u2500\u2500\u2500\u2500";
48010
48200
  var WORKER_RESULT_MAX = 320;
48011
48201
  function deriveCardResult(state, summary) {
@@ -48106,8 +48296,8 @@ function appendActivityLabel(lines, label) {
48106
48296
  }
48107
48297
 
48108
48298
  // outbound-class.ts
48109
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
48110
- var store = new AsyncLocalStorage2;
48299
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
48300
+ var store = new AsyncLocalStorage3;
48111
48301
  function withOutboundClass(cls, fn) {
48112
48302
  return store.run(cls, fn);
48113
48303
  }
@@ -48211,7 +48401,7 @@ function classifyEditError(err) {
48211
48401
  function createWorkerActivityFeed(opts) {
48212
48402
  const log = opts.log ?? (() => {});
48213
48403
  const nowFn = opts.now ?? Date.now;
48214
- const floodWaitRemainingMs2 = opts.floodWaitRemainingMs ?? (() => 0);
48404
+ const floodWaitRemainingMs = opts.floodWaitRemainingMs ?? (() => 0);
48215
48405
  const minEditInterval = opts.minEditIntervalMs ?? 2500;
48216
48406
  const elapsedRefreshMs = Math.max(minEditInterval, Math.floor(opts.elapsedRefreshMs ?? 15000));
48217
48407
  const firstPaintMin = opts.firstPaintMinMs ?? 8000;
@@ -48264,7 +48454,7 @@ function createWorkerActivityFeed(opts) {
48264
48454
  log(`worker-feed: ${label} 429 \u2014 backing off ${retryAfter}s`);
48265
48455
  }
48266
48456
  function parkIfFloodWindowOpen(g) {
48267
- const remaining = floodWaitRemainingMs2();
48457
+ const remaining = floodWaitRemainingMs();
48268
48458
  if (remaining <= 0)
48269
48459
  return false;
48270
48460
  const until = nowFn() + remaining + COOLDOWN_JITTER_MS;
@@ -49221,13 +49411,13 @@ function buildCancelledCardEdits(cardText, cards) {
49221
49411
  var STALE_TAP_NOTICE = "This request already resolved (timed out) \u2014 ask again to act.";
49222
49412
 
49223
49413
  // gateway/permission-card-store.ts
49224
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "node:fs";
49225
- import { join as join10 } from "node:path";
49414
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "node:fs";
49415
+ import { join as join8 } from "node:path";
49226
49416
  function createPermissionCardStore(stateDir) {
49227
- const filePath = join10(stateDir, "pending-perm-cards.json");
49417
+ const filePath = join8(stateDir, "pending-perm-cards.json");
49228
49418
  function read() {
49229
49419
  try {
49230
- const raw = readFileSync11(filePath, "utf-8");
49420
+ const raw = readFileSync9(filePath, "utf-8");
49231
49421
  const parsed = JSON.parse(raw);
49232
49422
  return Array.isArray(parsed) ? parsed : [];
49233
49423
  } catch {
@@ -49236,7 +49426,7 @@ function createPermissionCardStore(stateDir) {
49236
49426
  }
49237
49427
  function write(entries) {
49238
49428
  try {
49239
- writeFileSync8(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
49429
+ writeFileSync6(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
49240
49430
  } catch (err) {
49241
49431
  process.stderr.write(`telegram gateway: permission-card-store write failed: ${err.message}
49242
49432
  `);
@@ -49265,7 +49455,7 @@ function createPermissionCardStore(stateDir) {
49265
49455
  },
49266
49456
  clear() {
49267
49457
  try {
49268
- unlinkSync6(filePath);
49458
+ unlinkSync4(filePath);
49269
49459
  } catch {}
49270
49460
  }
49271
49461
  };
@@ -49273,13 +49463,13 @@ function createPermissionCardStore(stateDir) {
49273
49463
 
49274
49464
  // gateway/pending-card-store.ts
49275
49465
  init_atomic();
49276
- import { unlinkSync as unlinkSync7 } from "node:fs";
49277
- import { join as join12 } from "node:path";
49466
+ import { unlinkSync as unlinkSync5 } from "node:fs";
49467
+ import { join as join10 } from "node:path";
49278
49468
 
49279
49469
  // gateway/store-file.ts
49280
49470
  import { randomBytes as randomBytes6 } from "node:crypto";
49281
- import { readFileSync as readFileSync12, readdirSync as readdirSync4, renameSync as renameSync9, rmSync as rmSync4, statSync as statSync7 } from "node:fs";
49282
- import { basename as basename6, dirname as dirname6, join as join11 } from "node:path";
49471
+ import { readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync as renameSync8, rmSync as rmSync4, statSync as statSync7 } from "node:fs";
49472
+ import { basename as basename6, dirname as dirname4, join as join9 } from "node:path";
49283
49473
  var defaultLog = (line) => {
49284
49474
  process.stderr.write(line);
49285
49475
  };
@@ -49290,18 +49480,18 @@ function quarantinePath(filePath) {
49290
49480
  return `${filePath}.corrupt-${Date.now()}-${seq}-${randomBytes6(4).toString("hex")}`;
49291
49481
  }
49292
49482
  function reapOldQuarantines(filePath, log) {
49293
- const dir = dirname6(filePath);
49483
+ const dir = dirname4(filePath);
49294
49484
  const prefix = `${basename6(filePath)}.corrupt-`;
49295
49485
  try {
49296
49486
  const copies = readdirSync4(dir).filter((f) => f.startsWith(prefix)).map((name) => {
49297
49487
  let mtimeNs = 0n;
49298
49488
  try {
49299
- mtimeNs = statSync7(join11(dir, name), { bigint: true }).mtimeNs;
49489
+ mtimeNs = statSync7(join9(dir, name), { bigint: true }).mtimeNs;
49300
49490
  } catch {}
49301
49491
  return { name, mtimeNs };
49302
49492
  }).sort((a, b) => a.mtimeNs === b.mtimeNs ? a.name.localeCompare(b.name) : a.mtimeNs < b.mtimeNs ? -1 : 1);
49303
49493
  for (const stale of copies.slice(0, Math.max(0, copies.length - MAX_QUARANTINED_COPIES))) {
49304
- rmSync4(join11(dir, stale.name), { force: true });
49494
+ rmSync4(join9(dir, stale.name), { force: true });
49305
49495
  }
49306
49496
  } catch (err) {
49307
49497
  log(`telegram gateway: quarantine reap failed dir=${dir}: ${err.message}
@@ -49312,7 +49502,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
49312
49502
  const target = quarantinePath(filePath);
49313
49503
  let preserved = target;
49314
49504
  try {
49315
- renameSync9(filePath, target);
49505
+ renameSync8(filePath, target);
49316
49506
  } catch (err) {
49317
49507
  preserved = `NOT preserved (${err.message})`;
49318
49508
  }
@@ -49323,7 +49513,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
49323
49513
  function preserveUnreadableStoreFile(filePath, store2, log = defaultLog) {
49324
49514
  const target = quarantinePath(filePath);
49325
49515
  try {
49326
- renameSync9(filePath, target);
49516
+ renameSync8(filePath, target);
49327
49517
  } catch (err) {
49328
49518
  if (err.code === "ENOENT") {
49329
49519
  return;
@@ -49339,7 +49529,7 @@ function preserveUnreadableStoreFile(filePath, store2, log = defaultLog) {
49339
49529
  function readStoreJsonSync(filePath, store2, log = defaultLog) {
49340
49530
  let raw;
49341
49531
  try {
49342
- raw = readFileSync12(filePath, "utf-8");
49532
+ raw = readFileSync10(filePath, "utf-8");
49343
49533
  } catch (err) {
49344
49534
  if (err.code === "ENOENT")
49345
49535
  return { status: "missing" };
@@ -49357,7 +49547,7 @@ function readStoreJsonSync(filePath, store2, log = defaultLog) {
49357
49547
 
49358
49548
  // gateway/pending-card-store.ts
49359
49549
  function createPendingCardStore(stateDir, log = (l) => process.stderr.write(l)) {
49360
- const filePath = join12(stateDir, "pending-approval-cards.json");
49550
+ const filePath = join10(stateDir, "pending-approval-cards.json");
49361
49551
  let unreadable = false;
49362
49552
  function read() {
49363
49553
  const result = readStoreJsonSync(filePath, "pending-card-store", log);
@@ -49405,7 +49595,7 @@ function createPendingCardStore(stateDir, log = (l) => process.stderr.write(l))
49405
49595
  },
49406
49596
  clear() {
49407
49597
  try {
49408
- unlinkSync7(filePath);
49598
+ unlinkSync5(filePath);
49409
49599
  } catch {}
49410
49600
  }
49411
49601
  };
@@ -49544,8 +49734,8 @@ function createSweepableCardStore(deps) {
49544
49734
  init_rich_send();
49545
49735
  var import_grammy4 = __toESM(require_mod2(), 1);
49546
49736
  import { execFileSync as execFileSync3 } from "child_process";
49547
- import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "fs";
49548
- import { dirname as dirname7 } from "path";
49737
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
49738
+ import { dirname as dirname5 } from "path";
49549
49739
  init_client2();
49550
49740
 
49551
49741
  // gateway/vault-grant-inbound-builders.ts
@@ -49677,26 +49867,26 @@ var import_yaml3 = __toESM(require_dist(), 1);
49677
49867
 
49678
49868
  // ../src/web/config-diff.ts
49679
49869
  import {
49680
- mkdirSync as mkdirSync12,
49870
+ mkdirSync as mkdirSync10,
49681
49871
  mkdtempSync as mkdtemp,
49682
49872
  rmSync as rmrf,
49683
- writeFileSync as writeFileSync9
49873
+ writeFileSync as writeFileSync7
49684
49874
  } from "node:fs";
49685
49875
  import { spawnSync } from "node:child_process";
49686
49876
  import { tmpdir as tmpdir2 } from "node:os";
49687
- import { join as join13 } from "node:path";
49877
+ import { join as join11 } from "node:path";
49688
49878
 
49689
49879
  class ConfigDiffError extends Error {
49690
49880
  }
49691
49881
  function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
49692
49882
  if (before === after)
49693
49883
  return "";
49694
- const dir = mkdtemp(join13(tmpdir2(), "switchroom-config-diff-"));
49884
+ const dir = mkdtemp(join11(tmpdir2(), "switchroom-config-diff-"));
49695
49885
  try {
49696
- mkdirSync12(join13(dir, "cur"), { recursive: true });
49697
- mkdirSync12(join13(dir, "new"), { recursive: true });
49698
- writeFileSync9(join13(dir, "cur", name), before);
49699
- writeFileSync9(join13(dir, "new", name), after);
49886
+ mkdirSync10(join11(dir, "cur"), { recursive: true });
49887
+ mkdirSync10(join11(dir, "new"), { recursive: true });
49888
+ writeFileSync7(join11(dir, "cur", name), before);
49889
+ writeFileSync7(join11(dir, "new", name), after);
49700
49890
  const r = spawnSync(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
49701
49891
  if (r.status === 0)
49702
49892
  return "";
@@ -50027,25 +50217,25 @@ function buildSkillProposalApplyInbound(opts) {
50027
50217
  // ../src/self-improve/skill-proposals.ts
50028
50218
  import {
50029
50219
  closeSync as closeSync5,
50030
- existsSync as existsSync10,
50031
- mkdirSync as mkdirSync13,
50220
+ existsSync as existsSync9,
50221
+ mkdirSync as mkdirSync11,
50032
50222
  openSync as openSync5,
50033
- readFileSync as readFileSync13,
50223
+ readFileSync as readFileSync11,
50034
50224
  writeSync as writeSync5
50035
50225
  } from "node:fs";
50036
- import { join as join14 } from "node:path";
50226
+ import { join as join12 } from "node:path";
50037
50227
  var PROPOSALS_FILE = "skill-proposals.jsonl";
50038
50228
  var REJECTED_FILE = "skill-proposals-rejected.jsonl";
50039
50229
  var REJECTION_TTL_MS = 90 * 24 * 60 * 60 * 1000;
50040
50230
  function proposalsPath(stateDir) {
50041
- return join14(stateDir, PROPOSALS_FILE);
50231
+ return join12(stateDir, PROPOSALS_FILE);
50042
50232
  }
50043
50233
  function rejectedPath(stateDir) {
50044
- return join14(stateDir, REJECTED_FILE);
50234
+ return join12(stateDir, REJECTED_FILE);
50045
50235
  }
50046
50236
  function ensureDir2(stateDir) {
50047
- if (!existsSync10(stateDir)) {
50048
- mkdirSync13(stateDir, { recursive: true, mode: 493 });
50237
+ if (!existsSync9(stateDir)) {
50238
+ mkdirSync11(stateDir, { recursive: true, mode: 493 });
50049
50239
  }
50050
50240
  }
50051
50241
  function appendLine(path, obj) {
@@ -50058,11 +50248,11 @@ function appendLine(path, obj) {
50058
50248
  }
50059
50249
  }
50060
50250
  function readLines(path, isValid2) {
50061
- if (!existsSync10(path))
50251
+ if (!existsSync9(path))
50062
50252
  return [];
50063
50253
  let raw;
50064
50254
  try {
50065
- raw = readFileSync13(path, "utf-8");
50255
+ raw = readFileSync11(path, "utf-8");
50066
50256
  } catch {
50067
50257
  return [];
50068
50258
  }
@@ -50204,7 +50394,7 @@ async function addAccountViaBroker(label, credentials, opts = {}) {
50204
50394
  }
50205
50395
 
50206
50396
  // gateway/hostd-dispatch.ts
50207
- import { existsSync as existsSync11 } from "node:fs";
50397
+ import { existsSync as existsSync10 } from "node:fs";
50208
50398
  import { randomBytes as randomBytes7 } from "node:crypto";
50209
50399
 
50210
50400
  // ../src/host-control/client.ts
@@ -50527,13 +50717,13 @@ function hostdSocketPath(agentName) {
50527
50717
  function hostdWillBeUsed(agentName) {
50528
50718
  if (!isHostdEnabled())
50529
50719
  return false;
50530
- return existsSync11(hostdSocketPath(agentName));
50720
+ return existsSync10(hostdSocketPath(agentName));
50531
50721
  }
50532
50722
  async function tryHostdDispatch(agentName, req, timeoutMs = 5000) {
50533
50723
  if (!isHostdEnabled())
50534
50724
  return "not-configured";
50535
50725
  const sockPath = hostdSocketPath(agentName);
50536
- if (!existsSync11(sockPath))
50726
+ if (!existsSync10(sockPath))
50537
50727
  return "not-configured";
50538
50728
  try {
50539
50729
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -50557,7 +50747,7 @@ async function hostdGetStatusOnce(agentName, targetRequestId) {
50557
50747
  if (!isHostdEnabled())
50558
50748
  return "not-configured";
50559
50749
  const sockPath = hostdSocketPath(agentName);
50560
- if (!existsSync11(sockPath))
50750
+ if (!existsSync10(sockPath))
50561
50751
  return "not-configured";
50562
50752
  try {
50563
50753
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -50705,8 +50895,8 @@ function createCallbackQueryHandlers(deps) {
50705
50895
  const { token, id } = result;
50706
50896
  const tokenPath = vaultTokenFilePath2(agentName);
50707
50897
  try {
50708
- mkdirSync14(dirname7(tokenPath), { recursive: true });
50709
- writeFileSync10(tokenPath, token, { mode: 384 });
50898
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
50899
+ writeFileSync8(tokenPath, token, { mode: 384 });
50710
50900
  } catch (err) {
50711
50901
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
50712
50902
  ` + `_Recover with: \`switchroom vault grant ${escapeHtmlForTg2(agentName)} ` + `--keys ${escapeHtmlForTg2(keyName)} --duration 30d\` on the host._`, { html: true });
@@ -50857,8 +51047,8 @@ function createCallbackQueryHandlers(deps) {
50857
51047
  const { token, id } = result;
50858
51048
  const tokenPath = vaultTokenFilePath2(pending.agent);
50859
51049
  try {
50860
- mkdirSync14(dirname7(tokenPath), { recursive: true });
50861
- writeFileSync10(tokenPath, token, { mode: 384 });
51050
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
51051
+ writeFileSync8(tokenPath, token, { mode: 384 });
50862
51052
  } catch (err) {
50863
51053
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
50864
51054
  ` + `_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 });
@@ -51686,8 +51876,8 @@ ${keyList}`,
51686
51876
  const { token, id } = result;
51687
51877
  const tokenPath = vaultTokenFilePath2(state.agent);
51688
51878
  try {
51689
- mkdirSync14(dirname7(tokenPath), { recursive: true });
51690
- writeFileSync10(tokenPath, token, { mode: 384 });
51879
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
51880
+ writeFileSync8(tokenPath, token, { mode: 384 });
51691
51881
  } catch (err) {
51692
51882
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
51693
51883
  return;
@@ -52745,12 +52935,12 @@ function hangStalenessMs(env = process.env) {
52745
52935
 
52746
52936
  // gateway/missed-approvals-store.ts
52747
52937
  init_atomic();
52748
- import { unlinkSync as unlinkSync8 } from "node:fs";
52749
- import { join as join16 } from "node:path";
52938
+ import { unlinkSync as unlinkSync6 } from "node:fs";
52939
+ import { join as join14 } from "node:path";
52750
52940
  var MAX_PENDING = 50;
52751
52941
  var MAX_DELIVERED = 20;
52752
52942
  function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(l)) {
52753
- const filePath = join16(stateDir, "missed-approvals.json");
52943
+ const filePath = join14(stateDir, "missed-approvals.json");
52754
52944
  const EMPTY = () => ({ pending: [], delivered: [] });
52755
52945
  let unreadable = false;
52756
52946
  function read() {
@@ -52830,7 +53020,7 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
52830
53020
  },
52831
53021
  clear() {
52832
53022
  try {
52833
- unlinkSync8(filePath);
53023
+ unlinkSync6(filePath);
52834
53024
  } catch {}
52835
53025
  }
52836
53026
  };
@@ -52838,8 +53028,8 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
52838
53028
 
52839
53029
  // gateway/always-allow-persist-queue.ts
52840
53030
  init_atomic();
52841
- import { unlinkSync as unlinkSync9 } from "node:fs";
52842
- import { join as join17 } from "node:path";
53031
+ import { unlinkSync as unlinkSync7 } from "node:fs";
53032
+ import { join as join15 } from "node:path";
52843
53033
  var MAX_ATTEMPTS = 5;
52844
53034
  var MAX_AGE_MS = 24 * 60 * 60 * 1000;
52845
53035
  var MAX_QUEUE_SIZE = 50;
@@ -52863,7 +53053,7 @@ var atomicWriteSeam = (path, data, opts) => {
52863
53053
  atomicWriteFileSync2(path, data, mode);
52864
53054
  };
52865
53055
  function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSeam, log = (l) => process.stderr.write(l)) {
52866
- const filePath = join17(stateDir, "always-allow-persist-queue.json");
53056
+ const filePath = join15(stateDir, "always-allow-persist-queue.json");
52867
53057
  let lock = Promise.resolve();
52868
53058
  function withLock(fn) {
52869
53059
  const result = lock.then(fn, fn);
@@ -52975,7 +53165,7 @@ function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSe
52975
53165
  },
52976
53166
  clear() {
52977
53167
  try {
52978
- unlinkSync9(filePath);
53168
+ unlinkSync7(filePath);
52979
53169
  } catch {}
52980
53170
  }
52981
53171
  };
@@ -53138,8 +53328,8 @@ function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
53138
53328
  }
53139
53329
 
53140
53330
  // gateway/approval-hold.ts
53141
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync12, readFileSync as readFileSync14, unlinkSync as unlinkSync10, chmodSync as chmodSync5, lstatSync as lstatSync2 } from "node:fs";
53142
- import { join as join18, dirname as dirname8 } from "node:path";
53331
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync8, chmodSync as chmodSync3, lstatSync as lstatSync2 } from "node:fs";
53332
+ import { join as join16, dirname as dirname6 } from "node:path";
53143
53333
  var BLOCKED_APPROVAL_FILE_MODE = 420;
53144
53334
  var BLOCKED_APPROVAL_DIR_MODE = 1023;
53145
53335
  var HELD_RETRY_BACKOFF_MS = 60000;
@@ -53168,24 +53358,24 @@ function safeActionForRecord(naturalAction2, toolName, inputPreview) {
53168
53358
  return ACTION_SAFE_WITH_INPUT2.has(toolName) ? naturalAction2(toolName, inputPreview) : naturalAction2(toolName, undefined);
53169
53359
  }
53170
53360
  function createBlockedApprovalStore(dir, agent, fallbackDir) {
53171
- const primary = join18(dir, `${agent}.json`);
53172
- const fallback = fallbackDir != null ? join18(fallbackDir, "blocked-approval.json") : null;
53361
+ const primary = join16(dir, `${agent}.json`);
53362
+ const fallback = fallbackDir != null ? join16(fallbackDir, "blocked-approval.json") : null;
53173
53363
  let active = primary;
53174
53364
  function tryWrite(target, body, dirMode) {
53175
- const parent = dirname8(target);
53365
+ const parent = dirname6(target);
53176
53366
  try {
53177
- mkdirSync15(parent, { recursive: true });
53367
+ mkdirSync13(parent, { recursive: true });
53178
53368
  try {
53179
53369
  if (lstatSync2(target).isSymbolicLink())
53180
53370
  return false;
53181
53371
  } catch {}
53182
53372
  if (dirMode != null) {
53183
53373
  try {
53184
- chmodSync5(parent, dirMode);
53374
+ chmodSync3(parent, dirMode);
53185
53375
  } catch {}
53186
53376
  }
53187
- writeFileSync12(target, body, { encoding: "utf-8", mode: BLOCKED_APPROVAL_FILE_MODE });
53188
- chmodSync5(target, BLOCKED_APPROVAL_FILE_MODE);
53377
+ writeFileSync10(target, body, { encoding: "utf-8", mode: BLOCKED_APPROVAL_FILE_MODE });
53378
+ chmodSync3(target, BLOCKED_APPROVAL_FILE_MODE);
53189
53379
  return true;
53190
53380
  } catch {
53191
53381
  return false;
@@ -53202,7 +53392,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53202
53392
  active = primary;
53203
53393
  if (fallback != null) {
53204
53394
  try {
53205
- unlinkSync10(fallback);
53395
+ unlinkSync8(fallback);
53206
53396
  } catch {}
53207
53397
  }
53208
53398
  return;
@@ -53221,7 +53411,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53221
53411
  if (p == null)
53222
53412
  continue;
53223
53413
  try {
53224
- unlinkSync10(p);
53414
+ unlinkSync8(p);
53225
53415
  } catch {}
53226
53416
  }
53227
53417
  active = primary;
@@ -53231,7 +53421,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53231
53421
  if (p == null)
53232
53422
  continue;
53233
53423
  try {
53234
- const parsed = JSON.parse(readFileSync14(p, "utf-8"));
53424
+ const parsed = JSON.parse(readFileSync12(p, "utf-8"));
53235
53425
  if (parsed != null && typeof parsed === "object")
53236
53426
  return parsed;
53237
53427
  } catch {}
@@ -53310,6 +53500,7 @@ function isTelegramSurfaceTool2(toolName) {
53310
53500
  }
53311
53501
 
53312
53502
  // tool-activity-summary.ts
53503
+ init_status_no_truncate();
53313
53504
  init_card_format();
53314
53505
  init_redact();
53315
53506
  function clipNarrative2(s) {
@@ -53528,6 +53719,7 @@ function createTurnTypingLoop(deps) {
53528
53719
 
53529
53720
  // gateway/handback-preturn-signal.ts
53530
53721
  var PRETURN_TURNKEY_PREFIX = "preturn:";
53722
+ var HANDBACK_REINJECT_COUNT_META_KEY = "handbackReinjectCount";
53531
53723
  function isHandbackInbound(msg) {
53532
53724
  return msg.type === "inbound" && msg.meta?.source === "subagent_handback";
53533
53725
  }
@@ -53535,6 +53727,8 @@ function createHandbackPreturnSignal(deps) {
53535
53727
  const now = deps.now ?? (() => Date.now());
53536
53728
  const debounceMs = deps.debounceMs ?? 700;
53537
53729
  const adoptTimeoutMs = deps.adoptTimeoutMs ?? 30000;
53730
+ const deliveredTtlMs = deps.deliveredTtlMs ?? 300000;
53731
+ const maxReinjects = deps.maxReinjects ?? 2;
53538
53732
  const setTimer = deps.setTimer ?? ((fn, ms) => {
53539
53733
  const t = setTimeout(fn, ms);
53540
53734
  t.unref?.();
@@ -53574,7 +53768,7 @@ function createHandbackPreturnSignal(deps) {
53574
53768
  if (messageId == null)
53575
53769
  return;
53576
53770
  if (entry.consumed) {
53577
- deps.finalizeCard({
53771
+ deps.deleteCard({
53578
53772
  turnKey: entry.syntheticTurnKey,
53579
53773
  chatId: entry.chatId,
53580
53774
  threadId: entry.threadId,
@@ -53607,12 +53801,15 @@ function createHandbackPreturnSignal(deps) {
53607
53801
  }
53608
53802
  deps.stopTypingLoop(entry.chatId, entry.threadId);
53609
53803
  }
53804
+ function deliveredAwaitingTurnStart(entry) {
53805
+ return now() - entry.deliveredAt < deliveredTtlMs;
53806
+ }
53610
53807
  function reap(entry) {
53611
53808
  entry.reapTimer = null;
53612
53809
  if (entry.consumed)
53613
53810
  return;
53614
- if (deps.isClaudeBusy?.() === true) {
53615
- log(`handback-preturn-signal: reap deferred key=${entry.statusKey} ` + `(claude busy \u2014 handback enqueued behind an in-flight turn, not orphaned)
53811
+ if (deps.isClaudeBusy?.() === true || deliveredAwaitingTurnStart(entry)) {
53812
+ log(`handback-preturn-signal: reap deferred key=${entry.statusKey} ` + `(${deps.isClaudeBusy?.() === true ? "claude busy" : "delivered, turn not yet streaming"}` + ` \u2014 not orphaned)
53616
53813
  `);
53617
53814
  entry.reapTimer = setTimer(() => reap(entry), adoptTimeoutMs);
53618
53815
  return;
@@ -53629,12 +53826,40 @@ function createHandbackPreturnSignal(deps) {
53629
53826
  pinned: entry.pinned
53630
53827
  };
53631
53828
  deps.clearCardRecord(entry.syntheticTurnKey, entry.activityMessageId);
53632
- Promise.resolve(deps.finalizeCard(record)).catch((err) => {
53633
- log(`handback-preturn-signal: orphan finalize failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
53829
+ Promise.resolve(deps.deleteCard(record)).catch((err) => {
53830
+ log(`handback-preturn-signal: orphan card delete failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
53634
53831
  `);
53635
53832
  });
53636
53833
  }
53637
53834
  dropEntry(entry);
53835
+ if (entry.reinjectCount < maxReinjects) {
53836
+ const nextCount = entry.reinjectCount + 1;
53837
+ entry.inbound.meta[HANDBACK_REINJECT_COUNT_META_KEY] = String(nextCount);
53838
+ log(`handback-preturn-signal: orphan reap key=${entry.statusKey} ` + `turnId=${entry.adoptTurnId} \u2014 re-injecting handback (attempt ${nextCount}/${maxReinjects})
53839
+ `);
53840
+ try {
53841
+ deps.reinjectHandback(entry.inbound);
53842
+ } catch (err) {
53843
+ log(`handback-preturn-signal: orphan re-inject failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
53844
+ `);
53845
+ }
53846
+ } else {
53847
+ log(`handback-preturn-signal: orphan reap key=${entry.statusKey} ` + `turnId=${entry.adoptTurnId} \u2014 re-injection cap (${maxReinjects}) exhausted, ` + `escalating to fleet-health telemetry
53848
+ `);
53849
+ try {
53850
+ deps.escalateOrphan({
53851
+ statusKey: entry.statusKey,
53852
+ chatId: entry.chatId,
53853
+ threadId: entry.threadId,
53854
+ adoptTurnId: entry.adoptTurnId,
53855
+ reinjectCount: entry.reinjectCount,
53856
+ ageMs: now() - entry.deliveredAt
53857
+ });
53858
+ } catch (err) {
53859
+ log(`handback-preturn-signal: orphan escalation failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
53860
+ `);
53861
+ }
53862
+ }
53638
53863
  }
53639
53864
  return {
53640
53865
  noteHandbackRelease(inbound) {
@@ -53661,6 +53886,9 @@ function createHandbackPreturnSignal(deps) {
53661
53886
  `);
53662
53887
  const startedAt = now();
53663
53888
  const syntheticTurnKey = `${PRETURN_TURNKEY_PREFIX}${statusKey}:${startedAt}`;
53889
+ const rawReinject = inbound.meta?.[HANDBACK_REINJECT_COUNT_META_KEY];
53890
+ const parsedReinject = rawReinject != null ? Number.parseInt(rawReinject, 10) : 0;
53891
+ const reinjectCount = Number.isFinite(parsedReinject) && parsedReinject > 0 ? parsedReinject : 0;
53664
53892
  const entry = {
53665
53893
  statusKey,
53666
53894
  chatId,
@@ -53668,6 +53896,9 @@ function createHandbackPreturnSignal(deps) {
53668
53896
  adoptTurnId,
53669
53897
  syntheticTurnKey,
53670
53898
  startedAt,
53899
+ deliveredAt: startedAt,
53900
+ reinjectCount,
53901
+ inbound,
53671
53902
  pinned: false,
53672
53903
  debounceTimer: null,
53673
53904
  reapTimer: null,
@@ -53750,6 +53981,24 @@ function createHandbackPreturnSignal(deps) {
53750
53981
  };
53751
53982
  }
53752
53983
 
53984
+ // gateway/handback-orphan-recovery.ts
53985
+ function formatOrphanEscalation(esc) {
53986
+ return `telegram gateway: handback orphan escalation key=${esc.statusKey} ` + `turnId=${esc.adoptTurnId} reinjects=${esc.reinjectCount} ageMs=${esc.ageMs} ` + `\u2014 deterministic recovery exhausted, no operator nudge issued
53987
+ `;
53988
+ }
53989
+ function createHandbackOrphanRecovery(deps) {
53990
+ const writeLog = deps.writeLog ?? ((line) => void process.stderr.write(line));
53991
+ return {
53992
+ deleteCard: (record) => deps.deleteMessage(record.chatId, record.activityMessageId, record.threadId).then(() => {
53993
+ return;
53994
+ }).catch(() => {
53995
+ return;
53996
+ }),
53997
+ reinjectHandback: (inbound) => deps.pushInbound(inbound),
53998
+ escalateOrphan: (esc) => writeLog(formatOrphanEscalation(esc))
53999
+ };
54000
+ }
54001
+
53753
54002
  // gateway/derive-turn-id.ts
53754
54003
  function deriveTurnId2(chatId, threadId, messageId) {
53755
54004
  if (messageId == null || messageId === "" || String(messageId) === "0")
@@ -63373,7 +63622,6 @@ function renderSafe(doc, source, maxLen = RICH_MESSAGE_MAX_CHARS) {
63373
63622
 
63374
63623
  // render/rich-render.ts
63375
63624
  init_format();
63376
- var PLAIN_TEXT_MAX_CHARS = 4096;
63377
63625
  function parseRichRenderEnabled(raw) {
63378
63626
  if (raw == null)
63379
63627
  return true;
@@ -63409,6 +63657,48 @@ function renderOutboundChunks(text4, env = process.env, maxLen = RICH_MESSAGE_MA
63409
63657
  return out.length > 0 ? out : [whole];
63410
63658
  }
63411
63659
 
63660
+ // reply-quote.ts
63661
+ var import_grammy6 = __toESM(require_mod2(), 1);
63662
+ var QUOTE_MAX_CHARS = 1024;
63663
+ function normalizeQuoteText(quoteText) {
63664
+ if (typeof quoteText !== "string")
63665
+ return null;
63666
+ if (quoteText.trim().length === 0)
63667
+ return null;
63668
+ if (quoteText.length <= QUOTE_MAX_CHARS)
63669
+ return quoteText;
63670
+ let end = QUOTE_MAX_CHARS;
63671
+ const code2 = quoteText.charCodeAt(end - 1);
63672
+ if (code2 >= 55296 && code2 <= 56319)
63673
+ end -= 1;
63674
+ return quoteText.slice(0, end);
63675
+ }
63676
+ function buildReplyParameters(messageId, quoteText) {
63677
+ const quote = normalizeQuoteText(quoteText);
63678
+ return { message_id: messageId, ...quote != null ? { quote } : {} };
63679
+ }
63680
+ function isQuoteRejectionError(err) {
63681
+ if (!(err instanceof import_grammy6.GrammyError) || err.error_code !== 400)
63682
+ return false;
63683
+ if (isHtmlParseRejectError(err) || isMessageTooLongError(err))
63684
+ return false;
63685
+ return (err.description || "").toLowerCase().includes("quote");
63686
+ }
63687
+ function sendOptsHaveQuote(opts) {
63688
+ const rp = opts.reply_parameters;
63689
+ return rp != null && typeof rp === "object" && rp.quote != null;
63690
+ }
63691
+ function dropQuoteFromSendOpts(opts) {
63692
+ if (!sendOptsHaveQuote(opts))
63693
+ return { ...opts };
63694
+ const rp = { ...opts.reply_parameters };
63695
+ delete rp.quote;
63696
+ delete rp.quote_parse_mode;
63697
+ delete rp.quote_entities;
63698
+ delete rp.quote_position;
63699
+ return { ...opts, reply_parameters: rp };
63700
+ }
63701
+
63412
63702
  // stream-controller.ts
63413
63703
  function createStreamController(cfg) {
63414
63704
  const {
@@ -63436,13 +63726,10 @@ function createStreamController(cfg) {
63436
63726
  ...disableLinkPreview ? { link_preview_options: { is_disabled: true } } : {},
63437
63727
  ...replyMarkup != null ? { reply_markup: replyMarkup } : {}
63438
63728
  };
63439
- const sendOpts = {
63729
+ let sendOpts = {
63440
63730
  ...baseOpts,
63441
63731
  ...replyToMessageId != null ? {
63442
- reply_parameters: {
63443
- message_id: replyToMessageId,
63444
- ...quoteText != null ? { quote: { text: quoteText, position: 0 } } : {}
63445
- }
63732
+ reply_parameters: buildReplyParameters(replyToMessageId, quoteText)
63446
63733
  } : {},
63447
63734
  ...protectContent === true ? { protect_content: true } : {},
63448
63735
  ...cfg.disableNotification === true ? { disable_notification: true } : {}
@@ -63459,6 +63746,17 @@ function createStreamController(cfg) {
63459
63746
  delete richOpts.link_preview_options;
63460
63747
  return bot.api.sendRichMessage(chatId, richMessage(piece.text), richOpts);
63461
63748
  };
63749
+ const sendPieceWithQuoteFallback = async (piece) => {
63750
+ try {
63751
+ return await sendPiece(piece, sendOpts);
63752
+ } catch (err) {
63753
+ if (!isQuoteRejectionError(err) || !sendOptsHaveQuote(sendOpts))
63754
+ throw err;
63755
+ warn?.(`stream-controller: quote not found in the replied-to message \u2014 re-sending without the quote (${err instanceof Error ? err.message : String(err)})`);
63756
+ sendOpts = dropQuoteFromSendOpts(sendOpts);
63757
+ return await sendPiece(piece, sendOpts);
63758
+ }
63759
+ };
63462
63760
  const editPiece = (id, piece, opts) => {
63463
63761
  if (!piece.rich)
63464
63762
  return bot.api.editMessageText(chatId, id, piece.text, opts);
@@ -63501,7 +63799,7 @@ function createStreamController(cfg) {
63501
63799
  return false;
63502
63800
  }
63503
63801
  try {
63504
- const sent = await retry(() => sendPiece(piece, sendOpts), { threadId, chat_id: chatId });
63802
+ const sent = await retry(() => sendPieceWithQuoteFallback(piece), { threadId, chat_id: chatId });
63505
63803
  tailIds[ti] = sent.message_id;
63506
63804
  tailLastText[ti] = piece.text;
63507
63805
  onSend?.(sent.message_id, piece.text.length);
@@ -63523,7 +63821,7 @@ function createStreamController(cfg) {
63523
63821
  const head = pieces[0];
63524
63822
  let anchorId;
63525
63823
  try {
63526
- const sent = await retry(() => sendPiece(head, sendOpts), { threadId, chat_id: chatId });
63824
+ const sent = await retry(() => sendPieceWithQuoteFallback(head), { threadId, chat_id: chatId });
63527
63825
  anchorId = sent.message_id;
63528
63826
  } catch (err) {
63529
63827
  if (!literalText && head.rich && isParseEntitiesError(err)) {
@@ -63708,7 +64006,42 @@ function createChatLock() {
63708
64006
  }
63709
64007
 
63710
64008
  // retry-api-call.ts
63711
- var import_grammy6 = __toESM(require_mod2(), 1);
64009
+ var import_grammy7 = __toESM(require_mod2(), 1);
64010
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
64011
+ var attemptStore2 = new AsyncLocalStorage4;
64012
+ function withAttemptContext2(ctx, fn) {
64013
+ return attemptStore2.run(ctx, fn);
64014
+ }
64015
+ function isTransientNetworkMessage2(msg) {
64016
+ return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND");
64017
+ }
64018
+ function isAbortError2(err) {
64019
+ const e = err;
64020
+ return e?.name === "AbortError" || e?.code === "ABORT_ERR";
64021
+ }
64022
+ function isTransientTransportError2(err) {
64023
+ if (err instanceof import_grammy7.GrammyError)
64024
+ return false;
64025
+ if (isLocalResourceError2(err))
64026
+ return false;
64027
+ if (err instanceof import_grammy7.HttpError) {
64028
+ const inner = err.error;
64029
+ if (isLocalResourceError2(inner))
64030
+ return false;
64031
+ if (isAbortError2(inner))
64032
+ return false;
64033
+ return true;
64034
+ }
64035
+ const msg = err instanceof Error ? err.message : String(err ?? "");
64036
+ if (isTransientNetworkMessage2(msg))
64037
+ return true;
64038
+ const cause = err?.cause;
64039
+ if (cause == null || cause === err)
64040
+ return false;
64041
+ if (isLocalResourceError2(cause))
64042
+ return false;
64043
+ return isTransientNetworkMessage2(cause instanceof Error ? cause.message : String(cause));
64044
+ }
63712
64045
  function classifyBenignTelegram4002(errorCode, description) {
63713
64046
  if (errorCode !== 400)
63714
64047
  return null;
@@ -63753,12 +64086,12 @@ function createRetryApiCall2(config = {}) {
63753
64086
  const observer = config.observer;
63754
64087
  const log = config.log;
63755
64088
  const onFloodWait = config.onFloodWait;
63756
- const floodWaitRemainingMs2 = config.floodWaitRemainingMs;
64089
+ const floodWaitRemainingMs = config.floodWaitRemainingMs;
63757
64090
  function openWindowMs() {
63758
- if (!floodWaitRemainingMs2)
64091
+ if (!floodWaitRemainingMs)
63759
64092
  return 0;
63760
64093
  try {
63761
- const ms = floodWaitRemainingMs2();
64094
+ const ms = floodWaitRemainingMs();
63762
64095
  return Number.isFinite(ms) && ms > 0 ? ms : 0;
63763
64096
  } catch {
63764
64097
  return 0;
@@ -63776,9 +64109,9 @@ function createRetryApiCall2(config = {}) {
63776
64109
  throw gated;
63777
64110
  }
63778
64111
  try {
63779
- return await fn();
64112
+ return await withAttemptContext2({ attempt, maxRetries, maxFloodSleepMs }, fn);
63780
64113
  } catch (err) {
63781
- const isGrammyErr = err instanceof import_grammy6.GrammyError;
64114
+ const isGrammyErr = err instanceof import_grammy7.GrammyError;
63782
64115
  const msg = err instanceof Error ? err.message : String(err);
63783
64116
  const desc = isGrammyErr ? err.description : msg;
63784
64117
  if (isLocalResourceError2(err)) {
@@ -63808,12 +64141,14 @@ function createRetryApiCall2(config = {}) {
63808
64141
  const benignKind = isGrammyErr ? classifyBenignTelegram4002(err.error_code, desc) : null;
63809
64142
  if (benignKind !== null) {
63810
64143
  observer?.onBenign?.({ kind: benignKind });
64144
+ if (opts?.rethrowBenign400)
64145
+ throw err;
63811
64146
  return;
63812
64147
  }
63813
64148
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
63814
64149
  throw Object.assign(new Error("THREAD_NOT_FOUND"), { original: err });
63815
64150
  }
63816
- if (!isGrammyErr && (msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND"))) {
64151
+ if (isTransientTransportError2(err)) {
63817
64152
  if (attempt < maxRetries - 1) {
63818
64153
  const delayMs = Math.pow(2, attempt) * 1000;
63819
64154
  log?.(`telegram gateway: network error, retrying in ${delayMs / 1000}s: ${msg}
@@ -65114,14 +65449,13 @@ function createFloodWindowObserver(config) {
65114
65449
  }
65115
65450
 
65116
65451
  // shared/bot-runtime.ts
65117
- var import_grammy7 = __toESM(require_mod2(), 1);
65452
+ var import_grammy8 = __toESM(require_mod2(), 1);
65118
65453
  var import_runner2 = __toESM(require_mod4(), 1);
65119
65454
  import { createHash as createHash3 } from "crypto";
65120
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
65121
- init_flood_circuit_breaker();
65455
+ import { AsyncLocalStorage as AsyncLocalStorage5 } from "async_hooks";
65122
65456
  init_format();
65123
65457
  init_rich_send();
65124
- var tgPostTagStore2 = new AsyncLocalStorage3;
65458
+ var tgPostTagStore2 = new AsyncLocalStorage5;
65125
65459
  function _getTgPostTags() {
65126
65460
  return tgPostTagStore2.getStore();
65127
65461
  }
@@ -65164,16 +65498,27 @@ function installTgPostLogger(bot) {
65164
65498
  if (r != null && typeof r === "object" && r.ok === false) {
65165
65499
  const code2 = r.error_code;
65166
65500
  const benign = classifyBenignTelegram400(code2, r.description);
65167
- emit(benign !== null ? "benign" : "err", `telegram_${code2 ?? "unknown"}`, code2 != null ? String(code2) : "-", shortDesc(r.description));
65501
+ const retrying = benign === null && willRetryTelegramFailure({
65502
+ errorCode: code2 ?? null,
65503
+ retryAfterSec: r.parameters?.retry_after ?? null,
65504
+ message: r.description ?? null
65505
+ });
65506
+ emit(benign !== null ? "benign" : retrying ? "retry" : "err", `telegram_${code2 ?? "unknown"}`, code2 != null ? String(code2) : "-", shortDesc(r.description));
65168
65507
  return res;
65169
65508
  }
65170
65509
  emit("ok", "-", "-", "-");
65171
65510
  return res;
65172
65511
  } catch (err) {
65173
- const errClass = err instanceof import_grammy7.GrammyError ? `grammy_${err.error_code}` : err?.constructor?.name ?? "Error";
65174
- const code2 = err instanceof import_grammy7.GrammyError ? String(err.error_code) : "-";
65175
- const rawDesc = err instanceof import_grammy7.GrammyError ? err.description : err instanceof Error ? err.message : "";
65176
- emit("err", errClass, code2, shortDesc(rawDesc));
65512
+ const errClass = err instanceof import_grammy8.GrammyError ? `grammy_${err.error_code}` : err?.constructor?.name ?? "Error";
65513
+ const code2 = err instanceof import_grammy8.GrammyError ? String(err.error_code) : "-";
65514
+ const rawDesc = err instanceof import_grammy8.GrammyError ? err.description : err instanceof Error ? err.message : "";
65515
+ const retrying = willRetryTelegramFailure({
65516
+ errorCode: err instanceof import_grammy8.GrammyError ? err.error_code : null,
65517
+ retryAfterSec: err instanceof import_grammy8.GrammyError ? err.parameters?.retry_after ?? null : null,
65518
+ message: err instanceof Error ? err.message : String(err ?? ""),
65519
+ cause: err
65520
+ });
65521
+ emit(retrying ? "retry" : "err", errClass, code2, shortDesc(rawDesc));
65177
65522
  throw err;
65178
65523
  }
65179
65524
  });
@@ -69887,11 +70232,11 @@ class PostHogBackendClient extends PostHogCoreStateless {
69887
70232
  }
69888
70233
 
69889
70234
  // ../node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/context/context.mjs
69890
- import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
70235
+ import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
69891
70236
 
69892
70237
  class PostHogContext {
69893
70238
  constructor() {
69894
- this.storage = new AsyncLocalStorage4;
70239
+ this.storage = new AsyncLocalStorage6;
69895
70240
  }
69896
70241
  get() {
69897
70242
  return this.storage.getStore();
@@ -76344,9 +76689,7 @@ function splitMarkdownChunks2(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
76344
76689
  } else if (spaceIdx > 0) {
76345
76690
  cut = spaceIdx;
76346
76691
  }
76347
- cut = backOffOpenFence2(rest, cut);
76348
- cut = backOffTableRow2(rest, cut);
76349
- cut = backOffOpenInline2(rest, cut);
76692
+ cut = safeMarkdownCut2(rest, cut);
76350
76693
  if (cut <= 0) {
76351
76694
  const sliced = hardSliceToCap2(rest, maxLen);
76352
76695
  chunks.push(stripBoundarySpacers2(sliced[0], "trailing"));
@@ -76399,8 +76742,29 @@ var INLINE_SPAN_PATTERNS2 = [
76399
76742
  /\*\*[^*\n]+\*\*/g,
76400
76743
  /__[^_\n]+__/g,
76401
76744
  /(?<![\w*])_[^_\n]+_(?![\w*])/g,
76402
- /\[[^\]\n]*\]\([^)\n]*\)/g
76745
+ /\[[^\]\n]*\]\([^)\n]*\)/g,
76746
+ /~~[^~\n]+~~/g,
76747
+ /\|\|[^|\n]+\|\|/g
76403
76748
  ];
76749
+ var DELIMITER_RUN_CHARS2 = new Set(["*", "_", "`", "~", "|"]);
76750
+ function safeMarkdownCut2(text4, cut) {
76751
+ if (cut <= 0)
76752
+ return 0;
76753
+ if (cut >= text4.length)
76754
+ return text4.length;
76755
+ let safe = backOffOpenFence2(text4, cut);
76756
+ safe = backOffTableRow2(text4, safe);
76757
+ safe = backOffOpenInline2(text4, safe);
76758
+ while (safe > 0 && DELIMITER_RUN_CHARS2.has(text4[safe]) && text4[safe - 1] === text4[safe])
76759
+ safe--;
76760
+ if (safe <= 0)
76761
+ return 0;
76762
+ const prev = text4.charCodeAt(safe - 1);
76763
+ const here = text4.charCodeAt(safe);
76764
+ if (prev >= 55296 && prev <= 56319 && here >= 56320 && here <= 57343)
76765
+ safe -= 1;
76766
+ return safe;
76767
+ }
76404
76768
  function backOffOpenInline2(text4, cut) {
76405
76769
  if (cut <= 0 || cut >= text4.length)
76406
76770
  return cut;
@@ -76424,7 +76788,7 @@ function backOffOpenInline2(text4, cut) {
76424
76788
 
76425
76789
  // gateway/command-format.ts
76426
76790
  init_format();
76427
- var import_grammy8 = __toESM(require_mod2(), 1);
76791
+ var import_grammy9 = __toESM(require_mod2(), 1);
76428
76792
  function formatSwitchroomOutput(output, maxLen = RICH_MESSAGE_MAX_CHARS) {
76429
76793
  const trimmed = output.trim();
76430
76794
  if (trimmed.length <= maxLen)
@@ -76492,7 +76856,7 @@ function formatAuthOutputForTelegram(output) {
76492
76856
  `), url };
76493
76857
  }
76494
76858
  function buildAuthUrlKeyboard(authorizeUrl) {
76495
- return new import_grammy8.InlineKeyboard().url("\uD83D\uDD10 Open Claude auth", authorizeUrl);
76859
+ return new import_grammy9.InlineKeyboard().url("\uD83D\uDD10 Open Claude auth", authorizeUrl);
76496
76860
  }
76497
76861
  function buildDeferredSecretKeyboard(deferKey) {
76498
76862
  const unlockData = `vd:unlock:${deferKey}`;
@@ -76502,7 +76866,7 @@ function buildDeferredSecretKeyboard(deferKey) {
76502
76866
  `);
76503
76867
  throw new Error(`callback_data overflow: deferKey too long (${deferKey.length} chars)`);
76504
76868
  }
76505
- return new import_grammy8.InlineKeyboard().text("\uD83D\uDD13 Unlock vault & save", unlockData).text("\uD83D\uDDD1 Discard", cancelData);
76869
+ return new import_grammy9.InlineKeyboard().text("\uD83D\uDD13 Unlock vault & save", unlockData).text("\uD83D\uDDD1 Discard", cancelData);
76506
76870
  }
76507
76871
  function renderVaultOpFailure(verbLabel, cliOutput, key) {
76508
76872
  const parsed = parseVaultCliError(cliOutput);
@@ -76538,7 +76902,7 @@ _${escapeHtmlForTg2(outcome.paneTailText)}_` : "";
76538
76902
  }
76539
76903
  }
76540
76904
  function buildDoctorScopeKeyboard() {
76541
- return new import_grammy8.InlineKeyboard().text("\uD83E\uDE7A Whole fleet", "dr:fleet").text("\uD83E\uDE7A This agent", "dr:self");
76905
+ return new import_grammy9.InlineKeyboard().text("\uD83E\uDE7A Whole fleet", "dr:fleet").text("\uD83E\uDE7A This agent", "dr:self");
76542
76906
  }
76543
76907
  function formatDoctorReport(raw) {
76544
76908
  const trimmed = stripAnsi4(raw).trim();
@@ -76554,7 +76918,7 @@ init_emphasis_guard();
76554
76918
  init_line_start_guard();
76555
76919
  init_inline_pairs_guard();
76556
76920
  init_unsupported_token_guard();
76557
- var import_grammy9 = __toESM(require_mod2(), 1);
76921
+ var import_grammy10 = __toESM(require_mod2(), 1);
76558
76922
  function guardAccidentalFormatting2(markdown) {
76559
76923
  let out = markdown;
76560
76924
  out = guardUnsupportedTokens(out);
@@ -76811,7 +77175,7 @@ function normalizeTemporal(text4, tz, nowMs2) {
76811
77175
  }
76812
77176
 
76813
77177
  // gateway/outbound-send-path.ts
76814
- var import_grammy10 = __toESM(require_mod2(), 1);
77178
+ var import_grammy11 = __toESM(require_mod2(), 1);
76815
77179
  import { statSync as statSync11 } from "fs";
76816
77180
  import { extname } from "path";
76817
77181
 
@@ -76958,6 +77322,42 @@ import {
76958
77322
  } from "node:fs";
76959
77323
  import { join as join31 } from "node:path";
76960
77324
  import { homedir as homedir9 } from "node:os";
77325
+
77326
+ // hooks/audience-classify.mjs
77327
+ var AUDIENCE_USER = "user";
77328
+ var AUDIENCE_INTERNAL = "internal";
77329
+ function resolveRecordAudience(value) {
77330
+ return value === AUDIENCE_INTERNAL ? AUDIENCE_INTERNAL : AUDIENCE_USER;
77331
+ }
77332
+ function shouldSuppressForAudience(record, opts) {
77333
+ if (opts?.gateEnabled === false)
77334
+ return false;
77335
+ return resolveRecordAudience(record?.audience) === AUDIENCE_INTERNAL;
77336
+ }
77337
+ var REPLY_THROW_PROVENANCE_NOTICE = "(my reply tool errored this turn, so this was never sent as an answer \u2014 " + "the safety net captured the last thing I wrote, which may be working notes)";
77338
+ function shouldFrameReplyThrow(record, opts) {
77339
+ if (opts?.frameEnabled === false)
77340
+ return false;
77341
+ return record?.replyToolThrewThisTurn === true;
77342
+ }
77343
+ function applyReplyThrowFraming(text4) {
77344
+ const body = typeof text4 === "string" ? text4 : "";
77345
+ if (body.trim().length === 0)
77346
+ return body;
77347
+ return `${REPLY_THROW_PROVENANCE_NOTICE}
77348
+
77349
+ ${body}`;
77350
+ }
77351
+ function formatReplyThrowFraming(s) {
77352
+ return `telegram gateway: outbox provenance framing nonce=${s.turnNonce} ` + `turnId=${s.turnId ?? "unknown"} chatId=${s.chatId ?? "unresolved"} ` + `sha=${(s.textSha256 ?? "").slice(0, 12)} source=${s.source ?? "unknown"} ` + `audience=user replyToolThrew=true \u2014 captured prose delivered with its ` + `provenance stated, not suppressed
77353
+ `;
77354
+ }
77355
+ function formatInternalSuppression(s) {
77356
+ return `telegram gateway: outbox audience suppression nonce=${s.turnNonce} ` + `turnId=${s.turnId ?? "unknown"} chatId=${s.chatId ?? "unresolved"} ` + `sha=${(s.textSha256 ?? "").slice(0, 12)} ageMs=${s.ageMs ?? 0} ` + `source=${s.source ?? "unknown"} pastWindow=${s.pastWindow === true} ` + `audience=internal \u2014 internal prose withheld from chat, journaled terminally
77357
+ `;
77358
+ }
77359
+
77360
+ // outbox.ts
76961
77361
  function sha256Hex(s) {
76962
77362
  return createHash4("sha256").update(s, "utf8").digest("hex");
76963
77363
  }
@@ -77137,7 +77537,8 @@ function decideOutboxSweep(input) {
77137
77537
  routePrefix = "",
77138
77538
  quietMs = OUTBOX_QUIET_MS,
77139
77539
  maxAgeMs = OUTBOX_MAX_AGE_MS,
77140
- shownLedgerHit = false
77540
+ shownLedgerHit = false,
77541
+ provenanceFraming = true
77141
77542
  } = input;
77142
77543
  if (deliveredNonces.has(record.turnNonce))
77143
77544
  return { action: "skip-journaled" };
@@ -77152,7 +77553,13 @@ function decideOutboxSweep(input) {
77152
77553
  return { action: "skip-unroutable" };
77153
77554
  const delayed = age > maxAgeMs;
77154
77555
  const prefix = (delayed ? "(delayed) " : "") + routePrefix;
77155
- return { action: delayed ? "send-delayed" : "send", text: prefix + record.text };
77556
+ const framed = shouldFrameReplyThrow(record, { frameEnabled: provenanceFraming });
77557
+ const body = framed ? applyReplyThrowFraming(record.text) : record.text;
77558
+ return {
77559
+ action: delayed ? "send-delayed" : "send",
77560
+ text: prefix + body,
77561
+ ...framed && body !== record.text ? { framedProvenance: "reply-throw" } : {}
77562
+ };
77156
77563
  }
77157
77564
  function resolveOutboxChat(record, deps) {
77158
77565
  if (record.chatId != null && record.chatId !== "") {
@@ -77447,7 +77854,8 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
77447
77854
  tgMessageId: args.tgMessageId,
77448
77855
  ts: now,
77449
77856
  deliverySource: args.deliverySource ?? "reply-tool",
77450
- ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn }
77857
+ ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn },
77858
+ ...args.framedProvenance == null ? {} : { framedProvenance: args.framedProvenance }
77451
77859
  }, stateDir);
77452
77860
  clearOutboxRecord(nonce, stateDir);
77453
77861
  }
@@ -77502,6 +77910,7 @@ function queueFloodBlockedReply(input) {
77502
77910
  textSha256: sha256Hex(text4),
77503
77911
  createdAt,
77504
77912
  source: FLOOD_QUEUED_SOURCE,
77913
+ audience: "user",
77505
77914
  ...input.originChatId != null ? { originChatId: input.originChatId } : {},
77506
77915
  ...input.originThreadId != null ? { originThreadId: input.originThreadId } : {}
77507
77916
  };
@@ -78020,7 +78429,12 @@ function decideCapturedProseDelivery(args, deps) {
78020
78429
  if (deps?.backstopDeliveredNonceHit?.(state3.turnId) === true) {
78021
78430
  return { deliver: false, reason: "already-delivered" };
78022
78431
  }
78023
- return { deliver: true, text: text4, reason: "captured-prose" };
78432
+ return {
78433
+ deliver: true,
78434
+ text: text4,
78435
+ reason: "captured-prose",
78436
+ ...state3.replyToolThrewThisTurn === true ? { replyToolThrewThisTurn: true } : {}
78437
+ };
78024
78438
  }
78025
78439
  function settleCapturedProseDelivery(outcome, effects) {
78026
78440
  if (outcome === "failed") {
@@ -78157,10 +78571,23 @@ async function sendReplyChunks(deps, state3) {
78157
78571
  }
78158
78572
  const sendChunkPlainText = async (opts) => {
78159
78573
  const plain = chunks[i].length > 0 ? chunks[i] : "\u26a0\ufe0f (a fragment could not be rendered for Telegram)";
78160
- const sent = await deps.sendLiteralRaw(opts, plain);
78161
- sentIds.push(sent.message_id);
78162
- deps.logOutbound("reply", chatId, sent.message_id, plain.length, `chunk=${i + 1}/${chunks.length} plaintext-fallback`);
78163
- deps.stderr(`telegram gateway: markdown parse-reject \u2014 resent chunk ${i + 1}/${chunks.length} as plain text
78574
+ const pieces = splitPlainTextToCap(plain);
78575
+ const pieceOpts = (p) => {
78576
+ if (pieces.length === 1)
78577
+ return opts;
78578
+ const o = { ...opts };
78579
+ if (p !== pieces.length - 1)
78580
+ delete o.reply_markup;
78581
+ if (p !== 0)
78582
+ delete o.reply_parameters;
78583
+ return o;
78584
+ };
78585
+ for (let p = 0;p < pieces.length; p++) {
78586
+ const sent = await deps.sendLiteralRaw(pieceOpts(p), pieces[p]);
78587
+ sentIds.push(sent.message_id);
78588
+ deps.logOutbound("reply", chatId, sent.message_id, pieces[p].length, pieces.length > 1 ? `chunk=${i + 1}/${chunks.length} plaintext-fallback piece=${p + 1}/${pieces.length}` : `chunk=${i + 1}/${chunks.length} plaintext-fallback`);
78589
+ }
78590
+ deps.stderr(`telegram gateway: markdown parse-reject \u2014 resent chunk ${i + 1}/${chunks.length} as plain text` + (pieces.length > 1 ? ` in ${pieces.length} piece(s) (4096-char plain cap)` : "") + `
78164
78591
  `);
78165
78592
  };
78166
78593
  const sendChunk = (opts, wrapped) => {
@@ -78209,6 +78636,13 @@ async function sendReplyChunks(deps, state3) {
78209
78636
  else
78210
78637
  throw retryErr;
78211
78638
  }
78639
+ } else if (isQuoteRejectionError(err) && sendOptsHaveQuote(sendOpts)) {
78640
+ const retryOpts = dropQuoteFromSendOpts(sendOpts);
78641
+ const sent = await sendChunk(retryOpts, false);
78642
+ sentIds.push(sent.message_id);
78643
+ deps.logOutbound("reply", chatId, sent.message_id, chunks[i].length, `chunk=${i + 1}/${chunks.length} quote-dropped`);
78644
+ deps.stderr(`telegram gateway: quote not found in the replied-to message \u2014 resent chunk ${i + 1}/${chunks.length} without the quote
78645
+ `);
78212
78646
  } else if (isMessageTooLongError(err)) {
78213
78647
  await sendChunkResplit(sendOpts);
78214
78648
  } else if (isHtmlParseRejectError(err)) {
@@ -78221,7 +78655,7 @@ async function sendReplyChunks(deps, state3) {
78221
78655
  return { threadId, previewMessageId };
78222
78656
  }
78223
78657
  function classifyReadBackError(err) {
78224
- if (err instanceof import_grammy10.GrammyError && err.error_code === 400) {
78658
+ if (err instanceof import_grammy11.GrammyError && err.error_code === 400) {
78225
78659
  const d = (err.description || "").toLowerCase();
78226
78660
  if (d.includes("message to edit not found"))
78227
78661
  return "absent";
@@ -78776,10 +79210,7 @@ ${url}`;
78776
79210
  const shouldReplyTo = reply_to != null && replyMode !== "off" && (replyMode === "all" || i === 0);
78777
79211
  return {
78778
79212
  ...shouldReplyTo ? {
78779
- reply_parameters: {
78780
- message_id: reply_to,
78781
- ...quoteText != null ? { quote: { text: quoteText, position: 0 } } : {}
78782
- }
79213
+ reply_parameters: buildReplyParameters(reply_to, quoteText)
78783
79214
  } : {},
78784
79215
  ...tid != null ? { message_thread_id: tid } : {},
78785
79216
  ...disableLinkPreview ? { link_preview_options: { is_disabled: true } } : {},
@@ -78838,7 +79269,7 @@ ${url}`;
78838
79269
  opts.message_thread_id = tid;
78839
79270
  else
78840
79271
  delete opts.message_thread_id;
78841
- return lockedBot.api.sendVoice(chat_id, new import_grammy10.InputFile(Buffer.from(oggBytes)), opts);
79272
+ return lockedBot.api.sendVoice(chat_id, new import_grammy11.InputFile(Buffer.from(oggBytes)), opts);
78842
79273
  }, { threadId, chat_id, verb: "sendVoice" });
78843
79274
  sentIds.push(sentVoice.message_id);
78844
79275
  logOutbound("reply", chat_id, sentVoice.message_id, voiceOutPlan?.ttsChunks[v]?.length ?? 0, `voice-note=${v + 1}/${voiceOggs.length}`);
@@ -78911,12 +79342,12 @@ ${url}`;
78911
79342
  ...replyParams,
78912
79343
  ...tid != null ? { message_thread_id: tid } : {}
78913
79344
  };
78914
- return lockedBot.api.sendDocument(chat_id, new import_grammy10.InputFile(f), baseOpts);
79345
+ return lockedBot.api.sendDocument(chat_id, new import_grammy11.InputFile(f), baseOpts);
78915
79346
  }, { threadId, chat_id, verb: "sendDocument" });
78916
79347
  if (allPhotos) {
78917
79348
  const media = files.map((f) => ({
78918
79349
  type: "photo",
78919
- media: new import_grammy10.InputFile(f)
79350
+ media: new import_grammy11.InputFile(f)
78920
79351
  }));
78921
79352
  let sent;
78922
79353
  try {
@@ -78949,7 +79380,7 @@ ${url}`;
78949
79380
  sentIds.push(m.message_id);
78950
79381
  } else {
78951
79382
  for (const f of files) {
78952
- const input = new import_grammy10.InputFile(f);
79383
+ const input = new import_grammy11.InputFile(f);
78953
79384
  const isPhoto = sendableAsPhoto(f);
78954
79385
  let sent;
78955
79386
  try {
@@ -79047,10 +79478,11 @@ async function deliverCapturedProse(deps, args) {
79047
79478
  } = deps;
79048
79479
  const { chatId, threadId, statusKeyStr, registryKey, originTurnId, text: text4, turnDurationMs } = args;
79049
79480
  const now = Date.now();
79481
+ const framed = shouldFrameReplyThrow({ replyToolThrewThisTurn: args.replyToolThrewThisTurn }, { frameEnabled: process.env.SWITCHROOM_TG_OUTBOX_PROVENANCE_FRAMING !== "0" });
79050
79482
  let outcome;
79051
79483
  const already = outboundDedup.check(chatId, threadId, text4, now, registryKey);
79052
79484
  if (already == null) {
79053
- let out = normalizeParagraphBreaks(repairEscapedWhitespace(text4));
79485
+ let out = normalizeParagraphBreaks(repairEscapedWhitespace(framed ? applyReplyThrowFraming(text4) : text4));
79054
79486
  out = redactOutboundText(out, "captured_prose");
79055
79487
  const chunks = splitMarkdownChunks(out, RICH_MESSAGE_MAX_CHARS);
79056
79488
  const sentIds = [];
@@ -79080,9 +79512,24 @@ async function deliverCapturedProse(deps, args) {
79080
79512
  } catch {}
79081
79513
  }
79082
79514
  outboundDedup.record(chatId, threadId, text4, now, registryKey);
79083
- journalExternalDelivery({ turnNonce: originTurnId, text: text4, tgMessageId: sentIds[sentIds.length - 1], replyAlreadyDeliveredThisTurn: false });
79515
+ journalExternalDelivery({
79516
+ turnNonce: originTurnId,
79517
+ text: text4,
79518
+ tgMessageId: sentIds[sentIds.length - 1],
79519
+ replyAlreadyDeliveredThisTurn: false,
79520
+ ...framed ? { framedProvenance: "reply-throw" } : {}
79521
+ });
79084
79522
  process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from ` + `transcript scan (chat=${chatId} origin=${originTurnId})
79085
79523
  `);
79524
+ if (framed) {
79525
+ process.stderr.write(formatReplyThrowFraming({
79526
+ turnNonce: originTurnId,
79527
+ turnId: originTurnId,
79528
+ chatId,
79529
+ textSha256: sha256Hex(text4),
79530
+ source: "captured-prose-bridge"
79531
+ }));
79532
+ }
79086
79533
  outcome = "sent";
79087
79534
  } catch (err) {
79088
79535
  process.stderr.write(`telegram gateway: captured-prose delivery failed: ${err.message} \u2014 ` + `arming the silent-end re-prompt net (recordUndeliveredTurnEnd) so the ` + `answer is recoverable (chat=${chatId} origin=${originTurnId})
@@ -79120,7 +79567,7 @@ async function deliverCapturedProse(deps, args) {
79120
79567
  process.stderr.write(`telegram gateway: WARN captured-prose exhausted-boundary fallback \u2014 rich send ` + `failed with the re-prompt budget already spent; attempting a plain-text ` + `delivery of the recovered answer before the generic apology ` + `(chat=${chatId} origin=${originTurnId})
79121
79568
  `);
79122
79569
  const plain = redactOutboundText(text4, "captured_prose");
79123
- const plainChunks = splitMarkdownChunks(plain, RICH_MESSAGE_MAX_CHARS);
79570
+ const plainChunks = splitPlainTextToCap(plain);
79124
79571
  try {
79125
79572
  let liveThreadId = threadId;
79126
79573
  const plainSentIds = [];
@@ -79527,7 +79974,7 @@ class LivenessTracker {
79527
79974
  // gateway/outbound-send-path.ts
79528
79975
  init_format();
79529
79976
  init_text_voice_scrub();
79530
- var import_grammy11 = __toESM(require_mod2(), 1);
79977
+ var import_grammy12 = __toESM(require_mod2(), 1);
79531
79978
  init_format();
79532
79979
  init_rich_send();
79533
79980
  function normalizeOutboundBody2(rawText, site, redact2, opts = {}) {
@@ -80388,6 +80835,25 @@ function discardParkedTurnStart(rawContent) {
80388
80835
  }
80389
80836
  return null;
80390
80837
  }
80838
+ function drainParkedTurnStartsForChat(deps, chatId, threadId) {
80839
+ const wantThread = envThreadIdNum(threadId);
80840
+ const drained = [];
80841
+ for (let i = 0;i < parkedTurnStarts.length; ) {
80842
+ const entry = parkedTurnStarts[i];
80843
+ if (entry.chatId === chatId && envThreadIdNum(entry.threadId) === wantThread) {
80844
+ drained.push(entry);
80845
+ parkedTurnStarts.splice(i, 1);
80846
+ } else {
80847
+ i++;
80848
+ }
80849
+ }
80850
+ for (const env of drained) {
80851
+ process.stderr.write(`telegram gateway: parked-turn-start drained on silence-fallback ` + `chat=${env.chatId ?? "-"} thread=${envThreadIdNum(env.threadId) ?? "-"} ` + `msg=${env.messageId ?? "-"}
80852
+ `);
80853
+ beginTurn(deps, env);
80854
+ }
80855
+ return drained;
80856
+ }
80391
80857
  async function openQueuedCard(deps, chatId, threadId, replyToMessageId) {
80392
80858
  try {
80393
80859
  const sent = await deps.robustApiCall(() => deps.bot.api.sendRichMessage(chatId, richMessage(QUEUED_CARD_HTML), {
@@ -81381,6 +81847,7 @@ function handleSessionEvent(deps, ev) {
81381
81847
  registryKey: turn.registryKey ?? null,
81382
81848
  originTurnId: turn.turnId,
81383
81849
  text: proseDecision.text,
81850
+ replyToolThrewThisTurn: proseDecision.replyToolThrewThisTurn === true,
81384
81851
  turnDurationMs
81385
81852
  });
81386
81853
  } else {
@@ -82901,7 +83368,7 @@ async function loadFromAuthBroker(options = {}) {
82901
83368
  }
82902
83369
 
82903
83370
  // gateway/folder-picker-handler.ts
82904
- var import_grammy12 = __toESM(require_mod2(), 1);
83371
+ var import_grammy13 = __toESM(require_mod2(), 1);
82905
83372
 
82906
83373
  // ../src/drive/folder-picker.ts
82907
83374
  var DRIVE_ID_RE2 = /^[A-Za-z0-9_-]+$/;
@@ -83220,7 +83687,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
83220
83687
  const confirmText = `\u2705 Granted ${deps.agentName} access to folder ${parsed.folder_id}
83221
83688
  ` + `Scope: \`${scope}\`
83222
83689
  ` + `Revoke with: \`/approvals revoke ${decisionId}\``;
83223
- const kb = new import_grammy12.InlineKeyboard().url("\uD83D\uDCD6 Open in Drive", `https://drive.google.com/drive/folders/${parsed.folder_id}`);
83690
+ const kb = new import_grammy13.InlineKeyboard().url("\uD83D\uDCD6 Open in Drive", `https://drive.google.com/drive/folders/${parsed.folder_id}`);
83224
83691
  try {
83225
83692
  await ctx.editMessageText({ markdown: confirmText }, {
83226
83693
  reply_markup: kb
@@ -83229,7 +83696,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
83229
83696
  await ctx.answerCallbackQuery({ text: "Allowed" });
83230
83697
  }
83231
83698
  function toKeyboard(spec) {
83232
- const kb = new import_grammy12.InlineKeyboard;
83699
+ const kb = new import_grammy13.InlineKeyboard;
83233
83700
  for (let r = 0;r < spec.rows.length; r++) {
83234
83701
  const row = spec.rows[r];
83235
83702
  for (const btn of row) {
@@ -84127,10 +84594,6 @@ Allowed: \`${deps.escapeHtml(allow)}\``, { html: true });
84127
84594
 
84128
84595
  // gateway/model-command.ts
84129
84596
  var MODEL_ALIASES2 = ["opus", "sonnet", "haiku", "fable", "default"];
84130
- var CLAUDE_MODEL_ALIASES2 = {
84131
- opus48: "claude-opus-4-8",
84132
- "opus-4-8": "claude-opus-4-8"
84133
- };
84134
84597
  var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
84135
84598
  function isValidModelArg2(arg) {
84136
84599
  return MODEL_ARG_RE2.test(arg);
@@ -84289,7 +84752,7 @@ function resolveStaleAwareBusy(input) {
84289
84752
  var PERSIST_NOTE3 = "_A `/model` switch relaunches the session (~30s) on the chosen model. Session-only \u2014 reverts to the configured `model:` on the next restart. `/model default` reverts now. Live scrollback is replaced by a fresh session; memory and the handoff briefing carry the context. To change the default permanently, set `model:` in switchroom.yaml._";
84290
84753
  function claudeAliasHints2(prefix) {
84291
84754
  const byTarget = new Map;
84292
- for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES2)) {
84755
+ for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES)) {
84293
84756
  const spellings = byTarget.get(target) ?? [];
84294
84757
  spellings.push(alias);
84295
84758
  byTarget.set(target, spellings);
@@ -84297,7 +84760,7 @@ function claudeAliasHints2(prefix) {
84297
84760
  return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
84298
84761
  }
84299
84762
  function helpText4(deps, reason) {
84300
- const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`${a}\``).join(" \u00b7 ");
84763
+ const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
84301
84764
  const claudeAliasExamples = claudeAliasHints2("");
84302
84765
  const lines = [];
84303
84766
  if (reason)
@@ -84312,7 +84775,7 @@ async function handleModelCommand2(parsed, deps) {
84312
84775
  if (parsed.kind === "show") {
84313
84776
  const configured = deps.getConfiguredModel();
84314
84777
  const shown = configured && configured.length > 0 ? configured : "default";
84315
- const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`/model ${a}\``).join(" \u00b7 ");
84778
+ const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`/model ${a}\``).join(" \u00b7 ");
84316
84779
  return {
84317
84780
  text: [
84318
84781
  `**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
@@ -84330,7 +84793,7 @@ async function handleModelCommand2(parsed, deps) {
84330
84793
  if (!isValidModelArg2(parsed.model)) {
84331
84794
  return helpText4(deps, `not a valid model name: ${parsed.model}`);
84332
84795
  }
84333
- const model = expandModelAlias2(parsed.model);
84796
+ const model = expandModelAlias(parsed.model);
84334
84797
  if (deps.isBusy()) {
84335
84798
  return {
84336
84799
  text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
@@ -84357,10 +84820,10 @@ function relaunchErrorReply2(deps, model, err) {
84357
84820
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
84358
84821
  }
84359
84822
  function unvalidatedIdCaveat2(deps, model) {
84360
- const lower = canonicalModelToken2(model).toLowerCase();
84823
+ const lower = canonicalModelToken(model).toLowerCase();
84361
84824
  if (!lower.startsWith("claude-"))
84362
84825
  return null;
84363
- if (Object.values(CLAUDE_MODEL_ALIASES2).includes(lower))
84826
+ if (Object.values(CLAUDE_MODEL_ALIASES).includes(lower))
84364
84827
  return null;
84365
84828
  return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
84366
84829
  }
@@ -84445,45 +84908,17 @@ var SR_MODEL_LABELS2 = {
84445
84908
  "sr-gpt-5.6-terra": "GPT-5.6 Terra",
84446
84909
  "sr-gpt-5.6-luna": "GPT-5.6 Luna"
84447
84910
  };
84448
- var SR_MODEL_ALIASES2 = {
84449
- flash: "sr-gemini-2.5-flash",
84450
- gemini: "sr-gemini-2.5-pro",
84451
- deepseek: "sr-deepseek-v3",
84452
- r1: "sr-deepseek-r1",
84453
- glm: "sr-glm-5",
84454
- codex: "sr-codex-5.5",
84455
- grok: "sr-grok-4.5",
84456
- kimi: "sr-kimi-k3",
84457
- gpt: "sr-gpt-5.6-sol",
84458
- sol: "sr-gpt-5.6-sol",
84459
- terra: "sr-gpt-5.6-terra",
84460
- luna: "sr-gpt-5.6-luna"
84461
- };
84462
84911
  function isOfflineTrustedModelToken(token) {
84463
84912
  const lower = token.toLowerCase();
84464
84913
  if (MODEL_ALIASES2.includes(lower))
84465
84914
  return true;
84466
- if (lower in SR_MODEL_ALIASES2)
84915
+ if (lower in SR_MODEL_ALIASES)
84467
84916
  return true;
84468
- if (Object.values(SR_MODEL_ALIASES2).includes(lower))
84917
+ if (Object.values(SR_MODEL_ALIASES).includes(lower))
84469
84918
  return true;
84470
- if (lower in CLAUDE_MODEL_ALIASES2)
84919
+ if (lower in CLAUDE_MODEL_ALIASES)
84471
84920
  return true;
84472
- return Object.values(CLAUDE_MODEL_ALIASES2).includes(lower);
84473
- }
84474
- function expandSrAlias2(arg) {
84475
- return SR_MODEL_ALIASES2[arg.toLowerCase()] ?? arg;
84476
- }
84477
- function canonicalModelToken2(arg) {
84478
- const trimmed = arg.trim();
84479
- const lower = trimmed.toLowerCase();
84480
- return lower.startsWith("claude-") ? lower : trimmed;
84481
- }
84482
- function expandClaudeAlias2(arg) {
84483
- return CLAUDE_MODEL_ALIASES2[arg.trim().toLowerCase()] ?? arg;
84484
- }
84485
- function expandModelAlias2(arg) {
84486
- return canonicalModelToken2(expandSrAlias2(expandClaudeAlias2(arg)));
84921
+ return Object.values(CLAUDE_MODEL_ALIASES).includes(lower);
84487
84922
  }
84488
84923
  function srFriendlyLabel2(srName) {
84489
84924
  return SR_MODEL_LABELS2[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
@@ -84563,7 +84998,7 @@ function headerRow2(label) {
84563
84998
  return [{ text: label, callback_data: MODEL_CALLBACK_HEADER2 }];
84564
84999
  }
84565
85000
  function externalModelNames2(discovered) {
84566
- const set = new Set(Object.values(SR_MODEL_ALIASES2));
85001
+ const set = new Set(Object.values(SR_MODEL_ALIASES));
84567
85002
  for (const n of discovered) {
84568
85003
  if (isSrModel2(n))
84569
85004
  set.add(n);
@@ -84681,7 +85116,7 @@ async function handleModelMenuCallback(data, deps) {
84681
85116
  if (!isValidModelArg2(token)) {
84682
85117
  return { answer: "Invalid model name", reply: await buildModelMenu2(deps) };
84683
85118
  }
84684
- token = expandModelAlias2(token);
85119
+ token = expandModelAlias(token);
84685
85120
  if (!isRecognizedSwitchToken(token)) {
84686
85121
  return { answer: "Model list changed \u2014 menu refreshed", reply: await buildModelMenu2(deps) };
84687
85122
  }
@@ -85337,7 +85772,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
85337
85772
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
85338
85773
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
85339
85774
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
85340
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
85775
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1;
85776
+ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
85777
+ var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
85341
85778
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
85342
85779
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
85343
85780
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
@@ -85383,8 +85820,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
85383
85820
  String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
85384
85821
  ],
85385
85822
  [
85386
- "HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS",
85387
- String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS)
85823
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
85824
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
85388
85825
  ],
85389
85826
  [
85390
85827
  "HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
@@ -85401,6 +85838,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
85401
85838
  [
85402
85839
  "HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
85403
85840
  String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
85841
+ ],
85842
+ [
85843
+ "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
85844
+ String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
85404
85845
  ]
85405
85846
  ];
85406
85847
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
@@ -85421,22 +85862,41 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
85421
85862
  String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
85422
85863
  ],
85423
85864
  ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
85424
- ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
85865
+ ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)],
85866
+ ["HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS", HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS]
85425
85867
  ];
85426
85868
  var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
85427
85869
  "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
85428
85870
  "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
85429
85871
  "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
85430
85872
  "HINDSIGHT_API_WORKER_MAX_SLOTS",
85431
- "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS"
85873
+ "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS",
85874
+ "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
85875
+ "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
85876
+ "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
85877
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
85432
85878
  ]);
85879
+ var HINDSIGHT_WORKER_SLOT_TYPES = [
85880
+ "consolidation",
85881
+ "file_convert_retain",
85882
+ "graph_maintenance",
85883
+ "import_documents",
85884
+ "refresh_mental_model",
85885
+ "retain"
85886
+ ];
85887
+ var HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES = new Map(HINDSIGHT_WORKER_SLOT_TYPES.map((type) => [
85888
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
85889
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
85890
+ ]));
85433
85891
  var HINDSIGHT_PERF_ENV_KEYS = new Set([
85434
85892
  ...[
85435
85893
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
85436
85894
  ...HINDSIGHT_PERF_DEFAULTS_GPU,
85437
85895
  ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
85438
85896
  ].map(([k]) => k),
85439
- ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
85897
+ ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
85898
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
85899
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
85440
85900
  ]);
85441
85901
 
85442
85902
  // ../src/setup/hindsight-context-budget.ts
@@ -85787,7 +86247,9 @@ function resolveMainModel(model) {
85787
86247
  return normalizeModelAlias(model);
85788
86248
  }
85789
86249
  function normalizeModelAlias(model) {
85790
- return model === "claude-fable-5" ? "fable" : model;
86250
+ if (model === "claude-fable-5")
86251
+ return "fable";
86252
+ return expandModelAlias(model);
85791
86253
  }
85792
86254
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
85793
86255
  var pendingBankOps = new Set;
@@ -89830,7 +90292,9 @@ function createIpcServer(options) {
89830
90292
  case "rollout_status_edit":
89831
90293
  if (onRolloutStatusEdit) {
89832
90294
  try {
89833
- onRolloutStatusEdit(client3, msg);
90295
+ Promise.resolve(onRolloutStatusEdit(client3, msg)).catch((err) => {
90296
+ log(`rollout_status_edit handler rejected (client=${client3.id}): ${err.message}`);
90297
+ });
89834
90298
  } catch (err) {
89835
90299
  log(`rollout_status_edit handler threw (client=${client3.id}): ${err.message}`);
89836
90300
  }
@@ -90016,6 +90480,58 @@ function createIpcServer(options) {
90016
90480
  return ipcServer;
90017
90481
  }
90018
90482
 
90483
+ // gateway/rollout-status-edit.ts
90484
+ var GONE_PATTERNS = [
90485
+ /message to edit not found/i,
90486
+ /message can't be edited/i,
90487
+ /message_id_invalid/i,
90488
+ /message to be edited not found/i
90489
+ ];
90490
+ var NOT_MODIFIED = /message is not modified/i;
90491
+ function classifyRolloutEditError(err) {
90492
+ const e = err;
90493
+ const text4 = [
90494
+ typeof e?.description === "string" ? e.description : "",
90495
+ typeof e?.message === "string" ? e.message : ""
90496
+ ].join(" ");
90497
+ if (NOT_MODIFIED.test(text4))
90498
+ return "not-modified";
90499
+ if (GONE_PATTERNS.some((re) => re.test(text4)))
90500
+ return "gone";
90501
+ return "transient";
90502
+ }
90503
+ async function handleRolloutStatusEdit(deps, client3, msg) {
90504
+ const reply = (ok3, extra = {}) => {
90505
+ try {
90506
+ client3.send({ type: "rollout_status_edited", requestId: msg.requestId, ok: ok3, ...extra });
90507
+ } catch {}
90508
+ };
90509
+ const self = deps.selfAgentName;
90510
+ if (self && msg.agentName !== self) {
90511
+ deps.log(`rollout_status_edit rejected \u2014 agent mismatch (${msg.agentName} != ${self})`);
90512
+ reply(false, { gone: false, reason: "agent mismatch" });
90513
+ return;
90514
+ }
90515
+ const operator = deps.operatorChatId();
90516
+ if (operator === undefined) {
90517
+ deps.log("rollout_status_edit \u2014 no operator chat (allowFrom empty)");
90518
+ reply(false, { gone: false, reason: "no operator chat" });
90519
+ return;
90520
+ }
90521
+ try {
90522
+ await deps.editMessage(operator, msg.messageId, msg.text);
90523
+ reply(true);
90524
+ } catch (err) {
90525
+ const cls = classifyRolloutEditError(err);
90526
+ if (cls === "not-modified") {
90527
+ reply(true);
90528
+ return;
90529
+ }
90530
+ deps.log(`rollout_status_edit failed (${cls}) request=${msg.requestId} message_id=${msg.messageId}: ${err.message}`);
90531
+ reply(false, { gone: cls === "gone", reason: err.message });
90532
+ }
90533
+ }
90534
+
90019
90535
  // ../src/drive/diff-preview.ts
90020
90536
  function buildDiffPreview(input) {
90021
90537
  validateInput(input);
@@ -90463,7 +90979,7 @@ async function handleRequestMs365Approval(client3, msg, deps) {
90463
90979
 
90464
90980
  // gateway/diff-preview-card.ts
90465
90981
  init_format();
90466
- var import_grammy13 = __toESM(require_mod2(), 1);
90982
+ var import_grammy14 = __toESM(require_mod2(), 1);
90467
90983
  var REQUEST_ID_RE = /^[0-9a-f]{32}$/;
90468
90984
  var PENDING_FILE_ID_SENTINEL = "pending-create";
90469
90985
  function buildDiffPreviewCard(input) {
@@ -90481,7 +90997,7 @@ function buildDiffPreviewCard(input) {
90481
90997
  }
90482
90998
  const text4 = bodyLines.join(`
90483
90999
  `);
90484
- const kb = new import_grammy13.InlineKeyboard;
91000
+ const kb = new import_grammy14.InlineKeyboard;
90485
91001
  const ROW_BREAK_AFTER = [
90486
91002
  "apply_suggestion"
90487
91003
  ];
@@ -90964,6 +91480,23 @@ function loadObligations(path3, fs3) {
90964
91480
  return [];
90965
91481
  return env.obligations.filter(isObligationRow).map(sanitizeCapturedDelivery);
90966
91482
  }
91483
+ function removeUnmaintainedSnapshot(path3, fs3, reason = "persistence disabled", log = (l) => process.stderr.write(l)) {
91484
+ const unlink = fs3.unlinkSync;
91485
+ if (unlink == null)
91486
+ return false;
91487
+ try {
91488
+ if (!fs3.existsSync(path3))
91489
+ return false;
91490
+ unlink(path3);
91491
+ log(`obligation-store: persistence is off (${reason}) \u2014 removed unmaintained ` + `snapshot path=${path3} so it cannot be read as "nobody is waiting" (#4146)
91492
+ `);
91493
+ return true;
91494
+ } catch (err) {
91495
+ log(`obligation-store: could not remove unmaintained snapshot path=${path3}: ` + `${err.message} \u2014 audience classification falls back to the ` + `reader-side ledger-off guard (#4146)
91496
+ `);
91497
+ return false;
91498
+ }
91499
+ }
90967
91500
  function persistObligations(path3, fs3, snapshot, log = (l) => process.stderr.write(l)) {
90968
91501
  const env = { v: 1, obligations: [...snapshot] };
90969
91502
  const tmp = path3 + ".tmp";
@@ -93615,6 +94148,7 @@ function buildSilencePokeOptions(deps) {
93615
94148
  endCurrentTurnForKey,
93616
94149
  getPendingInboundBuffer,
93617
94150
  trackRedeliveredInbound,
94151
+ drainParkedTurnStarts,
93618
94152
  closeActivityLane,
93619
94153
  closeProgressLane,
93620
94154
  hangRestart
@@ -93808,7 +94342,8 @@ function buildSilencePokeOptions(deps) {
93808
94342
  } catch {}
93809
94343
  const fbSelfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
93810
94344
  const fbRedeliver = redeliverBufferedInbound2(getPendingInboundBuffer(), fbSelfAgent, (m) => sendToAgent(fbSelfAgent, m), getInboundSpool(), trackRedeliveredInbound);
93811
- process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${tearsDownLiveTurn} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
94345
+ const fbDrainedParked = drainParkedTurnStarts?.(fbChatId, fbThreadId ?? null) ?? 0;
94346
+ process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${tearsDownLiveTurn} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbDrainedParked > 0 ? ` drained_parked=${fbDrainedParked}` : ""}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
93812
94347
  `);
93813
94348
  }
93814
94349
  };
@@ -97492,6 +98027,7 @@ function readAndFilter(raw, filters, limit) {
97492
98027
  }
97493
98028
 
97494
98029
  // gateway/update-announce.ts
98030
+ init_status_no_truncate();
97495
98031
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
97496
98032
  function readLastTerminalUpdateAudit(opts = {}) {
97497
98033
  const path3 = opts.auditLogPath ?? defaultAuditLogPath();
@@ -97552,7 +98088,10 @@ function renderUpdateOutcomeLine(entry) {
97552
98088
  const stderrTail = (entry.stderr_tail ?? entry.error ?? "").slice(-400);
97553
98089
  const opStep = entry.op;
97554
98090
  const hint = recoveryHint(entry.install_context?.install_type);
97555
- const lines = [`\u274c update failed at ${opStep}: ${stderrTail || "(no stderr captured)"}`, ` \u21b3 Recovery: ${hint}`];
98091
+ const lines = [
98092
+ `\u274c update failed at ${opStep}: ${stderrTail || "(no stderr captured)"}`,
98093
+ `${NESTED_PREFIX}Recovery: ${hint}`
98094
+ ];
97556
98095
  return lines.join(`
97557
98096
  `);
97558
98097
  }
@@ -99881,8 +100420,37 @@ async function sweepOutbox(deps) {
99881
100420
  return summary;
99882
100421
  const deliveredNonces = readDeliveredNonces(deps.stateDir);
99883
100422
  const records = pending.map((fileName) => readOutboxRecord(fileName, deps.stateDir)).filter((r) => r != null).sort((a, b) => a.createdAt !== b.createdAt ? a.createdAt - b.createdAt : a.turnNonce < b.turnNonce ? -1 : a.turnNonce > b.turnNonce ? 1 : 0);
100423
+ const audienceGateEnabled = deps.audienceGateEnabled?.() ?? process.env.SWITCHROOM_TG_OUTBOX_AUDIENCE_GATE !== "0";
100424
+ const escalateInternalSuppression = deps.escalateInternalSuppression ?? log;
100425
+ const provenanceFramingEnabled = deps.provenanceFramingEnabled?.() ?? process.env.SWITCHROOM_TG_OUTBOX_PROVENANCE_FRAMING !== "0";
100426
+ const logProvenanceFraming = deps.logProvenanceFraming ?? log;
99884
100427
  for (const record2 of records) {
99885
100428
  summary.scanned++;
100429
+ if (shouldSuppressForAudience(record2, { gateEnabled: audienceGateEnabled })) {
100430
+ summary.skipped++;
100431
+ summary.audienceSuppressed = (summary.audienceSuppressed ?? 0) + 1;
100432
+ const ageMs = now - record2.createdAt;
100433
+ appendDelivered({
100434
+ turnNonce: record2.turnNonce,
100435
+ textSha256: record2.textSha256,
100436
+ ts: now,
100437
+ deliverySource: "sweep",
100438
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true,
100439
+ audience: AUDIENCE_INTERNAL,
100440
+ suppressedAudience: AUDIENCE_INTERNAL
100441
+ }, deps.stateDir);
100442
+ clearOutboxRecord(record2.turnNonce, deps.stateDir);
100443
+ escalateInternalSuppression(formatInternalSuppression({
100444
+ turnNonce: record2.turnNonce,
100445
+ turnId: turnIdFromNonce(record2.turnNonce),
100446
+ chatId: record2.chatId,
100447
+ textSha256: record2.textSha256,
100448
+ ageMs,
100449
+ source: record2.source,
100450
+ pastWindow: ageMs > OUTBOX_MAX_AGE_MS
100451
+ }));
100452
+ continue;
100453
+ }
99886
100454
  const resolved = resolveOutboxChat(record2, {
99887
100455
  registryChainLookup: (anchorContent) => {
99888
100456
  const taskId = extractTaskId(anchorContent);
@@ -99900,7 +100468,8 @@ async function sweepOutbox(deps) {
99900
100468
  routable: resolved != null,
99901
100469
  routePrefix,
99902
100470
  quietMs: deps.quietMs ?? OUTBOX_QUIET_MS,
99903
- shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir)
100471
+ shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir),
100472
+ provenanceFraming: provenanceFramingEnabled
99904
100473
  });
99905
100474
  if (decision.action !== "send" && decision.action !== "send-delayed") {
99906
100475
  summary.skipped++;
@@ -99943,8 +100512,19 @@ async function sweepOutbox(deps) {
99943
100512
  tgMessageId: sendResult.messageId,
99944
100513
  ts: now,
99945
100514
  deliverySource: "sweep",
99946
- replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true
100515
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true,
100516
+ ...decision.framedProvenance != null ? { framedProvenance: decision.framedProvenance } : {}
99947
100517
  }, deps.stateDir);
100518
+ if (decision.framedProvenance != null) {
100519
+ summary.provenanceFramed = (summary.provenanceFramed ?? 0) + 1;
100520
+ logProvenanceFraming(formatReplyThrowFraming({
100521
+ turnNonce: record2.turnNonce,
100522
+ turnId: turnIdFromNonce(record2.turnNonce),
100523
+ chatId: resolvedChat.chatId,
100524
+ textSha256: record2.textSha256,
100525
+ source: record2.source
100526
+ }));
100527
+ }
99948
100528
  if (deps.recordOutbound != null && sendResult.chunks.length > 0) {
99949
100529
  try {
99950
100530
  deps.recordOutbound(resolvedChat.chatId, resolvedChat.threadId, sendResult.chunks.map((c) => c.messageId), sendResult.chunks.map((c) => c.text));
@@ -99964,6 +100544,9 @@ async function sweepOutbox(deps) {
99964
100544
  }
99965
100545
  return summary;
99966
100546
  }
100547
+ function turnIdFromNonce(nonce) {
100548
+ return /^-?\d+:[^#]*#\d+$/.test(nonce) ? nonce : null;
100549
+ }
99967
100550
  function probeFloodWindow(probe) {
99968
100551
  if (probe == null)
99969
100552
  return 0;
@@ -100004,15 +100587,29 @@ function createSweepBackoff(cfg = {}) {
100004
100587
  failures: () => failures
100005
100588
  };
100006
100589
  }
100007
- async function sendChunkRich(bot, chatId, chunk2, opts) {
100008
- try {
100009
- return await bot.api.sendRichMessage(chatId, richMessage(chunk2), opts);
100010
- } catch (err) {
100011
- if (isParseEntitiesError(err)) {
100012
- return await bot.api.sendMessage(chatId, chunk2, opts);
100590
+ function newChunkSendState() {
100591
+ return { parseRejected: false, landed: [] };
100592
+ }
100593
+ async function sendChunkRich(bot, chatId, chunk2, opts, state7 = newChunkSendState()) {
100594
+ if (!state7.parseRejected) {
100595
+ try {
100596
+ const sent = await bot.api.sendRichMessage(chatId, richMessage(chunk2), opts);
100597
+ return [{ message_id: sent?.message_id, text: chunk2 }];
100598
+ } catch (err) {
100599
+ if (!isParseEntitiesError(err))
100600
+ throw err;
100601
+ state7.parseRejected = true;
100013
100602
  }
100014
- throw err;
100015
100603
  }
100604
+ const pieces = splitPlainTextToCap(chunk2);
100605
+ const withoutMarkup = { ...opts };
100606
+ delete withoutMarkup.reply_markup;
100607
+ for (let p = state7.landed.length;p < pieces.length; p++) {
100608
+ const isFinalPiece = p === pieces.length - 1;
100609
+ const sent = await bot.api.sendMessage(chatId, pieces[p], isFinalPiece ? opts : withoutMarkup);
100610
+ state7.landed.push({ message_id: sent?.message_id, text: pieces[p] });
100611
+ }
100612
+ return state7.landed;
100016
100613
  }
100017
100614
  function createOutboxSend(deps) {
100018
100615
  return async (chatId, threadId, text4) => {
@@ -100027,14 +100624,16 @@ function createOutboxSend(deps) {
100027
100624
  for (let idx = 0;idx < chunks.length; idx++) {
100028
100625
  const chunk2 = chunks[idx];
100029
100626
  const isLast = idx === chunks.length - 1;
100627
+ const chunkState = newChunkSendState();
100030
100628
  const res = await retryWithThreadFallback(deps.retry, (tid) => {
100031
100629
  const base = tid != null ? { message_thread_id: tid } : {};
100032
100630
  const opts = isLast && replyMarkup != null ? { ...base, reply_markup: replyMarkup } : base;
100033
- return sendChunkRich(bot, chatId, chunk2, opts);
100631
+ return sendChunkRich(bot, chatId, chunk2, opts, chunkState);
100034
100632
  }, { threadId: threadId ?? undefined, chat_id: chatId, verb: "outbox-sweep.sendMessage" });
100035
- const id = res?.message_id;
100036
- if (id != null)
100037
- landed.push({ messageId: id, text: chunk2 });
100633
+ for (const sent of res ?? []) {
100634
+ if (sent.message_id != null)
100635
+ landed.push({ messageId: sent.message_id, text: sent.text });
100636
+ }
100038
100637
  }
100039
100638
  return {
100040
100639
  messageId: landed.length > 0 ? landed[landed.length - 1].messageId : undefined,
@@ -100042,6 +100641,54 @@ function createOutboxSend(deps) {
100042
100641
  };
100043
100642
  };
100044
100643
  }
100644
+ function createOutboxSweepTick(deps) {
100645
+ const now = deps.now ?? (() => Date.now());
100646
+ let lastDeferLogAt = 0;
100647
+ let inFlight = false;
100648
+ let overlapSkips = 0;
100649
+ const tick3 = () => {
100650
+ if (deps.getBot() == null)
100651
+ return;
100652
+ if (!deps.backoff.ready(now()))
100653
+ return;
100654
+ if (inFlight) {
100655
+ overlapSkips++;
100656
+ if (overlapSkips === 1) {
100657
+ deps.log?.(`outbox-sweep: tick skipped \u2014 the previous sweep is still in flight ` + `(single-flight guard; no concurrent sweep started)
100658
+ `);
100659
+ }
100660
+ return;
100661
+ }
100662
+ inFlight = true;
100663
+ deps.runSweep().then((summary) => {
100664
+ if (summary.floodDeferred === true) {
100665
+ const at = now();
100666
+ if (at - lastDeferLogAt >= OUTBOX_SWEEP_DEFER_LOG_INTERVAL_MS) {
100667
+ lastDeferLogAt = at;
100668
+ deps.log?.(`outbox-sweep: deferred \u2014 Telegram flood window still open for ` + `${Math.ceil((summary.floodRemainingMs ?? 0) / 1000)}s; not issuing any ` + `send (would feed the ban)
100669
+ `);
100670
+ }
100671
+ return;
100672
+ }
100673
+ if (summary.sendFailures > 0) {
100674
+ const delay = deps.backoff.noteFailure(now());
100675
+ deps.log?.(`outbox-sweep: ${summary.sendFailures} send failure(s) \u2014 backing off ` + `${Math.round(delay / 1000)}s before the next sweep (attempt ${deps.backoff.failures()})
100676
+ `);
100677
+ } else {
100678
+ deps.backoff.noteSuccess();
100679
+ }
100680
+ }).catch((err) => deps.log?.(`outbox-sweep: tick failed: ${err.message}
100681
+ `)).finally(() => {
100682
+ inFlight = false;
100683
+ if (overlapSkips > 0) {
100684
+ deps.log?.(`outbox-sweep: sweep finished; ${overlapSkips} tick(s) were skipped while it ran
100685
+ `);
100686
+ overlapSkips = 0;
100687
+ }
100688
+ });
100689
+ };
100690
+ return Object.assign(tick3, { inFlight: () => inFlight });
100691
+ }
100045
100692
  function startOutboxSweep(deps) {
100046
100693
  if (!deps.isGatewayMain || process.env.SWITCHROOM_TG_OUTBOX_DELIVERY === "0")
100047
100694
  return;
@@ -100058,15 +100705,11 @@ function startOutboxSweep(deps) {
100058
100705
  ...deps.resolveReplyMarkup != null ? { resolveReplyMarkup: deps.resolveReplyMarkup } : {}
100059
100706
  });
100060
100707
  const backoff = deps.backoff ?? createSweepBackoff();
100061
- let lastDeferLogAt = 0;
100062
- const tick3 = () => {
100063
- const bot = deps.getBot();
100064
- if (bot == null)
100065
- return;
100066
- const now = Date.now();
100067
- if (!backoff.ready(now))
100068
- return;
100069
- sweepOutbox({
100708
+ const tick3 = createOutboxSweepTick({
100709
+ getBot: deps.getBot,
100710
+ backoff,
100711
+ log: deps.log,
100712
+ runSweep: () => sweepOutbox({
100070
100713
  stateDir: deps.stateDir,
100071
100714
  log: deps.log,
100072
100715
  send,
@@ -100080,36 +100723,18 @@ function startOutboxSweep(deps) {
100080
100723
  const turnKey3 = resolveSubagentOriginTurnKey(db3, taskId);
100081
100724
  return turnKey3 == null ? null : chatFromTurnKey(turnKey3);
100082
100725
  }
100083
- }).then((summary) => {
100084
- if (summary.floodDeferred === true) {
100085
- const at = Date.now();
100086
- if (at - lastDeferLogAt >= OUTBOX_SWEEP_DEFER_LOG_INTERVAL_MS) {
100087
- lastDeferLogAt = at;
100088
- deps.log?.(`outbox-sweep: deferred \u2014 Telegram flood window still open for ` + `${Math.ceil((summary.floodRemainingMs ?? 0) / 1000)}s; not issuing any ` + `send (would feed the ban)
100089
- `);
100090
- }
100091
- return;
100092
- }
100093
- if (summary.sendFailures > 0) {
100094
- const delay = backoff.noteFailure(Date.now());
100095
- deps.log?.(`outbox-sweep: ${summary.sendFailures} send failure(s) \u2014 backing off ` + `${Math.round(delay / 1000)}s before the next sweep (attempt ${backoff.failures()})
100096
- `);
100097
- } else {
100098
- backoff.noteSuccess();
100099
- }
100100
- }).catch((err) => deps.log?.(`outbox-sweep: tick failed: ${err.message}
100101
- `));
100102
- };
100726
+ })
100727
+ });
100103
100728
  const timer3 = setInterval(tick3, OUTBOX_SWEEP_INTERVAL_MS);
100104
100729
  timer3.unref?.();
100105
100730
  return timer3;
100106
100731
  }
100107
100732
 
100108
100733
  // ../src/build-info.ts
100109
- var VERSION2 = "0.19.41";
100110
- var COMMIT_SHA = "e77ee9c0";
100111
- var COMMIT_DATE = "2026-07-31T09:46:10Z";
100112
- var LATEST_PR = 4066;
100734
+ var VERSION2 = "0.19.43";
100735
+ var COMMIT_SHA = "4aaf442f";
100736
+ var COMMIT_DATE = "2026-08-01T10:38:38Z";
100737
+ var LATEST_PR = 4159;
100113
100738
  var COMMITS_AHEAD_OF_TAG = 0;
100114
100739
 
100115
100740
  // gateway/boot-version.ts
@@ -100154,10 +100779,10 @@ function composeBootVersionString(inputs) {
100154
100779
  }
100155
100780
 
100156
100781
  // gateway/unhandled-rejection-policy.ts
100157
- var import_grammy14 = __toESM(require_mod2(), 1);
100782
+ var import_grammy15 = __toESM(require_mod2(), 1);
100158
100783
  function classifyRejection(err, opts = {}) {
100159
- const isGrammy = opts.isGrammyError != null ? opts.isGrammyError(err) : err instanceof import_grammy14.GrammyError;
100160
- const isHttp = opts.isHttpError != null ? opts.isHttpError(err) : err instanceof import_grammy14.HttpError;
100784
+ const isGrammy = opts.isGrammyError != null ? opts.isGrammyError(err) : err instanceof import_grammy15.GrammyError;
100785
+ const isHttp = opts.isHttpError != null ? opts.isHttpError(err) : err instanceof import_grammy15.HttpError;
100161
100786
  if (isHttp)
100162
100787
  return "log_only";
100163
100788
  if (err instanceof Error && err.message === FLOOD_WAIT_ACTIVE)
@@ -102807,7 +103432,8 @@ var obligationStoreFs = {
102807
103432
  renameSync: (a, b) => renameSync27(a, b),
102808
103433
  existsSync: (p) => existsSync57(p),
102809
103434
  fsyncFileSync: fsyncPathSync,
102810
- fsyncDirSync: fsyncPathSync
103435
+ fsyncDirSync: fsyncPathSync,
103436
+ unlinkSync: unlinkSync31
102811
103437
  };
102812
103438
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
102813
103439
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -102819,7 +103445,8 @@ if (isGatewayMain && !STATIC && OBLIGATION_LEDGER_ENABLED) {
102819
103445
  process.stderr.write(`telegram gateway: obligation-ledger hydrated ${restored.length} open obligation(s) from ${OBLIGATION_STORE_PATH}
102820
103446
  `);
102821
103447
  }
102822
- }
103448
+ } else if (isGatewayMain)
103449
+ removeUnmaintainedSnapshot(OBLIGATION_STORE_PATH, obligationStoreFs, `static=${STATIC} enabled=${OBLIGATION_LEDGER_ENABLED}`);
102823
103450
  var obligationEscalateInFlight = new Set;
102824
103451
  var capturedResume = createCapturedResumeDispatcher({
102825
103452
  deliverAnswer: (a) => deliverAnswer(a),
@@ -104068,7 +104695,7 @@ function wrapBootCardApi(threadId) {
104068
104695
  await lockedBot.api.editMessageText(cid, mid, richMessage2(text5), editOpts);
104069
104696
  return "edited";
104070
104697
  } catch (err) {
104071
- const desc = err instanceof import_grammy16.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
104698
+ const desc = err instanceof import_grammy17.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
104072
104699
  if (typeof desc === "string" && desc.toLowerCase().includes("not modified"))
104073
104700
  return "edited";
104074
104701
  return "gone";
@@ -105649,6 +106276,7 @@ function gatewayLivenessWiringDeps() {
105649
106276
  endCurrentTurnForKey,
105650
106277
  getPendingInboundBuffer: () => pendingInboundBuffer,
105651
106278
  trackRedeliveredInbound,
106279
+ drainParkedTurnStarts: (chatId, threadId) => drainParkedTurnStartsForChat(gatewayStreamRenderDeps(), chatId, threadId != null ? String(threadId) : null).length,
105652
106280
  closeActivityLane,
105653
106281
  closeProgressLane,
105654
106282
  hangRestart: {
@@ -105906,7 +106534,7 @@ if (isGatewayMain && inboundSpool != null) {
105906
106534
  }
105907
106535
  var pendingPermissionBuffer = createPendingPermissionBuffer();
105908
106536
  function buildPermissionActionRow(requestId, showAlways) {
105909
- const kb = new import_grammy16.InlineKeyboard().text("\u274C Deny", `perm:deny:${requestId}`).text("\u2705 Allow", `perm:allow:${requestId}`);
106537
+ const kb = new import_grammy17.InlineKeyboard().text("\u274C Deny", `perm:deny:${requestId}`).text("\u2705 Allow", `perm:allow:${requestId}`);
105910
106538
  if (showAlways)
105911
106539
  kb.text("\uD83D\uDD01 Always\u2026", `perm:always:${requestId}`);
105912
106540
  return kb;
@@ -106466,7 +107094,7 @@ if (isGatewayMain)
106466
107094
  }
106467
107095
  },
106468
107096
  postAttachment: async (args) => {
106469
- const input = new import_grammy16.InputFile(Buffer.from(args.content, "utf8"), args.filename);
107097
+ const input = new import_grammy17.InputFile(Buffer.from(args.content, "utf8"), args.filename);
106470
107098
  await robustApiCall(() => bot.api.sendDocument(args.chatId, input, {
106471
107099
  ...args.threadId !== undefined ? { message_thread_id: args.threadId } : {}
106472
107100
  }), {
@@ -106551,20 +107179,19 @@ if (isGatewayMain)
106551
107179
  } catch {}
106552
107180
  }
106553
107181
  },
106554
- onRolloutStatusEdit(_client, msg) {
106555
- const self = process.env.SWITCHROOM_AGENT_NAME;
106556
- if (self && msg.agentName !== self) {
106557
- process.stderr.write(`telegram gateway: rollout_status_edit rejected \u2014 agent mismatch (${msg.agentName} != ${self})
106558
- `);
106559
- return;
106560
- }
106561
- const operator = loadAccess().allowFrom[0];
106562
- if (operator === undefined) {
106563
- process.stderr.write(`telegram gateway: rollout_status_edit \u2014 no operator chat (allowFrom empty)
106564
- `);
106565
- return;
106566
- }
106567
- swallowingApiCall(() => bot.api.editMessageText(operator, msg.messageId, richMessage2(msg.text), {}), { chat_id: String(operator), verb: "rollout-status-edit" });
107182
+ onRolloutStatusEdit(client3, msg) {
107183
+ return handleRolloutStatusEdit({
107184
+ selfAgentName: process.env.SWITCHROOM_AGENT_NAME,
107185
+ operatorChatId: () => loadAccess().allowFrom[0],
107186
+ editMessage: (chatId, messageId, text5) => robustApiCall(() => bot.api.editMessageText(chatId, messageId, richMessage2(text5), {}), {
107187
+ chat_id: String(chatId),
107188
+ verb: "rollout-status-edit",
107189
+ priorityClass: "critical",
107190
+ rethrowBenign400: true
107191
+ }),
107192
+ log: (m) => process.stderr.write(`telegram gateway: ${m}
107193
+ `)
107194
+ }, client3, msg);
106568
107195
  },
106569
107196
  onInjectInbound(_client, msg) {
106570
107197
  if (!isCronInjectFire(msg.inbound.meta))
@@ -107264,7 +107891,7 @@ async function executeAskUser(rawArgs) {
107264
107891
  }
107265
107892
  }
107266
107893
  const askId = generateAskId();
107267
- const keyboard = new import_grammy16.InlineKeyboard;
107894
+ const keyboard = new import_grammy17.InlineKeyboard;
107268
107895
  for (let i = 0;i < args.options.length; i++) {
107269
107896
  keyboard.text(args.options[i], encodeAskCallback(askId, i));
107270
107897
  if (i < args.options.length - 1)
@@ -107912,7 +108539,6 @@ if (isGatewayMain && !STATIC && FEED_HEARTBEAT_ENABLED) {
107912
108539
  }
107913
108540
  var HANDBACK_PRETURN_ENABLED = !STATIC && process.env.SWITCHROOM_HANDBACK_PRETURN !== "0";
107914
108541
  var HANDBACK_PRETURN_HTML = "\uD83E\uDD1D Reading the worker\u2019s results\u2026";
107915
- var HANDBACK_PRETURN_ORPHAN_HTML = "\uD83E\uDD1D A background worker finished, but the handback never started \u2014 it may need a nudge.";
107916
108542
  async function openHandbackPreTurnCard(chatId, threadId) {
107917
108543
  if (STATIC)
107918
108544
  return null;
@@ -107932,24 +108558,23 @@ async function openHandbackPreTurnCard(chatId, threadId) {
107932
108558
  return null;
107933
108559
  }
107934
108560
  }
107935
- function finalizeHandbackPreTurnCard(record2) {
107936
- return robustApiCall(() => bot.api.editMessageText(record2.chatId, record2.activityMessageId, richMessage2(HANDBACK_PRETURN_ORPHAN_HTML), {}), {
107937
- chat_id: record2.chatId,
107938
- ...record2.threadId != null ? { threadId: record2.threadId } : {},
107939
- verb: "handback-preturn.orphan-finalize"
107940
- }).then(() => {
107941
- return;
107942
- }).catch(() => {
107943
- return;
107944
- });
107945
- }
108561
+ var handbackOrphanRecovery = createHandbackOrphanRecovery({
108562
+ deleteMessage: (chatId, messageId, threadId) => robustApiCall(() => bot.api.deleteMessage(chatId, messageId), {
108563
+ chat_id: chatId,
108564
+ ...threadId != null ? { threadId } : {},
108565
+ verb: "handback-preturn.orphan-delete"
108566
+ }),
108567
+ pushInbound: (inbound) => pendingInboundBuffer.push(process.env.SWITCHROOM_AGENT_NAME ?? "", inbound)
108568
+ });
107946
108569
  var handbackPreturnSignal = createHandbackPreturnSignal({
107947
108570
  chatKey: (chatId, threadId) => chatKey2(chatId, threadId),
107948
108571
  deriveTurnId: (chatId, threadId, messageId) => deriveTurnId2(chatId, threadId, messageId),
107949
108572
  startTypingLoop: (chatId, threadId) => startTurnTypingLoop(chatId, threadId),
107950
108573
  stopTypingLoop: (chatId, threadId) => stopTurnTypingLoop(chatId, threadId),
107951
108574
  openCard: openHandbackPreTurnCard,
107952
- finalizeCard: finalizeHandbackPreTurnCard,
108575
+ deleteCard: handbackOrphanRecovery.deleteCard,
108576
+ reinjectHandback: handbackOrphanRecovery.reinjectHandback,
108577
+ escalateOrphan: handbackOrphanRecovery.escalateOrphan,
107953
108578
  writeCardRecord: (record2) => {
107954
108579
  if (!activityCardPersistEnabled)
107955
108580
  return;
@@ -109718,7 +110343,7 @@ function buildModelDeps(restartCtx) {
109718
110343
  function modelMenuReplyMarkup(reply) {
109719
110344
  if (!reply.keyboard)
109720
110345
  return;
109721
- const kb = new import_grammy16.InlineKeyboard;
110346
+ const kb = new import_grammy17.InlineKeyboard;
109722
110347
  for (const row of reply.keyboard) {
109723
110348
  for (const btn of row)
109724
110349
  kb.text(btn.text, btn.callback_data);
@@ -109784,7 +110409,7 @@ function persistQueuedCommandForRestart(action) {
109784
110409
  return `\u21A9\uFE0F Couldn\u2019t verify \`${escapeHtmlForTg2(action.cmd.targetLabel || action.arg)}\` as a known model without the live session \u2014 it was NOT saved. Re-issue \`/model ${escapeHtmlForTg2(action.arg)}\` once the agent is back.`;
109785
110410
  }
109786
110411
  const configured = readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined);
109787
- writeSessionModelFile(agentDir, expandModelAlias2(action.arg), configured);
110412
+ writeSessionModelFile(agentDir, expandModelAlias(action.arg), configured);
109788
110413
  break;
109789
110414
  }
109790
110415
  case "clear-model":
@@ -109858,7 +110483,7 @@ function buildEffortDeps() {
109858
110483
  function effortMenuReplyMarkup(reply) {
109859
110484
  if (!reply.keyboard)
109860
110485
  return;
109861
- const kb = new import_grammy16.InlineKeyboard;
110486
+ const kb = new import_grammy17.InlineKeyboard;
109862
110487
  for (const row of reply.keyboard) {
109863
110488
  for (const btn of row)
109864
110489
  kb.text(btn.text, btn.callback_data);
@@ -111511,7 +112136,7 @@ _Google accounts are connected from the host CLI._`, { html: true });
111511
112136
  return;
111512
112137
  }
111513
112138
  const appNote = started.source === "default" ? "_Using switchroom's shipped Microsoft app._" : "_Using your configured Microsoft app._";
111514
- const keyboard = new import_grammy16.InlineKeyboard().url("\uD83D\uDD10 Sign in to Microsoft", started.device.verification_uri).row().text("\u2716 Cancel", `cn:cancel:${key}`);
112139
+ const keyboard = new import_grammy17.InlineKeyboard().url("\uD83D\uDD10 Sign in to Microsoft", started.device.verification_uri).row().text("\u2716 Cancel", `cn:cancel:${key}`);
111515
112140
  const sent = await ctx.replyWithRichMessage(richMessage2(`\uD83D\uDD17 **Connect a Microsoft account**
111516
112141
 
111517
112142
  ` + `1. Tap **Sign in to Microsoft** below.
@@ -111647,7 +112272,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111647
112272
  tz: process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ
111648
112273
  });
111649
112274
  if (reply.keyboard && reply.keyboard.length > 0) {
111650
- const kb = new import_grammy16.InlineKeyboard;
112275
+ const kb = new import_grammy17.InlineKeyboard;
111651
112276
  for (let i = 0;i < reply.keyboard.length; i++) {
111652
112277
  const row = reply.keyboard[i];
111653
112278
  for (const b of row) {
@@ -111789,7 +112414,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111789
112414
  }
111790
112415
  lines.push("");
111791
112416
  const denials = readRecentDenialsForAgent(targetAgent, 604800000, 5);
111792
- const denialKeyboard = new import_grammy16.InlineKeyboard;
112417
+ const denialKeyboard = new import_grammy17.InlineKeyboard;
111793
112418
  if (denials.length > 0) {
111794
112419
  lines.push("**Recent denials (last 7d):**");
111795
112420
  for (const d of denials) {
@@ -111837,7 +112462,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111837
112462
  byAgent.set(g.agent_slug, list3);
111838
112463
  }
111839
112464
  const lines = ["**\uD83D\uDCDC Active grants**", ""];
111840
- const keyboard = new import_grammy16.InlineKeyboard;
112465
+ const keyboard = new import_grammy17.InlineKeyboard;
111841
112466
  for (const [agentName3, agentGrants] of byAgent) {
111842
112467
  lines.push(`**${escapeHtmlForTg2(agentName3)}:**`);
111843
112468
  for (const g of agentGrants) {
@@ -112034,7 +112659,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
112034
112659
  if (ctx.chat?.type !== "private") {
112035
112660
  kbRows = kbRows.filter((row) => !row.some((b) => b.callbackData?.startsWith("auth:use:")));
112036
112661
  }
112037
- const keyboard = new import_grammy16.InlineKeyboard;
112662
+ const keyboard = new import_grammy17.InlineKeyboard;
112038
112663
  kbRows.forEach((row, ri) => {
112039
112664
  if (ri > 0)
112040
112665
  keyboard.row();
@@ -112386,7 +113011,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
112386
113011
  await ctx.answerCallbackQuery({ text: "No always-allow rule for this tool." }).catch(() => {});
112387
113012
  return;
112388
113013
  }
112389
- keyboard = new import_grammy16.InlineKeyboard().text("\u2190 Back", `perm:back:${request_id}`);
113014
+ keyboard = new import_grammy17.InlineKeyboard().text("\u2190 Back", `perm:back:${request_id}`);
112390
113015
  if (choices.specific)
112391
113016
  keyboard.text(choices.specific.buttonLabel, `perm:asn:${request_id}`);
112392
113017
  keyboard.text(`${choices.broad.buttonLabel} \u26A0\uFE0F`, `perm:asb:${request_id}`);
@@ -112689,7 +113314,7 @@ ${labelWithResume}` : labelWithResume,
112689
113314
  verb: "voice-ondemand.sendVoice",
112690
113315
  ...threadId != null ? { threadId } : {}
112691
113316
  }),
112692
- sendVoiceByUpload: (chatId, audio, sendOpts, threadId) => robustApiCall(() => bot2.api.sendVoice(chatId, new import_grammy16.InputFile(Buffer.from(audio)), sendOpts), {
113317
+ sendVoiceByUpload: (chatId, audio, sendOpts, threadId) => robustApiCall(() => bot2.api.sendVoice(chatId, new import_grammy17.InputFile(Buffer.from(audio)), sendOpts), {
112693
113318
  chat_id: chatId,
112694
113319
  verb: "voice-ondemand.sendVoice",
112695
113320
  ...threadId != null ? { threadId } : {}
@@ -112955,7 +113580,7 @@ async function initGatewayBot() {
112955
113580
  `);
112956
113581
  process.exit(1);
112957
113582
  }
112958
- bot = new import_grammy16.Bot(TOKEN);
113583
+ bot = new import_grammy17.Bot(TOKEN);
112959
113584
  installTgPostLogger(bot);
112960
113585
  installRichMarkdownGuard(bot);
112961
113586
  installEditFloodFuse(bot, {
@@ -113927,7 +114552,7 @@ async function startGateway() {
113927
114552
  await runnerHandle.task();
113928
114553
  return;
113929
114554
  } catch (err) {
113930
- if (err instanceof import_grammy16.GrammyError && err.error_code === 409) {
114555
+ if (err instanceof import_grammy17.GrammyError && err.error_code === 409) {
113931
114556
  const delay = Math.min(1000 * attempt, 15000);
113932
114557
  const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
113933
114558
  process.stderr.write(`telegram gateway: poll.409.detected attempt=${attempt} retry_in_ms=${delay} agent=${agentName3}