switchroom 0.19.42 → 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 (72) hide show
  1. package/dist/agent-scheduler/index.js +1 -1
  2. package/dist/auth-broker/index.js +32 -7
  3. package/dist/cli/notion-write-pretool.mjs +1 -1
  4. package/dist/cli/switchroom.js +1187 -669
  5. package/dist/host-control/main.js +205 -28
  6. package/dist/vault/approvals/kernel-server.js +32 -7
  7. package/dist/vault/broker/server.js +32 -7
  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 +1225 -614
  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/boot-card.ts +7 -2
  19. package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
  20. package/telegram-plugin/gateway/gateway.ts +49 -51
  21. package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
  22. package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
  23. package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
  24. package/telegram-plugin/gateway/ipc-server.ts +13 -4
  25. package/telegram-plugin/gateway/obligation-store.ts +55 -0
  26. package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
  27. package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
  28. package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
  29. package/telegram-plugin/gateway/stream-render.ts +5 -0
  30. package/telegram-plugin/gateway/update-announce.ts +11 -4
  31. package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
  32. package/telegram-plugin/hooks/audience-classify.mjs +364 -0
  33. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
  34. package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
  35. package/telegram-plugin/outbox.ts +103 -2
  36. package/telegram-plugin/render/rich-render.ts +10 -2
  37. package/telegram-plugin/reply-quote.ts +150 -0
  38. package/telegram-plugin/retry-api-call.ts +254 -11
  39. package/telegram-plugin/shared/bot-runtime.ts +69 -34
  40. package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
  41. package/telegram-plugin/silent-end.ts +34 -2
  42. package/telegram-plugin/status-no-truncate.ts +31 -16
  43. package/telegram-plugin/stream-controller.ts +44 -9
  44. package/telegram-plugin/stream-reply-handler.ts +5 -3
  45. package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
  46. package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
  47. package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
  48. package/telegram-plugin/tests/card-variants.golden.txt +9 -9
  49. package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
  50. package/telegram-plugin/tests/format-consistency.test.ts +160 -14
  51. package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
  52. package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
  53. package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
  54. package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
  55. package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
  56. package/telegram-plugin/tests/obligation-store.test.ts +51 -0
  57. package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
  58. package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
  59. package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
  60. package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
  61. package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
  62. package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
  63. package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
  64. package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
  65. package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
  66. package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
  67. package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
  68. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
  69. package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
  70. package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
  71. package/telegram-plugin/tool-activity-summary.ts +58 -30
  72. package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
@@ -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")
@@ -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
  }
@@ -22165,7 +21969,7 @@ var init_schema = __esm(() => {
22165
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."),
22166
21970
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
22167
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."),
22168
- 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).")
22169
21973
  });
22170
21974
  MicrosoftWorkspaceConfigSchema = exports_external.object({
22171
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)."),
@@ -22698,7 +22502,7 @@ var init_schema = __esm(() => {
22698
22502
  });
22699
22503
 
22700
22504
  // ../src/config/paths.ts
22701
- import { existsSync as existsSync4 } from "node:fs";
22505
+ import { existsSync as existsSync3 } from "node:fs";
22702
22506
  import { resolve as resolve3 } from "node:path";
22703
22507
  function home() {
22704
22508
  return process.env.HOME ?? "/root";
@@ -22714,7 +22518,7 @@ function resolveStatePath(fragment) {
22714
22518
  const h = home();
22715
22519
  const primary = resolve3(h, DEFAULT_STATE_DIR, fragment);
22716
22520
  const legacy = resolve3(h, LEGACY_STATE_DIR, fragment);
22717
- if (!existsSync4(primary) && existsSync4(legacy)) {
22521
+ if (!existsSync3(primary) && existsSync3(legacy)) {
22718
22522
  warnLegacyStateOnce(legacy);
22719
22523
  return legacy;
22720
22524
  }
@@ -22727,9 +22531,9 @@ function resolveDualPath(pathStr) {
22727
22531
  const absolute = resolve3(h, rest);
22728
22532
  if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
22729
22533
  const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
22730
- if (!existsSync4(absolute)) {
22534
+ if (!existsSync3(absolute)) {
22731
22535
  const legacy = resolve3(h, LEGACY_STATE_DIR, frag);
22732
- if (existsSync4(legacy)) {
22536
+ if (existsSync3(legacy)) {
22733
22537
  warnLegacyStateOnce(legacy);
22734
22538
  return legacy;
22735
22539
  }
@@ -22754,7 +22558,7 @@ var init_overlay_schema = __esm(() => {
22754
22558
  });
22755
22559
 
22756
22560
  // ../src/config/overlay-loader.ts
22757
- 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";
22758
22562
  import { basename as basename4, resolve as resolve4 } from "node:path";
22759
22563
  function deriveOverlayTitle(raw, fileName) {
22760
22564
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
@@ -22770,7 +22574,7 @@ function overlayDirFor(agentName, subdir) {
22770
22574
  return resolve4(base);
22771
22575
  }
22772
22576
  function listYamlFiles(dir) {
22773
- if (!existsSync5(dir))
22577
+ if (!existsSync4(dir))
22774
22578
  return [];
22775
22579
  let entries;
22776
22580
  try {
@@ -22818,7 +22622,7 @@ function applyAgentOverlays(config) {
22818
22622
  const merged = [...agentCfg.schedule ?? []];
22819
22623
  for (const file of files) {
22820
22624
  try {
22821
- const raw = readFileSync6(file, "utf-8");
22625
+ const raw = readFileSync4(file, "utf-8");
22822
22626
  const parsed = import_yaml.parse(raw);
22823
22627
  const doc = OverlayDocSchema.parse(parsed);
22824
22628
  const title = deriveOverlayTitle(raw, basename4(file));
@@ -22859,7 +22663,7 @@ function applyAgentOverlays(config) {
22859
22663
  const seen = new Set(merged);
22860
22664
  for (const file of skillFiles) {
22861
22665
  try {
22862
- const raw = readFileSync6(file, "utf-8");
22666
+ const raw = readFileSync4(file, "utf-8");
22863
22667
  const parsed = import_yaml.parse(raw);
22864
22668
  const doc = OverlayDocSchema.parse(parsed);
22865
22669
  for (const skillName of doc.skills ?? []) {
@@ -23329,7 +23133,7 @@ var init_timezone = __esm(() => {
23329
23133
  });
23330
23134
 
23331
23135
  // ../src/config/loader.ts
23332
- import { readFileSync as readFileSync7, existsSync as existsSync6 } from "node:fs";
23136
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
23333
23137
  import { homedir as homedir4 } from "node:os";
23334
23138
  import { resolve as resolve5 } from "node:path";
23335
23139
  function formatZodErrors(error) {
@@ -23396,7 +23200,7 @@ function findConfigFile(startDir) {
23396
23200
  resolve5(userDir, "clerk.yml")
23397
23201
  ].filter(Boolean);
23398
23202
  for (const path of searchPaths) {
23399
- if (existsSync6(path)) {
23203
+ if (existsSync5(path)) {
23400
23204
  return path;
23401
23205
  }
23402
23206
  }
@@ -23404,12 +23208,12 @@ function findConfigFile(startDir) {
23404
23208
  }
23405
23209
  function loadConfig(configPath) {
23406
23210
  const filePath = configPath ?? findConfigFile();
23407
- if (!existsSync6(filePath)) {
23211
+ if (!existsSync5(filePath)) {
23408
23212
  throw new ConfigError(`Config file not found: ${filePath}`);
23409
23213
  }
23410
23214
  let raw;
23411
23215
  try {
23412
- raw = readFileSync7(filePath, "utf-8");
23216
+ raw = readFileSync5(filePath, "utf-8");
23413
23217
  } catch (err) {
23414
23218
  throw new ConfigError(`Failed to read config file: ${filePath}`, [
23415
23219
  ` ${err.message}`
@@ -23523,12 +23327,12 @@ var init_flock = () => {};
23523
23327
  // ../src/vault/vault.ts
23524
23328
  import { randomBytes as randomBytes4, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
23525
23329
  import {
23526
- readFileSync as readFileSync8,
23330
+ readFileSync as readFileSync6,
23527
23331
  writeSync as writeSync2,
23528
- existsSync as existsSync7,
23529
- renameSync as renameSync7,
23530
- mkdirSync as mkdirSync9,
23531
- unlinkSync as unlinkSync5,
23332
+ existsSync as existsSync6,
23333
+ renameSync as renameSync6,
23334
+ mkdirSync as mkdirSync7,
23335
+ unlinkSync as unlinkSync3,
23532
23336
  fsyncSync as fsyncSync2,
23533
23337
  openSync as openSync2,
23534
23338
  closeSync as closeSync2,
@@ -23566,12 +23370,12 @@ function normalizeSecrets(raw) {
23566
23370
  return out;
23567
23371
  }
23568
23372
  function openVault(passphrase, vaultPath) {
23569
- if (!existsSync7(vaultPath)) {
23373
+ if (!existsSync6(vaultPath)) {
23570
23374
  throw new VaultError(`Vault file not found: ${vaultPath}`);
23571
23375
  }
23572
23376
  let vaultFile;
23573
23377
  try {
23574
- vaultFile = JSON.parse(readFileSync8(vaultPath, "utf8"));
23378
+ vaultFile = JSON.parse(readFileSync6(vaultPath, "utf8"));
23575
23379
  } catch {
23576
23380
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
23577
23381
  }
@@ -23616,16 +23420,16 @@ var init_vault = __esm(() => {
23616
23420
 
23617
23421
  // ../src/vault/resolver.ts
23618
23422
  import {
23619
- chmodSync as chmodSync4,
23423
+ chmodSync as chmodSync2,
23620
23424
  closeSync as closeSync3,
23621
- mkdirSync as mkdirSync10,
23425
+ mkdirSync as mkdirSync8,
23622
23426
  mkdtempSync,
23623
23427
  openSync as openSync3,
23624
23428
  rmSync as rmSync2,
23625
23429
  statSync as statSync6,
23626
23430
  writeSync as writeSync3
23627
23431
  } from "node:fs";
23628
- import { join as join8 } from "node:path";
23432
+ import { join as join6 } from "node:path";
23629
23433
  import { tmpdir } from "node:os";
23630
23434
  import { constants as fsConstants } from "node:fs";
23631
23435
  function isVaultReference(value) {
@@ -23677,13 +23481,13 @@ function materializationRoot() {
23677
23481
  return cachedRoot;
23678
23482
  const xdg = process.env.XDG_RUNTIME_DIR;
23679
23483
  if (xdg) {
23680
- const base = join8(xdg, "switchroom", "vault");
23681
- mkdirSync10(base, { recursive: true, mode: 448 });
23682
- 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-"));
23683
23487
  } else {
23684
- cachedRoot = mkdtempSync(join8(tmpdir(), "switchroom-vault-"));
23488
+ cachedRoot = mkdtempSync(join6(tmpdir(), "switchroom-vault-"));
23685
23489
  }
23686
- chmodSync4(cachedRoot, 448);
23490
+ chmodSync2(cachedRoot, 448);
23687
23491
  return cachedRoot;
23688
23492
  }
23689
23493
  function writeFileExclusive(filePath, content) {
@@ -23696,14 +23500,14 @@ function writeFileExclusive(filePath, content) {
23696
23500
  }
23697
23501
  }
23698
23502
  function materializeFilesEntry(key, files) {
23699
- const dir = join8(materializationRoot(), key);
23503
+ const dir = join6(materializationRoot(), key);
23700
23504
  if (materializedDirs.has(dir)) {
23701
23505
  try {
23702
23506
  rmSync2(dir, { recursive: true, force: true });
23703
23507
  } catch {}
23704
23508
  }
23705
- mkdirSync10(dir, { recursive: true, mode: 448 });
23706
- chmodSync4(dir, 448);
23509
+ mkdirSync8(dir, { recursive: true, mode: 448 });
23510
+ chmodSync2(dir, 448);
23707
23511
  const st = statSync6(dir);
23708
23512
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
23709
23513
  throw new Error(`Refusing to materialize vault entry: ${dir} not owned by caller`);
@@ -23712,7 +23516,7 @@ function materializeFilesEntry(key, files) {
23712
23516
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
23713
23517
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
23714
23518
  }
23715
- const filePath = join8(dir, filename);
23519
+ const filePath = join6(dir, filename);
23716
23520
  const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
23717
23521
  writeFileExclusive(filePath, content);
23718
23522
  }
@@ -23839,9 +23643,16 @@ var init_resolver = __esm(() => {
23839
23643
  materializedDirs = new Set;
23840
23644
  });
23841
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
+
23842
23653
  // ../src/util/atomic.ts
23843
23654
  import { randomBytes as randomBytes5 } from "node:crypto";
23844
- 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";
23845
23656
  function atomicWriteFileSync2(destPath, contents, modeOrOpts = 384) {
23846
23657
  const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
23847
23658
  const mode = opts.mode ?? 384;
@@ -23865,7 +23676,7 @@ function atomicWriteFileSync2(destPath, contents, modeOrOpts = 384) {
23865
23676
  fsyncSync3(fd);
23866
23677
  closeSync4(fd);
23867
23678
  fd = null;
23868
- renameSync8(tmp, destPath);
23679
+ renameSync7(tmp, destPath);
23869
23680
  } catch (err) {
23870
23681
  if (fd !== null) {
23871
23682
  try {
@@ -23931,6 +23742,276 @@ var init_approval_card = __esm(() => {
23931
23742
  import_grammy5 = __toESM(require_mod2(), 1);
23932
23743
  });
23933
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
+
23934
24015
  // quota-check.ts
23935
24016
  import { readFileSync as readFileSync19, existsSync as existsSync17 } from "fs";
23936
24017
  import { join as join25 } from "path";
@@ -37216,7 +37297,7 @@ function renderBootCard(opts) {
37216
37297
  const ageStr = restartAgeMs != null && restartAgeMs > 0 ? ` \u00b7 ${(restartAgeMs / 1000).toFixed(1)}s ago` : "";
37217
37298
  degradedRows.push(`\u26a0\ufe0f **Restart** ${escapeMarkdown(REASON_LABEL.crash)}${ageStr}`);
37218
37299
  const tailCmd = process.env.SWITCHROOM_RUNTIME === "docker" ? `docker logs --tail 100 switchroom-${agentSlug}` : `journalctl --user -u switchroom-${agentSlug} -n 100`;
37219
- degradedRows.push(` \u21b3 Tail logs: \`${tailCmd}\``);
37300
+ degradedRows.push(`${NESTED_PREFIX}Tail logs: \`${tailCmd}\``);
37220
37301
  }
37221
37302
  if (probes) {
37222
37303
  for (const key of PROBE_KEYS) {
@@ -37230,7 +37311,7 @@ function renderBootCard(opts) {
37230
37311
  const dot = DOT[r.status] ?? DOT.fail;
37231
37312
  degradedRows.push(`${dot} **${PROBE_LABELS[key]}** ${escapeMarkdown(r.detail)}`);
37232
37313
  if (r.nextStep) {
37233
- degradedRows.push(` \u21b3 ${renderNextStep(r.nextStep)}`);
37314
+ degradedRows.push(`${NESTED_PREFIX}${renderNextStep(r.nextStep)}`);
37234
37315
  }
37235
37316
  }
37236
37317
  }
@@ -37534,6 +37615,7 @@ var SETTLE_WINDOW_MS = 6000, DOT, PROBE_LABELS, PROBE_KEYS, REASON_EMOJI, REASON
37534
37615
  var init_boot_card = __esm(() => {
37535
37616
  init_boot_probes();
37536
37617
  init_card_format();
37618
+ init_status_no_truncate();
37537
37619
  init_boot_issue_cache();
37538
37620
  init_config_snapshot();
37539
37621
  init_boot_card_msgid();
@@ -38999,7 +39081,7 @@ function buildGrantedKeyboard(scope) {
38999
39081
  const btn = scopeToOpenInDriveButton(scope);
39000
39082
  if (btn === null)
39001
39083
  return;
39002
- return new import_grammy15.InlineKeyboard().url(btn.text, btn.url);
39084
+ return new import_grammy16.InlineKeyboard().url(btn.text, btn.url);
39003
39085
  }
39004
39086
  async function handleApprovalCallback(ctx, data) {
39005
39087
  const parsed = parseApprovalCallback(data);
@@ -39045,11 +39127,11 @@ async function handleApprovalCallback(ctx, data) {
39045
39127
  } catch {}
39046
39128
  await ctx.answerCallbackQuery({ text: granted ? "Approved" : "Denied" });
39047
39129
  }
39048
- var import_grammy15;
39130
+ var import_grammy16;
39049
39131
  var init_approval_callback = __esm(() => {
39050
39132
  init_approval_card();
39051
39133
  init_client3();
39052
- import_grammy15 = __toESM(require_mod2(), 1);
39134
+ import_grammy16 = __toESM(require_mod2(), 1);
39053
39135
  });
39054
39136
 
39055
39137
  // ../src/telegram/materialize-bot-token.ts
@@ -39222,7 +39304,7 @@ var init_approvals_commands = __esm(() => {
39222
39304
  });
39223
39305
 
39224
39306
  // gateway/gateway.ts
39225
- var import_grammy16 = __toESM(require_mod(), 1);
39307
+ var import_grammy17 = __toESM(require_mod(), 1);
39226
39308
  var import_runner3 = __toESM(require_mod3(), 1);
39227
39309
  import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39228
39310
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
@@ -43201,10 +43283,64 @@ init_rich_send();
43201
43283
  // shared/bot-runtime.ts
43202
43284
  var import_grammy3 = __toESM(require_mod2(), 1);
43203
43285
  var import_runner = __toESM(require_mod4(), 1);
43204
- import { AsyncLocalStorage } from "async_hooks";
43286
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
43205
43287
 
43206
43288
  // retry-api-call.ts
43207
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
+ }
43208
43344
  function classifyBenignTelegram400(errorCode, description) {
43209
43345
  if (errorCode !== 400)
43210
43346
  return null;
@@ -43272,7 +43408,7 @@ function createRetryApiCall(config = {}) {
43272
43408
  throw gated;
43273
43409
  }
43274
43410
  try {
43275
- return await fn();
43411
+ return await withAttemptContext({ attempt, maxRetries, maxFloodSleepMs }, fn);
43276
43412
  } catch (err) {
43277
43413
  const isGrammyErr = err instanceof import_grammy2.GrammyError;
43278
43414
  const msg = err instanceof Error ? err.message : String(err);
@@ -43304,12 +43440,14 @@ function createRetryApiCall(config = {}) {
43304
43440
  const benignKind = isGrammyErr ? classifyBenignTelegram400(err.error_code, desc) : null;
43305
43441
  if (benignKind !== null) {
43306
43442
  observer?.onBenign?.({ kind: benignKind });
43443
+ if (opts?.rethrowBenign400)
43444
+ throw err;
43307
43445
  return;
43308
43446
  }
43309
43447
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
43310
43448
  throw Object.assign(new Error("THREAD_NOT_FOUND"), { original: err });
43311
43449
  }
43312
- if (!isGrammyErr && (msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND"))) {
43450
+ if (isTransientTransportError(err)) {
43313
43451
  if (attempt < maxRetries - 1) {
43314
43452
  const delayMs = Math.pow(2, attempt) * 1000;
43315
43453
  log?.(`telegram gateway: network error, retrying in ${delayMs / 1000}s: ${msg}
@@ -43367,7 +43505,6 @@ function isPhotoDimensionRejectError(err) {
43367
43505
  }
43368
43506
 
43369
43507
  // shared/bot-runtime.ts
43370
- init_flood_circuit_breaker();
43371
43508
  init_format();
43372
43509
 
43373
43510
  // shared/gw-trace-gate.ts
@@ -43380,7 +43517,7 @@ var ZERO_SIGNAL_POLL_METHODS = new Set(["getUpdates", "getMe"]);
43380
43517
  function shouldEmitTgPost(method, status, verbose = gwTraceVerbose) {
43381
43518
  if (verbose)
43382
43519
  return true;
43383
- if (status === "err")
43520
+ if (status === "err" || status === "retry")
43384
43521
  return true;
43385
43522
  return !ZERO_SIGNAL_POLL_METHODS.has(method);
43386
43523
  }
@@ -43397,7 +43534,7 @@ function shouldEmitShadowTrace(eventKind, effectCount, globalKind, verbose = gwT
43397
43534
 
43398
43535
  // shared/bot-runtime.ts
43399
43536
  init_rich_send();
43400
- var tgPostTagStore = new AsyncLocalStorage;
43537
+ var tgPostTagStore = new AsyncLocalStorage2;
43401
43538
  function escapeHtmlForTg(text) {
43402
43539
  return text.replace(/([\\`*_~=\[\]|])/g, "\\$1");
43403
43540
  }
@@ -45005,10 +45142,10 @@ async function handleAnimationMessage(ctx, deps) {
45005
45142
  }
45006
45143
 
45007
45144
  // gateway/photo-message-handler.ts
45008
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
45145
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
45009
45146
 
45010
45147
  // attachment-path.ts
45011
- 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";
45012
45149
  function sanitizeExtension(ext) {
45013
45150
  if (ext == null)
45014
45151
  return "bin";
@@ -45031,7 +45168,7 @@ function buildAttachmentPath(input) {
45031
45168
  const ext = extractExtension(input.telegramFilePath);
45032
45169
  const uid = sanitizeUniqueId(input.fileUniqueId);
45033
45170
  const filename = `${input.now}-${uid}.${ext}`;
45034
- return join7(input.inboxDir, filename);
45171
+ return join5(input.inboxDir, filename);
45035
45172
  }
45036
45173
  function assertInsideInbox(inboxDir, candidatePath) {
45037
45174
  const inboxReal = resolve2(inboxDir);
@@ -45066,9 +45203,9 @@ async function handlePhotoMessage(ctx, deps) {
45066
45203
  fileUniqueId: best.file_unique_id,
45067
45204
  now: Date.now()
45068
45205
  });
45069
- mkdirSync8(deps.inboxDir, { recursive: true, mode: 448 });
45206
+ mkdirSync6(deps.inboxDir, { recursive: true, mode: 448 });
45070
45207
  assertInsideInbox(deps.inboxDir, dlPath);
45071
- writeFileSync7(dlPath, buf, { mode: 384 });
45208
+ writeFileSync5(dlPath, buf, { mode: 384 });
45072
45209
  return dlPath;
45073
45210
  } catch (err) {
45074
45211
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -46294,7 +46431,7 @@ async function handleAskCallback(ctx, data, deps) {
46294
46431
  }
46295
46432
 
46296
46433
  // gateway/voice-ondemand-callback-handler.ts
46297
- import { readFileSync as readFileSync9 } from "node:fs";
46434
+ import { readFileSync as readFileSync7 } from "node:fs";
46298
46435
 
46299
46436
  // voice-send.ts
46300
46437
  function extractVoiceFileId(sent) {
@@ -46732,14 +46869,14 @@ table omitted.`);
46732
46869
  init_loader();
46733
46870
  init_resolver();
46734
46871
  init_vault();
46735
- import { existsSync as existsSync8 } from "node:fs";
46872
+ import { existsSync as existsSync7 } from "node:fs";
46736
46873
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
46737
46874
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
46738
46875
  function tryDirectVaultRead(ref, config, passphrase) {
46739
46876
  if (!passphrase)
46740
46877
  return null;
46741
46878
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
46742
- if (!existsSync8(vaultPath))
46879
+ if (!existsSync7(vaultPath))
46743
46880
  return null;
46744
46881
  try {
46745
46882
  const secrets = openVault(passphrase, vaultPath);
@@ -46826,7 +46963,7 @@ async function handleVoiceOnDemandCallback(ctx, data, deps) {
46826
46963
  let audio = null;
46827
46964
  if (entry.filePath != null) {
46828
46965
  try {
46829
- audio = readFileSync9(entry.filePath);
46966
+ audio = readFileSync7(entry.filePath);
46830
46967
  } catch {
46831
46968
  audio = null;
46832
46969
  }
@@ -47411,18 +47548,11 @@ class DeferredDoneReactions {
47411
47548
 
47412
47549
  // worker-activity-feed.ts
47413
47550
  init_card_format();
47414
-
47415
- // status-no-truncate.ts
47416
- init_format();
47417
- var STATUS_ROLLING_LINES = 5;
47418
- var WORKER_HISTORY_MAX = 6;
47419
- var STATUS_LINE_MAX = 200;
47420
- var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
47421
- var NESTED_PREFIX = " \u21b3 ";
47422
- var WORKER_STEP_INDENT = "\u2800\u2800\u2800";
47551
+ init_status_no_truncate();
47423
47552
 
47424
47553
  // card-layout.ts
47425
47554
  init_card_format();
47555
+ init_format();
47426
47556
 
47427
47557
  // model-label.ts
47428
47558
  var DATE_SUFFIX = /^\d{8}$/;
@@ -47461,6 +47591,7 @@ function formatModelLabel(model) {
47461
47591
  }
47462
47592
 
47463
47593
  // card-layout.ts
47594
+ init_status_no_truncate();
47464
47595
  function formatFeedElapsed(ms) {
47465
47596
  const s = Math.floor(ms / 1000);
47466
47597
  if (s < 60)
@@ -47559,15 +47690,36 @@ function fitCardToBudget(build, maxLevel) {
47559
47690
  if (candidate.overflow !== true && text.length <= STATUS_CARD_CHAR_BUDGET)
47560
47691
  return text;
47561
47692
  }
47562
- 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]+$/, "");
47563
47715
  }
47564
47716
 
47565
47717
  // hooks/tool-label-pretool.mjs
47566
- import { readFileSync as readFileSync10, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, existsSync as existsSync9 } from "node:fs";
47567
- 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";
47568
47720
  function readStdin() {
47569
47721
  try {
47570
- return readFileSync10(0, "utf8");
47722
+ return readFileSync8(0, "utf8");
47571
47723
  } catch {
47572
47724
  return "";
47573
47725
  }
@@ -47873,10 +48025,10 @@ function main() {
47873
48025
  }) + `
47874
48026
  `;
47875
48027
  try {
47876
- if (!existsSync9(stateDir)) {
47877
- mkdirSync11(stateDir, { recursive: true });
48028
+ if (!existsSync8(stateDir)) {
48029
+ mkdirSync9(stateDir, { recursive: true });
47878
48030
  }
47879
- const target = join9(stateDir, `tool-labels-${sessionId}.jsonl`);
48031
+ const target = join7(stateDir, `tool-labels-${sessionId}.jsonl`);
47880
48032
  appendFileSync2(target, line);
47881
48033
  } catch (err) {
47882
48034
  try {
@@ -47898,6 +48050,7 @@ if (isMain)
47898
48050
  main();
47899
48051
 
47900
48052
  // tool-activity-summary.ts
48053
+ init_status_no_truncate();
47901
48054
  init_card_format();
47902
48055
  init_redact();
47903
48056
 
@@ -47975,14 +48128,14 @@ function renderStatusCard(opts) {
47975
48128
  return null;
47976
48129
  const parentMarker = hasChildren && rawSteps.length > 0 ? `_\u2713 +${rawSteps.length} earlier\u2026_` : null;
47977
48130
  const deepest = Math.max(1, escapedBody.length);
48131
+ const markerSection = {
48132
+ steps: [],
48133
+ window: 1,
48134
+ placeholder: parentMarker ?? undefined
48135
+ };
47978
48136
  return fitCardToBudget((level) => {
47979
48137
  if (level === 0)
47980
48138
  return { spec: fullSpec() };
47981
- const markerSection = {
47982
- steps: [],
47983
- window: 1,
47984
- placeholder: parentMarker ?? undefined
47985
- };
47986
48139
  if (level < escapedBody.length) {
47987
48140
  return {
47988
48141
  spec: {
@@ -48011,23 +48164,38 @@ function renderStatusCard(opts) {
48011
48164
  }
48012
48165
  };
48013
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
+ }
48014
48177
  function truncatedNewestLine() {
48015
- const fixedCost = [...chrome, ...footer].join(`
48016
- `).length;
48017
48178
  const rawNewest = rawBody.length > 0 ? cleanStepLine(rawBody[rawBody.length - 1]) : "";
48018
- const wrapperOverhead = final ? (bodyIndent + "_\u2713 _").length : (bodyIndent + "**\u2192 **").length + liveSuffix.length;
48019
- const headerFooterCost = fixedCost + (fixedCost > 0 ? 1 : 0) + (parentMarker != null ? parentMarker.length + 1 : 0);
48020
- const budget = STATUS_CARD_CHAR_BUDGET - headerFooterCost - wrapperOverhead;
48021
- let raw = rawNewest.slice(0, Math.max(0, budget));
48022
- let newest = escapeMarkdown(raw);
48023
- while (raw.length > 0 && wrapperOverhead + headerFooterCost + newest.length > STATUS_CARD_CHAR_BUDGET) {
48024
- const excess = wrapperOverhead + headerFooterCost + newest.length - STATUS_CARD_CHAR_BUDGET;
48025
- raw = raw.slice(0, Math.max(0, raw.length - excess - 1));
48026
- 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));
48027
48192
  }
48028
- return final ? `${bodyIndent}_\u2713 ${newest}_` : `${bodyIndent}**\u2192 ${newest}${liveSuffix}**`;
48029
48193
  }
48030
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
+ }
48031
48199
  var WORKER_RESULT_RULE = "\u2500\u2500\u2500\u2500\u2500";
48032
48200
  var WORKER_RESULT_MAX = 320;
48033
48201
  function deriveCardResult(state, summary) {
@@ -48128,8 +48296,8 @@ function appendActivityLabel(lines, label) {
48128
48296
  }
48129
48297
 
48130
48298
  // outbound-class.ts
48131
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
48132
- var store = new AsyncLocalStorage2;
48299
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
48300
+ var store = new AsyncLocalStorage3;
48133
48301
  function withOutboundClass(cls, fn) {
48134
48302
  return store.run(cls, fn);
48135
48303
  }
@@ -48233,7 +48401,7 @@ function classifyEditError(err) {
48233
48401
  function createWorkerActivityFeed(opts) {
48234
48402
  const log = opts.log ?? (() => {});
48235
48403
  const nowFn = opts.now ?? Date.now;
48236
- const floodWaitRemainingMs2 = opts.floodWaitRemainingMs ?? (() => 0);
48404
+ const floodWaitRemainingMs = opts.floodWaitRemainingMs ?? (() => 0);
48237
48405
  const minEditInterval = opts.minEditIntervalMs ?? 2500;
48238
48406
  const elapsedRefreshMs = Math.max(minEditInterval, Math.floor(opts.elapsedRefreshMs ?? 15000));
48239
48407
  const firstPaintMin = opts.firstPaintMinMs ?? 8000;
@@ -48286,7 +48454,7 @@ function createWorkerActivityFeed(opts) {
48286
48454
  log(`worker-feed: ${label} 429 \u2014 backing off ${retryAfter}s`);
48287
48455
  }
48288
48456
  function parkIfFloodWindowOpen(g) {
48289
- const remaining = floodWaitRemainingMs2();
48457
+ const remaining = floodWaitRemainingMs();
48290
48458
  if (remaining <= 0)
48291
48459
  return false;
48292
48460
  const until = nowFn() + remaining + COOLDOWN_JITTER_MS;
@@ -49243,13 +49411,13 @@ function buildCancelledCardEdits(cardText, cards) {
49243
49411
  var STALE_TAP_NOTICE = "This request already resolved (timed out) \u2014 ask again to act.";
49244
49412
 
49245
49413
  // gateway/permission-card-store.ts
49246
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "node:fs";
49247
- 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";
49248
49416
  function createPermissionCardStore(stateDir) {
49249
- const filePath = join10(stateDir, "pending-perm-cards.json");
49417
+ const filePath = join8(stateDir, "pending-perm-cards.json");
49250
49418
  function read() {
49251
49419
  try {
49252
- const raw = readFileSync11(filePath, "utf-8");
49420
+ const raw = readFileSync9(filePath, "utf-8");
49253
49421
  const parsed = JSON.parse(raw);
49254
49422
  return Array.isArray(parsed) ? parsed : [];
49255
49423
  } catch {
@@ -49258,7 +49426,7 @@ function createPermissionCardStore(stateDir) {
49258
49426
  }
49259
49427
  function write(entries) {
49260
49428
  try {
49261
- writeFileSync8(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
49429
+ writeFileSync6(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
49262
49430
  } catch (err) {
49263
49431
  process.stderr.write(`telegram gateway: permission-card-store write failed: ${err.message}
49264
49432
  `);
@@ -49287,7 +49455,7 @@ function createPermissionCardStore(stateDir) {
49287
49455
  },
49288
49456
  clear() {
49289
49457
  try {
49290
- unlinkSync6(filePath);
49458
+ unlinkSync4(filePath);
49291
49459
  } catch {}
49292
49460
  }
49293
49461
  };
@@ -49295,13 +49463,13 @@ function createPermissionCardStore(stateDir) {
49295
49463
 
49296
49464
  // gateway/pending-card-store.ts
49297
49465
  init_atomic();
49298
- import { unlinkSync as unlinkSync7 } from "node:fs";
49299
- import { join as join12 } from "node:path";
49466
+ import { unlinkSync as unlinkSync5 } from "node:fs";
49467
+ import { join as join10 } from "node:path";
49300
49468
 
49301
49469
  // gateway/store-file.ts
49302
49470
  import { randomBytes as randomBytes6 } from "node:crypto";
49303
- import { readFileSync as readFileSync12, readdirSync as readdirSync4, renameSync as renameSync9, rmSync as rmSync4, statSync as statSync7 } from "node:fs";
49304
- 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";
49305
49473
  var defaultLog = (line) => {
49306
49474
  process.stderr.write(line);
49307
49475
  };
@@ -49312,18 +49480,18 @@ function quarantinePath(filePath) {
49312
49480
  return `${filePath}.corrupt-${Date.now()}-${seq}-${randomBytes6(4).toString("hex")}`;
49313
49481
  }
49314
49482
  function reapOldQuarantines(filePath, log) {
49315
- const dir = dirname6(filePath);
49483
+ const dir = dirname4(filePath);
49316
49484
  const prefix = `${basename6(filePath)}.corrupt-`;
49317
49485
  try {
49318
49486
  const copies = readdirSync4(dir).filter((f) => f.startsWith(prefix)).map((name) => {
49319
49487
  let mtimeNs = 0n;
49320
49488
  try {
49321
- mtimeNs = statSync7(join11(dir, name), { bigint: true }).mtimeNs;
49489
+ mtimeNs = statSync7(join9(dir, name), { bigint: true }).mtimeNs;
49322
49490
  } catch {}
49323
49491
  return { name, mtimeNs };
49324
49492
  }).sort((a, b) => a.mtimeNs === b.mtimeNs ? a.name.localeCompare(b.name) : a.mtimeNs < b.mtimeNs ? -1 : 1);
49325
49493
  for (const stale of copies.slice(0, Math.max(0, copies.length - MAX_QUARANTINED_COPIES))) {
49326
- rmSync4(join11(dir, stale.name), { force: true });
49494
+ rmSync4(join9(dir, stale.name), { force: true });
49327
49495
  }
49328
49496
  } catch (err) {
49329
49497
  log(`telegram gateway: quarantine reap failed dir=${dir}: ${err.message}
@@ -49334,7 +49502,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
49334
49502
  const target = quarantinePath(filePath);
49335
49503
  let preserved = target;
49336
49504
  try {
49337
- renameSync9(filePath, target);
49505
+ renameSync8(filePath, target);
49338
49506
  } catch (err) {
49339
49507
  preserved = `NOT preserved (${err.message})`;
49340
49508
  }
@@ -49345,7 +49513,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
49345
49513
  function preserveUnreadableStoreFile(filePath, store2, log = defaultLog) {
49346
49514
  const target = quarantinePath(filePath);
49347
49515
  try {
49348
- renameSync9(filePath, target);
49516
+ renameSync8(filePath, target);
49349
49517
  } catch (err) {
49350
49518
  if (err.code === "ENOENT") {
49351
49519
  return;
@@ -49361,7 +49529,7 @@ function preserveUnreadableStoreFile(filePath, store2, log = defaultLog) {
49361
49529
  function readStoreJsonSync(filePath, store2, log = defaultLog) {
49362
49530
  let raw;
49363
49531
  try {
49364
- raw = readFileSync12(filePath, "utf-8");
49532
+ raw = readFileSync10(filePath, "utf-8");
49365
49533
  } catch (err) {
49366
49534
  if (err.code === "ENOENT")
49367
49535
  return { status: "missing" };
@@ -49379,7 +49547,7 @@ function readStoreJsonSync(filePath, store2, log = defaultLog) {
49379
49547
 
49380
49548
  // gateway/pending-card-store.ts
49381
49549
  function createPendingCardStore(stateDir, log = (l) => process.stderr.write(l)) {
49382
- const filePath = join12(stateDir, "pending-approval-cards.json");
49550
+ const filePath = join10(stateDir, "pending-approval-cards.json");
49383
49551
  let unreadable = false;
49384
49552
  function read() {
49385
49553
  const result = readStoreJsonSync(filePath, "pending-card-store", log);
@@ -49427,7 +49595,7 @@ function createPendingCardStore(stateDir, log = (l) => process.stderr.write(l))
49427
49595
  },
49428
49596
  clear() {
49429
49597
  try {
49430
- unlinkSync7(filePath);
49598
+ unlinkSync5(filePath);
49431
49599
  } catch {}
49432
49600
  }
49433
49601
  };
@@ -49566,8 +49734,8 @@ function createSweepableCardStore(deps) {
49566
49734
  init_rich_send();
49567
49735
  var import_grammy4 = __toESM(require_mod2(), 1);
49568
49736
  import { execFileSync as execFileSync3 } from "child_process";
49569
- import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "fs";
49570
- import { dirname as dirname7 } from "path";
49737
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
49738
+ import { dirname as dirname5 } from "path";
49571
49739
  init_client2();
49572
49740
 
49573
49741
  // gateway/vault-grant-inbound-builders.ts
@@ -49699,26 +49867,26 @@ var import_yaml3 = __toESM(require_dist(), 1);
49699
49867
 
49700
49868
  // ../src/web/config-diff.ts
49701
49869
  import {
49702
- mkdirSync as mkdirSync12,
49870
+ mkdirSync as mkdirSync10,
49703
49871
  mkdtempSync as mkdtemp,
49704
49872
  rmSync as rmrf,
49705
- writeFileSync as writeFileSync9
49873
+ writeFileSync as writeFileSync7
49706
49874
  } from "node:fs";
49707
49875
  import { spawnSync } from "node:child_process";
49708
49876
  import { tmpdir as tmpdir2 } from "node:os";
49709
- import { join as join13 } from "node:path";
49877
+ import { join as join11 } from "node:path";
49710
49878
 
49711
49879
  class ConfigDiffError extends Error {
49712
49880
  }
49713
49881
  function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
49714
49882
  if (before === after)
49715
49883
  return "";
49716
- const dir = mkdtemp(join13(tmpdir2(), "switchroom-config-diff-"));
49884
+ const dir = mkdtemp(join11(tmpdir2(), "switchroom-config-diff-"));
49717
49885
  try {
49718
- mkdirSync12(join13(dir, "cur"), { recursive: true });
49719
- mkdirSync12(join13(dir, "new"), { recursive: true });
49720
- writeFileSync9(join13(dir, "cur", name), before);
49721
- 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);
49722
49890
  const r = spawnSync(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
49723
49891
  if (r.status === 0)
49724
49892
  return "";
@@ -50049,25 +50217,25 @@ function buildSkillProposalApplyInbound(opts) {
50049
50217
  // ../src/self-improve/skill-proposals.ts
50050
50218
  import {
50051
50219
  closeSync as closeSync5,
50052
- existsSync as existsSync10,
50053
- mkdirSync as mkdirSync13,
50220
+ existsSync as existsSync9,
50221
+ mkdirSync as mkdirSync11,
50054
50222
  openSync as openSync5,
50055
- readFileSync as readFileSync13,
50223
+ readFileSync as readFileSync11,
50056
50224
  writeSync as writeSync5
50057
50225
  } from "node:fs";
50058
- import { join as join14 } from "node:path";
50226
+ import { join as join12 } from "node:path";
50059
50227
  var PROPOSALS_FILE = "skill-proposals.jsonl";
50060
50228
  var REJECTED_FILE = "skill-proposals-rejected.jsonl";
50061
50229
  var REJECTION_TTL_MS = 90 * 24 * 60 * 60 * 1000;
50062
50230
  function proposalsPath(stateDir) {
50063
- return join14(stateDir, PROPOSALS_FILE);
50231
+ return join12(stateDir, PROPOSALS_FILE);
50064
50232
  }
50065
50233
  function rejectedPath(stateDir) {
50066
- return join14(stateDir, REJECTED_FILE);
50234
+ return join12(stateDir, REJECTED_FILE);
50067
50235
  }
50068
50236
  function ensureDir2(stateDir) {
50069
- if (!existsSync10(stateDir)) {
50070
- mkdirSync13(stateDir, { recursive: true, mode: 493 });
50237
+ if (!existsSync9(stateDir)) {
50238
+ mkdirSync11(stateDir, { recursive: true, mode: 493 });
50071
50239
  }
50072
50240
  }
50073
50241
  function appendLine(path, obj) {
@@ -50080,11 +50248,11 @@ function appendLine(path, obj) {
50080
50248
  }
50081
50249
  }
50082
50250
  function readLines(path, isValid2) {
50083
- if (!existsSync10(path))
50251
+ if (!existsSync9(path))
50084
50252
  return [];
50085
50253
  let raw;
50086
50254
  try {
50087
- raw = readFileSync13(path, "utf-8");
50255
+ raw = readFileSync11(path, "utf-8");
50088
50256
  } catch {
50089
50257
  return [];
50090
50258
  }
@@ -50226,7 +50394,7 @@ async function addAccountViaBroker(label, credentials, opts = {}) {
50226
50394
  }
50227
50395
 
50228
50396
  // gateway/hostd-dispatch.ts
50229
- import { existsSync as existsSync11 } from "node:fs";
50397
+ import { existsSync as existsSync10 } from "node:fs";
50230
50398
  import { randomBytes as randomBytes7 } from "node:crypto";
50231
50399
 
50232
50400
  // ../src/host-control/client.ts
@@ -50549,13 +50717,13 @@ function hostdSocketPath(agentName) {
50549
50717
  function hostdWillBeUsed(agentName) {
50550
50718
  if (!isHostdEnabled())
50551
50719
  return false;
50552
- return existsSync11(hostdSocketPath(agentName));
50720
+ return existsSync10(hostdSocketPath(agentName));
50553
50721
  }
50554
50722
  async function tryHostdDispatch(agentName, req, timeoutMs = 5000) {
50555
50723
  if (!isHostdEnabled())
50556
50724
  return "not-configured";
50557
50725
  const sockPath = hostdSocketPath(agentName);
50558
- if (!existsSync11(sockPath))
50726
+ if (!existsSync10(sockPath))
50559
50727
  return "not-configured";
50560
50728
  try {
50561
50729
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -50579,7 +50747,7 @@ async function hostdGetStatusOnce(agentName, targetRequestId) {
50579
50747
  if (!isHostdEnabled())
50580
50748
  return "not-configured";
50581
50749
  const sockPath = hostdSocketPath(agentName);
50582
- if (!existsSync11(sockPath))
50750
+ if (!existsSync10(sockPath))
50583
50751
  return "not-configured";
50584
50752
  try {
50585
50753
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -50727,8 +50895,8 @@ function createCallbackQueryHandlers(deps) {
50727
50895
  const { token, id } = result;
50728
50896
  const tokenPath = vaultTokenFilePath2(agentName);
50729
50897
  try {
50730
- mkdirSync14(dirname7(tokenPath), { recursive: true });
50731
- writeFileSync10(tokenPath, token, { mode: 384 });
50898
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
50899
+ writeFileSync8(tokenPath, token, { mode: 384 });
50732
50900
  } catch (err) {
50733
50901
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
50734
50902
  ` + `_Recover with: \`switchroom vault grant ${escapeHtmlForTg2(agentName)} ` + `--keys ${escapeHtmlForTg2(keyName)} --duration 30d\` on the host._`, { html: true });
@@ -50879,8 +51047,8 @@ function createCallbackQueryHandlers(deps) {
50879
51047
  const { token, id } = result;
50880
51048
  const tokenPath = vaultTokenFilePath2(pending.agent);
50881
51049
  try {
50882
- mkdirSync14(dirname7(tokenPath), { recursive: true });
50883
- writeFileSync10(tokenPath, token, { mode: 384 });
51050
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
51051
+ writeFileSync8(tokenPath, token, { mode: 384 });
50884
51052
  } catch (err) {
50885
51053
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
50886
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 });
@@ -51708,8 +51876,8 @@ ${keyList}`,
51708
51876
  const { token, id } = result;
51709
51877
  const tokenPath = vaultTokenFilePath2(state.agent);
51710
51878
  try {
51711
- mkdirSync14(dirname7(tokenPath), { recursive: true });
51712
- writeFileSync10(tokenPath, token, { mode: 384 });
51879
+ mkdirSync12(dirname5(tokenPath), { recursive: true });
51880
+ writeFileSync8(tokenPath, token, { mode: 384 });
51713
51881
  } catch (err) {
51714
51882
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
51715
51883
  return;
@@ -52767,12 +52935,12 @@ function hangStalenessMs(env = process.env) {
52767
52935
 
52768
52936
  // gateway/missed-approvals-store.ts
52769
52937
  init_atomic();
52770
- import { unlinkSync as unlinkSync8 } from "node:fs";
52771
- import { join as join16 } from "node:path";
52938
+ import { unlinkSync as unlinkSync6 } from "node:fs";
52939
+ import { join as join14 } from "node:path";
52772
52940
  var MAX_PENDING = 50;
52773
52941
  var MAX_DELIVERED = 20;
52774
52942
  function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(l)) {
52775
- const filePath = join16(stateDir, "missed-approvals.json");
52943
+ const filePath = join14(stateDir, "missed-approvals.json");
52776
52944
  const EMPTY = () => ({ pending: [], delivered: [] });
52777
52945
  let unreadable = false;
52778
52946
  function read() {
@@ -52852,7 +53020,7 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
52852
53020
  },
52853
53021
  clear() {
52854
53022
  try {
52855
- unlinkSync8(filePath);
53023
+ unlinkSync6(filePath);
52856
53024
  } catch {}
52857
53025
  }
52858
53026
  };
@@ -52860,8 +53028,8 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
52860
53028
 
52861
53029
  // gateway/always-allow-persist-queue.ts
52862
53030
  init_atomic();
52863
- import { unlinkSync as unlinkSync9 } from "node:fs";
52864
- import { join as join17 } from "node:path";
53031
+ import { unlinkSync as unlinkSync7 } from "node:fs";
53032
+ import { join as join15 } from "node:path";
52865
53033
  var MAX_ATTEMPTS = 5;
52866
53034
  var MAX_AGE_MS = 24 * 60 * 60 * 1000;
52867
53035
  var MAX_QUEUE_SIZE = 50;
@@ -52885,7 +53053,7 @@ var atomicWriteSeam = (path, data, opts) => {
52885
53053
  atomicWriteFileSync2(path, data, mode);
52886
53054
  };
52887
53055
  function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSeam, log = (l) => process.stderr.write(l)) {
52888
- const filePath = join17(stateDir, "always-allow-persist-queue.json");
53056
+ const filePath = join15(stateDir, "always-allow-persist-queue.json");
52889
53057
  let lock = Promise.resolve();
52890
53058
  function withLock(fn) {
52891
53059
  const result = lock.then(fn, fn);
@@ -52997,7 +53165,7 @@ function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSe
52997
53165
  },
52998
53166
  clear() {
52999
53167
  try {
53000
- unlinkSync9(filePath);
53168
+ unlinkSync7(filePath);
53001
53169
  } catch {}
53002
53170
  }
53003
53171
  };
@@ -53160,8 +53328,8 @@ function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
53160
53328
  }
53161
53329
 
53162
53330
  // gateway/approval-hold.ts
53163
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync12, readFileSync as readFileSync14, unlinkSync as unlinkSync10, chmodSync as chmodSync5, lstatSync as lstatSync2 } from "node:fs";
53164
- 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";
53165
53333
  var BLOCKED_APPROVAL_FILE_MODE = 420;
53166
53334
  var BLOCKED_APPROVAL_DIR_MODE = 1023;
53167
53335
  var HELD_RETRY_BACKOFF_MS = 60000;
@@ -53190,24 +53358,24 @@ function safeActionForRecord(naturalAction2, toolName, inputPreview) {
53190
53358
  return ACTION_SAFE_WITH_INPUT2.has(toolName) ? naturalAction2(toolName, inputPreview) : naturalAction2(toolName, undefined);
53191
53359
  }
53192
53360
  function createBlockedApprovalStore(dir, agent, fallbackDir) {
53193
- const primary = join18(dir, `${agent}.json`);
53194
- 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;
53195
53363
  let active = primary;
53196
53364
  function tryWrite(target, body, dirMode) {
53197
- const parent = dirname8(target);
53365
+ const parent = dirname6(target);
53198
53366
  try {
53199
- mkdirSync15(parent, { recursive: true });
53367
+ mkdirSync13(parent, { recursive: true });
53200
53368
  try {
53201
53369
  if (lstatSync2(target).isSymbolicLink())
53202
53370
  return false;
53203
53371
  } catch {}
53204
53372
  if (dirMode != null) {
53205
53373
  try {
53206
- chmodSync5(parent, dirMode);
53374
+ chmodSync3(parent, dirMode);
53207
53375
  } catch {}
53208
53376
  }
53209
- writeFileSync12(target, body, { encoding: "utf-8", mode: BLOCKED_APPROVAL_FILE_MODE });
53210
- 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);
53211
53379
  return true;
53212
53380
  } catch {
53213
53381
  return false;
@@ -53224,7 +53392,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53224
53392
  active = primary;
53225
53393
  if (fallback != null) {
53226
53394
  try {
53227
- unlinkSync10(fallback);
53395
+ unlinkSync8(fallback);
53228
53396
  } catch {}
53229
53397
  }
53230
53398
  return;
@@ -53243,7 +53411,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53243
53411
  if (p == null)
53244
53412
  continue;
53245
53413
  try {
53246
- unlinkSync10(p);
53414
+ unlinkSync8(p);
53247
53415
  } catch {}
53248
53416
  }
53249
53417
  active = primary;
@@ -53253,7 +53421,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
53253
53421
  if (p == null)
53254
53422
  continue;
53255
53423
  try {
53256
- const parsed = JSON.parse(readFileSync14(p, "utf-8"));
53424
+ const parsed = JSON.parse(readFileSync12(p, "utf-8"));
53257
53425
  if (parsed != null && typeof parsed === "object")
53258
53426
  return parsed;
53259
53427
  } catch {}
@@ -53332,6 +53500,7 @@ function isTelegramSurfaceTool2(toolName) {
53332
53500
  }
53333
53501
 
53334
53502
  // tool-activity-summary.ts
53503
+ init_status_no_truncate();
53335
53504
  init_card_format();
53336
53505
  init_redact();
53337
53506
  function clipNarrative2(s) {
@@ -53550,6 +53719,7 @@ function createTurnTypingLoop(deps) {
53550
53719
 
53551
53720
  // gateway/handback-preturn-signal.ts
53552
53721
  var PRETURN_TURNKEY_PREFIX = "preturn:";
53722
+ var HANDBACK_REINJECT_COUNT_META_KEY = "handbackReinjectCount";
53553
53723
  function isHandbackInbound(msg) {
53554
53724
  return msg.type === "inbound" && msg.meta?.source === "subagent_handback";
53555
53725
  }
@@ -53557,6 +53727,8 @@ function createHandbackPreturnSignal(deps) {
53557
53727
  const now = deps.now ?? (() => Date.now());
53558
53728
  const debounceMs = deps.debounceMs ?? 700;
53559
53729
  const adoptTimeoutMs = deps.adoptTimeoutMs ?? 30000;
53730
+ const deliveredTtlMs = deps.deliveredTtlMs ?? 300000;
53731
+ const maxReinjects = deps.maxReinjects ?? 2;
53560
53732
  const setTimer = deps.setTimer ?? ((fn, ms) => {
53561
53733
  const t = setTimeout(fn, ms);
53562
53734
  t.unref?.();
@@ -53596,7 +53768,7 @@ function createHandbackPreturnSignal(deps) {
53596
53768
  if (messageId == null)
53597
53769
  return;
53598
53770
  if (entry.consumed) {
53599
- deps.finalizeCard({
53771
+ deps.deleteCard({
53600
53772
  turnKey: entry.syntheticTurnKey,
53601
53773
  chatId: entry.chatId,
53602
53774
  threadId: entry.threadId,
@@ -53629,12 +53801,15 @@ function createHandbackPreturnSignal(deps) {
53629
53801
  }
53630
53802
  deps.stopTypingLoop(entry.chatId, entry.threadId);
53631
53803
  }
53804
+ function deliveredAwaitingTurnStart(entry) {
53805
+ return now() - entry.deliveredAt < deliveredTtlMs;
53806
+ }
53632
53807
  function reap(entry) {
53633
53808
  entry.reapTimer = null;
53634
53809
  if (entry.consumed)
53635
53810
  return;
53636
- if (deps.isClaudeBusy?.() === true) {
53637
- 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)
53638
53813
  `);
53639
53814
  entry.reapTimer = setTimer(() => reap(entry), adoptTimeoutMs);
53640
53815
  return;
@@ -53651,12 +53826,40 @@ function createHandbackPreturnSignal(deps) {
53651
53826
  pinned: entry.pinned
53652
53827
  };
53653
53828
  deps.clearCardRecord(entry.syntheticTurnKey, entry.activityMessageId);
53654
- Promise.resolve(deps.finalizeCard(record)).catch((err) => {
53655
- 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)}
53656
53831
  `);
53657
53832
  });
53658
53833
  }
53659
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
+ }
53660
53863
  }
53661
53864
  return {
53662
53865
  noteHandbackRelease(inbound) {
@@ -53683,6 +53886,9 @@ function createHandbackPreturnSignal(deps) {
53683
53886
  `);
53684
53887
  const startedAt = now();
53685
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;
53686
53892
  const entry = {
53687
53893
  statusKey,
53688
53894
  chatId,
@@ -53690,6 +53896,9 @@ function createHandbackPreturnSignal(deps) {
53690
53896
  adoptTurnId,
53691
53897
  syntheticTurnKey,
53692
53898
  startedAt,
53899
+ deliveredAt: startedAt,
53900
+ reinjectCount,
53901
+ inbound,
53693
53902
  pinned: false,
53694
53903
  debounceTimer: null,
53695
53904
  reapTimer: null,
@@ -53772,6 +53981,24 @@ function createHandbackPreturnSignal(deps) {
53772
53981
  };
53773
53982
  }
53774
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
+
53775
54002
  // gateway/derive-turn-id.ts
53776
54003
  function deriveTurnId2(chatId, threadId, messageId) {
53777
54004
  if (messageId == null || messageId === "" || String(messageId) === "0")
@@ -63395,7 +63622,6 @@ function renderSafe(doc, source, maxLen = RICH_MESSAGE_MAX_CHARS) {
63395
63622
 
63396
63623
  // render/rich-render.ts
63397
63624
  init_format();
63398
- var PLAIN_TEXT_MAX_CHARS = 4096;
63399
63625
  function parseRichRenderEnabled(raw) {
63400
63626
  if (raw == null)
63401
63627
  return true;
@@ -63431,6 +63657,48 @@ function renderOutboundChunks(text4, env = process.env, maxLen = RICH_MESSAGE_MA
63431
63657
  return out.length > 0 ? out : [whole];
63432
63658
  }
63433
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
+
63434
63702
  // stream-controller.ts
63435
63703
  function createStreamController(cfg) {
63436
63704
  const {
@@ -63458,13 +63726,10 @@ function createStreamController(cfg) {
63458
63726
  ...disableLinkPreview ? { link_preview_options: { is_disabled: true } } : {},
63459
63727
  ...replyMarkup != null ? { reply_markup: replyMarkup } : {}
63460
63728
  };
63461
- const sendOpts = {
63729
+ let sendOpts = {
63462
63730
  ...baseOpts,
63463
63731
  ...replyToMessageId != null ? {
63464
- reply_parameters: {
63465
- message_id: replyToMessageId,
63466
- ...quoteText != null ? { quote: { text: quoteText, position: 0 } } : {}
63467
- }
63732
+ reply_parameters: buildReplyParameters(replyToMessageId, quoteText)
63468
63733
  } : {},
63469
63734
  ...protectContent === true ? { protect_content: true } : {},
63470
63735
  ...cfg.disableNotification === true ? { disable_notification: true } : {}
@@ -63481,6 +63746,17 @@ function createStreamController(cfg) {
63481
63746
  delete richOpts.link_preview_options;
63482
63747
  return bot.api.sendRichMessage(chatId, richMessage(piece.text), richOpts);
63483
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
+ };
63484
63760
  const editPiece = (id, piece, opts) => {
63485
63761
  if (!piece.rich)
63486
63762
  return bot.api.editMessageText(chatId, id, piece.text, opts);
@@ -63523,7 +63799,7 @@ function createStreamController(cfg) {
63523
63799
  return false;
63524
63800
  }
63525
63801
  try {
63526
- const sent = await retry(() => sendPiece(piece, sendOpts), { threadId, chat_id: chatId });
63802
+ const sent = await retry(() => sendPieceWithQuoteFallback(piece), { threadId, chat_id: chatId });
63527
63803
  tailIds[ti] = sent.message_id;
63528
63804
  tailLastText[ti] = piece.text;
63529
63805
  onSend?.(sent.message_id, piece.text.length);
@@ -63545,7 +63821,7 @@ function createStreamController(cfg) {
63545
63821
  const head = pieces[0];
63546
63822
  let anchorId;
63547
63823
  try {
63548
- const sent = await retry(() => sendPiece(head, sendOpts), { threadId, chat_id: chatId });
63824
+ const sent = await retry(() => sendPieceWithQuoteFallback(head), { threadId, chat_id: chatId });
63549
63825
  anchorId = sent.message_id;
63550
63826
  } catch (err) {
63551
63827
  if (!literalText && head.rich && isParseEntitiesError(err)) {
@@ -63730,7 +64006,42 @@ function createChatLock() {
63730
64006
  }
63731
64007
 
63732
64008
  // retry-api-call.ts
63733
- 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
+ }
63734
64045
  function classifyBenignTelegram4002(errorCode, description) {
63735
64046
  if (errorCode !== 400)
63736
64047
  return null;
@@ -63775,12 +64086,12 @@ function createRetryApiCall2(config = {}) {
63775
64086
  const observer = config.observer;
63776
64087
  const log = config.log;
63777
64088
  const onFloodWait = config.onFloodWait;
63778
- const floodWaitRemainingMs2 = config.floodWaitRemainingMs;
64089
+ const floodWaitRemainingMs = config.floodWaitRemainingMs;
63779
64090
  function openWindowMs() {
63780
- if (!floodWaitRemainingMs2)
64091
+ if (!floodWaitRemainingMs)
63781
64092
  return 0;
63782
64093
  try {
63783
- const ms = floodWaitRemainingMs2();
64094
+ const ms = floodWaitRemainingMs();
63784
64095
  return Number.isFinite(ms) && ms > 0 ? ms : 0;
63785
64096
  } catch {
63786
64097
  return 0;
@@ -63798,9 +64109,9 @@ function createRetryApiCall2(config = {}) {
63798
64109
  throw gated;
63799
64110
  }
63800
64111
  try {
63801
- return await fn();
64112
+ return await withAttemptContext2({ attempt, maxRetries, maxFloodSleepMs }, fn);
63802
64113
  } catch (err) {
63803
- const isGrammyErr = err instanceof import_grammy6.GrammyError;
64114
+ const isGrammyErr = err instanceof import_grammy7.GrammyError;
63804
64115
  const msg = err instanceof Error ? err.message : String(err);
63805
64116
  const desc = isGrammyErr ? err.description : msg;
63806
64117
  if (isLocalResourceError2(err)) {
@@ -63830,12 +64141,14 @@ function createRetryApiCall2(config = {}) {
63830
64141
  const benignKind = isGrammyErr ? classifyBenignTelegram4002(err.error_code, desc) : null;
63831
64142
  if (benignKind !== null) {
63832
64143
  observer?.onBenign?.({ kind: benignKind });
64144
+ if (opts?.rethrowBenign400)
64145
+ throw err;
63833
64146
  return;
63834
64147
  }
63835
64148
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
63836
64149
  throw Object.assign(new Error("THREAD_NOT_FOUND"), { original: err });
63837
64150
  }
63838
- if (!isGrammyErr && (msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND"))) {
64151
+ if (isTransientTransportError2(err)) {
63839
64152
  if (attempt < maxRetries - 1) {
63840
64153
  const delayMs = Math.pow(2, attempt) * 1000;
63841
64154
  log?.(`telegram gateway: network error, retrying in ${delayMs / 1000}s: ${msg}
@@ -65136,14 +65449,13 @@ function createFloodWindowObserver(config) {
65136
65449
  }
65137
65450
 
65138
65451
  // shared/bot-runtime.ts
65139
- var import_grammy7 = __toESM(require_mod2(), 1);
65452
+ var import_grammy8 = __toESM(require_mod2(), 1);
65140
65453
  var import_runner2 = __toESM(require_mod4(), 1);
65141
65454
  import { createHash as createHash3 } from "crypto";
65142
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
65143
- init_flood_circuit_breaker();
65455
+ import { AsyncLocalStorage as AsyncLocalStorage5 } from "async_hooks";
65144
65456
  init_format();
65145
65457
  init_rich_send();
65146
- var tgPostTagStore2 = new AsyncLocalStorage3;
65458
+ var tgPostTagStore2 = new AsyncLocalStorage5;
65147
65459
  function _getTgPostTags() {
65148
65460
  return tgPostTagStore2.getStore();
65149
65461
  }
@@ -65186,16 +65498,27 @@ function installTgPostLogger(bot) {
65186
65498
  if (r != null && typeof r === "object" && r.ok === false) {
65187
65499
  const code2 = r.error_code;
65188
65500
  const benign = classifyBenignTelegram400(code2, r.description);
65189
- 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));
65190
65507
  return res;
65191
65508
  }
65192
65509
  emit("ok", "-", "-", "-");
65193
65510
  return res;
65194
65511
  } catch (err) {
65195
- const errClass = err instanceof import_grammy7.GrammyError ? `grammy_${err.error_code}` : err?.constructor?.name ?? "Error";
65196
- const code2 = err instanceof import_grammy7.GrammyError ? String(err.error_code) : "-";
65197
- const rawDesc = err instanceof import_grammy7.GrammyError ? err.description : err instanceof Error ? err.message : "";
65198
- 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));
65199
65522
  throw err;
65200
65523
  }
65201
65524
  });
@@ -69909,11 +70232,11 @@ class PostHogBackendClient extends PostHogCoreStateless {
69909
70232
  }
69910
70233
 
69911
70234
  // ../node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/context/context.mjs
69912
- import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
70235
+ import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
69913
70236
 
69914
70237
  class PostHogContext {
69915
70238
  constructor() {
69916
- this.storage = new AsyncLocalStorage4;
70239
+ this.storage = new AsyncLocalStorage6;
69917
70240
  }
69918
70241
  get() {
69919
70242
  return this.storage.getStore();
@@ -76366,9 +76689,7 @@ function splitMarkdownChunks2(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
76366
76689
  } else if (spaceIdx > 0) {
76367
76690
  cut = spaceIdx;
76368
76691
  }
76369
- cut = backOffOpenFence2(rest, cut);
76370
- cut = backOffTableRow2(rest, cut);
76371
- cut = backOffOpenInline2(rest, cut);
76692
+ cut = safeMarkdownCut2(rest, cut);
76372
76693
  if (cut <= 0) {
76373
76694
  const sliced = hardSliceToCap2(rest, maxLen);
76374
76695
  chunks.push(stripBoundarySpacers2(sliced[0], "trailing"));
@@ -76421,8 +76742,29 @@ var INLINE_SPAN_PATTERNS2 = [
76421
76742
  /\*\*[^*\n]+\*\*/g,
76422
76743
  /__[^_\n]+__/g,
76423
76744
  /(?<![\w*])_[^_\n]+_(?![\w*])/g,
76424
- /\[[^\]\n]*\]\([^)\n]*\)/g
76745
+ /\[[^\]\n]*\]\([^)\n]*\)/g,
76746
+ /~~[^~\n]+~~/g,
76747
+ /\|\|[^|\n]+\|\|/g
76425
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
+ }
76426
76768
  function backOffOpenInline2(text4, cut) {
76427
76769
  if (cut <= 0 || cut >= text4.length)
76428
76770
  return cut;
@@ -76446,7 +76788,7 @@ function backOffOpenInline2(text4, cut) {
76446
76788
 
76447
76789
  // gateway/command-format.ts
76448
76790
  init_format();
76449
- var import_grammy8 = __toESM(require_mod2(), 1);
76791
+ var import_grammy9 = __toESM(require_mod2(), 1);
76450
76792
  function formatSwitchroomOutput(output, maxLen = RICH_MESSAGE_MAX_CHARS) {
76451
76793
  const trimmed = output.trim();
76452
76794
  if (trimmed.length <= maxLen)
@@ -76514,7 +76856,7 @@ function formatAuthOutputForTelegram(output) {
76514
76856
  `), url };
76515
76857
  }
76516
76858
  function buildAuthUrlKeyboard(authorizeUrl) {
76517
- 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);
76518
76860
  }
76519
76861
  function buildDeferredSecretKeyboard(deferKey) {
76520
76862
  const unlockData = `vd:unlock:${deferKey}`;
@@ -76524,7 +76866,7 @@ function buildDeferredSecretKeyboard(deferKey) {
76524
76866
  `);
76525
76867
  throw new Error(`callback_data overflow: deferKey too long (${deferKey.length} chars)`);
76526
76868
  }
76527
- 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);
76528
76870
  }
76529
76871
  function renderVaultOpFailure(verbLabel, cliOutput, key) {
76530
76872
  const parsed = parseVaultCliError(cliOutput);
@@ -76560,7 +76902,7 @@ _${escapeHtmlForTg2(outcome.paneTailText)}_` : "";
76560
76902
  }
76561
76903
  }
76562
76904
  function buildDoctorScopeKeyboard() {
76563
- 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");
76564
76906
  }
76565
76907
  function formatDoctorReport(raw) {
76566
76908
  const trimmed = stripAnsi4(raw).trim();
@@ -76576,7 +76918,7 @@ init_emphasis_guard();
76576
76918
  init_line_start_guard();
76577
76919
  init_inline_pairs_guard();
76578
76920
  init_unsupported_token_guard();
76579
- var import_grammy9 = __toESM(require_mod2(), 1);
76921
+ var import_grammy10 = __toESM(require_mod2(), 1);
76580
76922
  function guardAccidentalFormatting2(markdown) {
76581
76923
  let out = markdown;
76582
76924
  out = guardUnsupportedTokens(out);
@@ -76833,7 +77175,7 @@ function normalizeTemporal(text4, tz, nowMs2) {
76833
77175
  }
76834
77176
 
76835
77177
  // gateway/outbound-send-path.ts
76836
- var import_grammy10 = __toESM(require_mod2(), 1);
77178
+ var import_grammy11 = __toESM(require_mod2(), 1);
76837
77179
  import { statSync as statSync11 } from "fs";
76838
77180
  import { extname } from "path";
76839
77181
 
@@ -76980,6 +77322,42 @@ import {
76980
77322
  } from "node:fs";
76981
77323
  import { join as join31 } from "node:path";
76982
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
76983
77361
  function sha256Hex(s) {
76984
77362
  return createHash4("sha256").update(s, "utf8").digest("hex");
76985
77363
  }
@@ -77159,7 +77537,8 @@ function decideOutboxSweep(input) {
77159
77537
  routePrefix = "",
77160
77538
  quietMs = OUTBOX_QUIET_MS,
77161
77539
  maxAgeMs = OUTBOX_MAX_AGE_MS,
77162
- shownLedgerHit = false
77540
+ shownLedgerHit = false,
77541
+ provenanceFraming = true
77163
77542
  } = input;
77164
77543
  if (deliveredNonces.has(record.turnNonce))
77165
77544
  return { action: "skip-journaled" };
@@ -77174,7 +77553,13 @@ function decideOutboxSweep(input) {
77174
77553
  return { action: "skip-unroutable" };
77175
77554
  const delayed = age > maxAgeMs;
77176
77555
  const prefix = (delayed ? "(delayed) " : "") + routePrefix;
77177
- 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
+ };
77178
77563
  }
77179
77564
  function resolveOutboxChat(record, deps) {
77180
77565
  if (record.chatId != null && record.chatId !== "") {
@@ -77469,7 +77854,8 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
77469
77854
  tgMessageId: args.tgMessageId,
77470
77855
  ts: now,
77471
77856
  deliverySource: args.deliverySource ?? "reply-tool",
77472
- ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn }
77857
+ ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn },
77858
+ ...args.framedProvenance == null ? {} : { framedProvenance: args.framedProvenance }
77473
77859
  }, stateDir);
77474
77860
  clearOutboxRecord(nonce, stateDir);
77475
77861
  }
@@ -77524,6 +77910,7 @@ function queueFloodBlockedReply(input) {
77524
77910
  textSha256: sha256Hex(text4),
77525
77911
  createdAt,
77526
77912
  source: FLOOD_QUEUED_SOURCE,
77913
+ audience: "user",
77527
77914
  ...input.originChatId != null ? { originChatId: input.originChatId } : {},
77528
77915
  ...input.originThreadId != null ? { originThreadId: input.originThreadId } : {}
77529
77916
  };
@@ -78042,7 +78429,12 @@ function decideCapturedProseDelivery(args, deps) {
78042
78429
  if (deps?.backstopDeliveredNonceHit?.(state3.turnId) === true) {
78043
78430
  return { deliver: false, reason: "already-delivered" };
78044
78431
  }
78045
- 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
+ };
78046
78438
  }
78047
78439
  function settleCapturedProseDelivery(outcome, effects) {
78048
78440
  if (outcome === "failed") {
@@ -78179,10 +78571,23 @@ async function sendReplyChunks(deps, state3) {
78179
78571
  }
78180
78572
  const sendChunkPlainText = async (opts) => {
78181
78573
  const plain = chunks[i].length > 0 ? chunks[i] : "\u26a0\ufe0f (a fragment could not be rendered for Telegram)";
78182
- const sent = await deps.sendLiteralRaw(opts, plain);
78183
- sentIds.push(sent.message_id);
78184
- deps.logOutbound("reply", chatId, sent.message_id, plain.length, `chunk=${i + 1}/${chunks.length} plaintext-fallback`);
78185
- 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)` : "") + `
78186
78591
  `);
78187
78592
  };
78188
78593
  const sendChunk = (opts, wrapped) => {
@@ -78231,6 +78636,13 @@ async function sendReplyChunks(deps, state3) {
78231
78636
  else
78232
78637
  throw retryErr;
78233
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
+ `);
78234
78646
  } else if (isMessageTooLongError(err)) {
78235
78647
  await sendChunkResplit(sendOpts);
78236
78648
  } else if (isHtmlParseRejectError(err)) {
@@ -78243,7 +78655,7 @@ async function sendReplyChunks(deps, state3) {
78243
78655
  return { threadId, previewMessageId };
78244
78656
  }
78245
78657
  function classifyReadBackError(err) {
78246
- if (err instanceof import_grammy10.GrammyError && err.error_code === 400) {
78658
+ if (err instanceof import_grammy11.GrammyError && err.error_code === 400) {
78247
78659
  const d = (err.description || "").toLowerCase();
78248
78660
  if (d.includes("message to edit not found"))
78249
78661
  return "absent";
@@ -78798,10 +79210,7 @@ ${url}`;
78798
79210
  const shouldReplyTo = reply_to != null && replyMode !== "off" && (replyMode === "all" || i === 0);
78799
79211
  return {
78800
79212
  ...shouldReplyTo ? {
78801
- reply_parameters: {
78802
- message_id: reply_to,
78803
- ...quoteText != null ? { quote: { text: quoteText, position: 0 } } : {}
78804
- }
79213
+ reply_parameters: buildReplyParameters(reply_to, quoteText)
78805
79214
  } : {},
78806
79215
  ...tid != null ? { message_thread_id: tid } : {},
78807
79216
  ...disableLinkPreview ? { link_preview_options: { is_disabled: true } } : {},
@@ -78860,7 +79269,7 @@ ${url}`;
78860
79269
  opts.message_thread_id = tid;
78861
79270
  else
78862
79271
  delete opts.message_thread_id;
78863
- 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);
78864
79273
  }, { threadId, chat_id, verb: "sendVoice" });
78865
79274
  sentIds.push(sentVoice.message_id);
78866
79275
  logOutbound("reply", chat_id, sentVoice.message_id, voiceOutPlan?.ttsChunks[v]?.length ?? 0, `voice-note=${v + 1}/${voiceOggs.length}`);
@@ -78933,12 +79342,12 @@ ${url}`;
78933
79342
  ...replyParams,
78934
79343
  ...tid != null ? { message_thread_id: tid } : {}
78935
79344
  };
78936
- 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);
78937
79346
  }, { threadId, chat_id, verb: "sendDocument" });
78938
79347
  if (allPhotos) {
78939
79348
  const media = files.map((f) => ({
78940
79349
  type: "photo",
78941
- media: new import_grammy10.InputFile(f)
79350
+ media: new import_grammy11.InputFile(f)
78942
79351
  }));
78943
79352
  let sent;
78944
79353
  try {
@@ -78971,7 +79380,7 @@ ${url}`;
78971
79380
  sentIds.push(m.message_id);
78972
79381
  } else {
78973
79382
  for (const f of files) {
78974
- const input = new import_grammy10.InputFile(f);
79383
+ const input = new import_grammy11.InputFile(f);
78975
79384
  const isPhoto = sendableAsPhoto(f);
78976
79385
  let sent;
78977
79386
  try {
@@ -79069,10 +79478,11 @@ async function deliverCapturedProse(deps, args) {
79069
79478
  } = deps;
79070
79479
  const { chatId, threadId, statusKeyStr, registryKey, originTurnId, text: text4, turnDurationMs } = args;
79071
79480
  const now = Date.now();
79481
+ const framed = shouldFrameReplyThrow({ replyToolThrewThisTurn: args.replyToolThrewThisTurn }, { frameEnabled: process.env.SWITCHROOM_TG_OUTBOX_PROVENANCE_FRAMING !== "0" });
79072
79482
  let outcome;
79073
79483
  const already = outboundDedup.check(chatId, threadId, text4, now, registryKey);
79074
79484
  if (already == null) {
79075
- let out = normalizeParagraphBreaks(repairEscapedWhitespace(text4));
79485
+ let out = normalizeParagraphBreaks(repairEscapedWhitespace(framed ? applyReplyThrowFraming(text4) : text4));
79076
79486
  out = redactOutboundText(out, "captured_prose");
79077
79487
  const chunks = splitMarkdownChunks(out, RICH_MESSAGE_MAX_CHARS);
79078
79488
  const sentIds = [];
@@ -79102,9 +79512,24 @@ async function deliverCapturedProse(deps, args) {
79102
79512
  } catch {}
79103
79513
  }
79104
79514
  outboundDedup.record(chatId, threadId, text4, now, registryKey);
79105
- 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
+ });
79106
79522
  process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from ` + `transcript scan (chat=${chatId} origin=${originTurnId})
79107
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
+ }
79108
79533
  outcome = "sent";
79109
79534
  } catch (err) {
79110
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})
@@ -79142,7 +79567,7 @@ async function deliverCapturedProse(deps, args) {
79142
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})
79143
79568
  `);
79144
79569
  const plain = redactOutboundText(text4, "captured_prose");
79145
- const plainChunks = splitMarkdownChunks(plain, RICH_MESSAGE_MAX_CHARS);
79570
+ const plainChunks = splitPlainTextToCap(plain);
79146
79571
  try {
79147
79572
  let liveThreadId = threadId;
79148
79573
  const plainSentIds = [];
@@ -79549,7 +79974,7 @@ class LivenessTracker {
79549
79974
  // gateway/outbound-send-path.ts
79550
79975
  init_format();
79551
79976
  init_text_voice_scrub();
79552
- var import_grammy11 = __toESM(require_mod2(), 1);
79977
+ var import_grammy12 = __toESM(require_mod2(), 1);
79553
79978
  init_format();
79554
79979
  init_rich_send();
79555
79980
  function normalizeOutboundBody2(rawText, site, redact2, opts = {}) {
@@ -81422,6 +81847,7 @@ function handleSessionEvent(deps, ev) {
81422
81847
  registryKey: turn.registryKey ?? null,
81423
81848
  originTurnId: turn.turnId,
81424
81849
  text: proseDecision.text,
81850
+ replyToolThrewThisTurn: proseDecision.replyToolThrewThisTurn === true,
81425
81851
  turnDurationMs
81426
81852
  });
81427
81853
  } else {
@@ -82942,7 +83368,7 @@ async function loadFromAuthBroker(options = {}) {
82942
83368
  }
82943
83369
 
82944
83370
  // gateway/folder-picker-handler.ts
82945
- var import_grammy12 = __toESM(require_mod2(), 1);
83371
+ var import_grammy13 = __toESM(require_mod2(), 1);
82946
83372
 
82947
83373
  // ../src/drive/folder-picker.ts
82948
83374
  var DRIVE_ID_RE2 = /^[A-Za-z0-9_-]+$/;
@@ -83261,7 +83687,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
83261
83687
  const confirmText = `\u2705 Granted ${deps.agentName} access to folder ${parsed.folder_id}
83262
83688
  ` + `Scope: \`${scope}\`
83263
83689
  ` + `Revoke with: \`/approvals revoke ${decisionId}\``;
83264
- 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}`);
83265
83691
  try {
83266
83692
  await ctx.editMessageText({ markdown: confirmText }, {
83267
83693
  reply_markup: kb
@@ -83270,7 +83696,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
83270
83696
  await ctx.answerCallbackQuery({ text: "Allowed" });
83271
83697
  }
83272
83698
  function toKeyboard(spec) {
83273
- const kb = new import_grammy12.InlineKeyboard;
83699
+ const kb = new import_grammy13.InlineKeyboard;
83274
83700
  for (let r = 0;r < spec.rows.length; r++) {
83275
83701
  const row = spec.rows[r];
83276
83702
  for (const btn of row) {
@@ -85346,7 +85772,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
85346
85772
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
85347
85773
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
85348
85774
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
85349
- 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";
85350
85778
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
85351
85779
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
85352
85780
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
@@ -85392,8 +85820,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
85392
85820
  String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
85393
85821
  ],
85394
85822
  [
85395
- "HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS",
85396
- String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS)
85823
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
85824
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
85397
85825
  ],
85398
85826
  [
85399
85827
  "HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
@@ -85410,6 +85838,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
85410
85838
  [
85411
85839
  "HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
85412
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)
85413
85845
  ]
85414
85846
  ];
85415
85847
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
@@ -85430,22 +85862,41 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
85430
85862
  String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
85431
85863
  ],
85432
85864
  ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
85433
- ["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]
85434
85867
  ];
85435
85868
  var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
85436
85869
  "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
85437
85870
  "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
85438
85871
  "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
85439
85872
  "HINDSIGHT_API_WORKER_MAX_SLOTS",
85440
- "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"
85441
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
+ ]));
85442
85891
  var HINDSIGHT_PERF_ENV_KEYS = new Set([
85443
85892
  ...[
85444
85893
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
85445
85894
  ...HINDSIGHT_PERF_DEFAULTS_GPU,
85446
85895
  ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
85447
85896
  ].map(([k]) => k),
85448
- ...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()
85449
85900
  ]);
85450
85901
 
85451
85902
  // ../src/setup/hindsight-context-budget.ts
@@ -89841,7 +90292,9 @@ function createIpcServer(options) {
89841
90292
  case "rollout_status_edit":
89842
90293
  if (onRolloutStatusEdit) {
89843
90294
  try {
89844
- onRolloutStatusEdit(client3, msg);
90295
+ Promise.resolve(onRolloutStatusEdit(client3, msg)).catch((err) => {
90296
+ log(`rollout_status_edit handler rejected (client=${client3.id}): ${err.message}`);
90297
+ });
89845
90298
  } catch (err) {
89846
90299
  log(`rollout_status_edit handler threw (client=${client3.id}): ${err.message}`);
89847
90300
  }
@@ -90027,6 +90480,58 @@ function createIpcServer(options) {
90027
90480
  return ipcServer;
90028
90481
  }
90029
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
+
90030
90535
  // ../src/drive/diff-preview.ts
90031
90536
  function buildDiffPreview(input) {
90032
90537
  validateInput(input);
@@ -90474,7 +90979,7 @@ async function handleRequestMs365Approval(client3, msg, deps) {
90474
90979
 
90475
90980
  // gateway/diff-preview-card.ts
90476
90981
  init_format();
90477
- var import_grammy13 = __toESM(require_mod2(), 1);
90982
+ var import_grammy14 = __toESM(require_mod2(), 1);
90478
90983
  var REQUEST_ID_RE = /^[0-9a-f]{32}$/;
90479
90984
  var PENDING_FILE_ID_SENTINEL = "pending-create";
90480
90985
  function buildDiffPreviewCard(input) {
@@ -90492,7 +90997,7 @@ function buildDiffPreviewCard(input) {
90492
90997
  }
90493
90998
  const text4 = bodyLines.join(`
90494
90999
  `);
90495
- const kb = new import_grammy13.InlineKeyboard;
91000
+ const kb = new import_grammy14.InlineKeyboard;
90496
91001
  const ROW_BREAK_AFTER = [
90497
91002
  "apply_suggestion"
90498
91003
  ];
@@ -90975,6 +91480,23 @@ function loadObligations(path3, fs3) {
90975
91480
  return [];
90976
91481
  return env.obligations.filter(isObligationRow).map(sanitizeCapturedDelivery);
90977
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
+ }
90978
91500
  function persistObligations(path3, fs3, snapshot, log = (l) => process.stderr.write(l)) {
90979
91501
  const env = { v: 1, obligations: [...snapshot] };
90980
91502
  const tmp = path3 + ".tmp";
@@ -97505,6 +98027,7 @@ function readAndFilter(raw, filters, limit) {
97505
98027
  }
97506
98028
 
97507
98029
  // gateway/update-announce.ts
98030
+ init_status_no_truncate();
97508
98031
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
97509
98032
  function readLastTerminalUpdateAudit(opts = {}) {
97510
98033
  const path3 = opts.auditLogPath ?? defaultAuditLogPath();
@@ -97565,7 +98088,10 @@ function renderUpdateOutcomeLine(entry) {
97565
98088
  const stderrTail = (entry.stderr_tail ?? entry.error ?? "").slice(-400);
97566
98089
  const opStep = entry.op;
97567
98090
  const hint = recoveryHint(entry.install_context?.install_type);
97568
- 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
+ ];
97569
98095
  return lines.join(`
97570
98096
  `);
97571
98097
  }
@@ -99894,8 +100420,37 @@ async function sweepOutbox(deps) {
99894
100420
  return summary;
99895
100421
  const deliveredNonces = readDeliveredNonces(deps.stateDir);
99896
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;
99897
100427
  for (const record2 of records) {
99898
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
+ }
99899
100454
  const resolved = resolveOutboxChat(record2, {
99900
100455
  registryChainLookup: (anchorContent) => {
99901
100456
  const taskId = extractTaskId(anchorContent);
@@ -99913,7 +100468,8 @@ async function sweepOutbox(deps) {
99913
100468
  routable: resolved != null,
99914
100469
  routePrefix,
99915
100470
  quietMs: deps.quietMs ?? OUTBOX_QUIET_MS,
99916
- shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir)
100471
+ shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir),
100472
+ provenanceFraming: provenanceFramingEnabled
99917
100473
  });
99918
100474
  if (decision.action !== "send" && decision.action !== "send-delayed") {
99919
100475
  summary.skipped++;
@@ -99956,8 +100512,19 @@ async function sweepOutbox(deps) {
99956
100512
  tgMessageId: sendResult.messageId,
99957
100513
  ts: now,
99958
100514
  deliverySource: "sweep",
99959
- replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true
100515
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true,
100516
+ ...decision.framedProvenance != null ? { framedProvenance: decision.framedProvenance } : {}
99960
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
+ }
99961
100528
  if (deps.recordOutbound != null && sendResult.chunks.length > 0) {
99962
100529
  try {
99963
100530
  deps.recordOutbound(resolvedChat.chatId, resolvedChat.threadId, sendResult.chunks.map((c) => c.messageId), sendResult.chunks.map((c) => c.text));
@@ -99977,6 +100544,9 @@ async function sweepOutbox(deps) {
99977
100544
  }
99978
100545
  return summary;
99979
100546
  }
100547
+ function turnIdFromNonce(nonce) {
100548
+ return /^-?\d+:[^#]*#\d+$/.test(nonce) ? nonce : null;
100549
+ }
99980
100550
  function probeFloodWindow(probe) {
99981
100551
  if (probe == null)
99982
100552
  return 0;
@@ -100017,15 +100587,29 @@ function createSweepBackoff(cfg = {}) {
100017
100587
  failures: () => failures
100018
100588
  };
100019
100589
  }
100020
- async function sendChunkRich(bot, chatId, chunk2, opts) {
100021
- try {
100022
- return await bot.api.sendRichMessage(chatId, richMessage(chunk2), opts);
100023
- } catch (err) {
100024
- if (isParseEntitiesError(err)) {
100025
- 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;
100026
100602
  }
100027
- throw err;
100028
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;
100029
100613
  }
100030
100614
  function createOutboxSend(deps) {
100031
100615
  return async (chatId, threadId, text4) => {
@@ -100040,14 +100624,16 @@ function createOutboxSend(deps) {
100040
100624
  for (let idx = 0;idx < chunks.length; idx++) {
100041
100625
  const chunk2 = chunks[idx];
100042
100626
  const isLast = idx === chunks.length - 1;
100627
+ const chunkState = newChunkSendState();
100043
100628
  const res = await retryWithThreadFallback(deps.retry, (tid) => {
100044
100629
  const base = tid != null ? { message_thread_id: tid } : {};
100045
100630
  const opts = isLast && replyMarkup != null ? { ...base, reply_markup: replyMarkup } : base;
100046
- return sendChunkRich(bot, chatId, chunk2, opts);
100631
+ return sendChunkRich(bot, chatId, chunk2, opts, chunkState);
100047
100632
  }, { threadId: threadId ?? undefined, chat_id: chatId, verb: "outbox-sweep.sendMessage" });
100048
- const id = res?.message_id;
100049
- if (id != null)
100050
- 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
+ }
100051
100637
  }
100052
100638
  return {
100053
100639
  messageId: landed.length > 0 ? landed[landed.length - 1].messageId : undefined,
@@ -100055,6 +100641,54 @@ function createOutboxSend(deps) {
100055
100641
  };
100056
100642
  };
100057
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
+ }
100058
100692
  function startOutboxSweep(deps) {
100059
100693
  if (!deps.isGatewayMain || process.env.SWITCHROOM_TG_OUTBOX_DELIVERY === "0")
100060
100694
  return;
@@ -100071,15 +100705,11 @@ function startOutboxSweep(deps) {
100071
100705
  ...deps.resolveReplyMarkup != null ? { resolveReplyMarkup: deps.resolveReplyMarkup } : {}
100072
100706
  });
100073
100707
  const backoff = deps.backoff ?? createSweepBackoff();
100074
- let lastDeferLogAt = 0;
100075
- const tick3 = () => {
100076
- const bot = deps.getBot();
100077
- if (bot == null)
100078
- return;
100079
- const now = Date.now();
100080
- if (!backoff.ready(now))
100081
- return;
100082
- sweepOutbox({
100708
+ const tick3 = createOutboxSweepTick({
100709
+ getBot: deps.getBot,
100710
+ backoff,
100711
+ log: deps.log,
100712
+ runSweep: () => sweepOutbox({
100083
100713
  stateDir: deps.stateDir,
100084
100714
  log: deps.log,
100085
100715
  send,
@@ -100093,36 +100723,18 @@ function startOutboxSweep(deps) {
100093
100723
  const turnKey3 = resolveSubagentOriginTurnKey(db3, taskId);
100094
100724
  return turnKey3 == null ? null : chatFromTurnKey(turnKey3);
100095
100725
  }
100096
- }).then((summary) => {
100097
- if (summary.floodDeferred === true) {
100098
- const at = Date.now();
100099
- if (at - lastDeferLogAt >= OUTBOX_SWEEP_DEFER_LOG_INTERVAL_MS) {
100100
- lastDeferLogAt = at;
100101
- 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)
100102
- `);
100103
- }
100104
- return;
100105
- }
100106
- if (summary.sendFailures > 0) {
100107
- const delay = backoff.noteFailure(Date.now());
100108
- deps.log?.(`outbox-sweep: ${summary.sendFailures} send failure(s) \u2014 backing off ` + `${Math.round(delay / 1000)}s before the next sweep (attempt ${backoff.failures()})
100109
- `);
100110
- } else {
100111
- backoff.noteSuccess();
100112
- }
100113
- }).catch((err) => deps.log?.(`outbox-sweep: tick failed: ${err.message}
100114
- `));
100115
- };
100726
+ })
100727
+ });
100116
100728
  const timer3 = setInterval(tick3, OUTBOX_SWEEP_INTERVAL_MS);
100117
100729
  timer3.unref?.();
100118
100730
  return timer3;
100119
100731
  }
100120
100732
 
100121
100733
  // ../src/build-info.ts
100122
- var VERSION2 = "0.19.42";
100123
- var COMMIT_SHA = "e288dac2";
100124
- var COMMIT_DATE = "2026-08-01T02:20:24Z";
100125
- var LATEST_PR = 4094;
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;
100126
100738
  var COMMITS_AHEAD_OF_TAG = 0;
100127
100739
 
100128
100740
  // gateway/boot-version.ts
@@ -100167,10 +100779,10 @@ function composeBootVersionString(inputs) {
100167
100779
  }
100168
100780
 
100169
100781
  // gateway/unhandled-rejection-policy.ts
100170
- var import_grammy14 = __toESM(require_mod2(), 1);
100782
+ var import_grammy15 = __toESM(require_mod2(), 1);
100171
100783
  function classifyRejection(err, opts = {}) {
100172
- const isGrammy = opts.isGrammyError != null ? opts.isGrammyError(err) : err instanceof import_grammy14.GrammyError;
100173
- 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;
100174
100786
  if (isHttp)
100175
100787
  return "log_only";
100176
100788
  if (err instanceof Error && err.message === FLOOD_WAIT_ACTIVE)
@@ -102820,7 +103432,8 @@ var obligationStoreFs = {
102820
103432
  renameSync: (a, b) => renameSync27(a, b),
102821
103433
  existsSync: (p) => existsSync57(p),
102822
103434
  fsyncFileSync: fsyncPathSync,
102823
- fsyncDirSync: fsyncPathSync
103435
+ fsyncDirSync: fsyncPathSync,
103436
+ unlinkSync: unlinkSync31
102824
103437
  };
102825
103438
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
102826
103439
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -102832,7 +103445,8 @@ if (isGatewayMain && !STATIC && OBLIGATION_LEDGER_ENABLED) {
102832
103445
  process.stderr.write(`telegram gateway: obligation-ledger hydrated ${restored.length} open obligation(s) from ${OBLIGATION_STORE_PATH}
102833
103446
  `);
102834
103447
  }
102835
- }
103448
+ } else if (isGatewayMain)
103449
+ removeUnmaintainedSnapshot(OBLIGATION_STORE_PATH, obligationStoreFs, `static=${STATIC} enabled=${OBLIGATION_LEDGER_ENABLED}`);
102836
103450
  var obligationEscalateInFlight = new Set;
102837
103451
  var capturedResume = createCapturedResumeDispatcher({
102838
103452
  deliverAnswer: (a) => deliverAnswer(a),
@@ -104081,7 +104695,7 @@ function wrapBootCardApi(threadId) {
104081
104695
  await lockedBot.api.editMessageText(cid, mid, richMessage2(text5), editOpts);
104082
104696
  return "edited";
104083
104697
  } catch (err) {
104084
- 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);
104085
104699
  if (typeof desc === "string" && desc.toLowerCase().includes("not modified"))
104086
104700
  return "edited";
104087
104701
  return "gone";
@@ -105920,7 +106534,7 @@ if (isGatewayMain && inboundSpool != null) {
105920
106534
  }
105921
106535
  var pendingPermissionBuffer = createPendingPermissionBuffer();
105922
106536
  function buildPermissionActionRow(requestId, showAlways) {
105923
- 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}`);
105924
106538
  if (showAlways)
105925
106539
  kb.text("\uD83D\uDD01 Always\u2026", `perm:always:${requestId}`);
105926
106540
  return kb;
@@ -106480,7 +107094,7 @@ if (isGatewayMain)
106480
107094
  }
106481
107095
  },
106482
107096
  postAttachment: async (args) => {
106483
- 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);
106484
107098
  await robustApiCall(() => bot.api.sendDocument(args.chatId, input, {
106485
107099
  ...args.threadId !== undefined ? { message_thread_id: args.threadId } : {}
106486
107100
  }), {
@@ -106565,20 +107179,19 @@ if (isGatewayMain)
106565
107179
  } catch {}
106566
107180
  }
106567
107181
  },
106568
- onRolloutStatusEdit(_client, msg) {
106569
- const self = process.env.SWITCHROOM_AGENT_NAME;
106570
- if (self && msg.agentName !== self) {
106571
- process.stderr.write(`telegram gateway: rollout_status_edit rejected \u2014 agent mismatch (${msg.agentName} != ${self})
106572
- `);
106573
- return;
106574
- }
106575
- const operator = loadAccess().allowFrom[0];
106576
- if (operator === undefined) {
106577
- process.stderr.write(`telegram gateway: rollout_status_edit \u2014 no operator chat (allowFrom empty)
106578
- `);
106579
- return;
106580
- }
106581
- 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);
106582
107195
  },
106583
107196
  onInjectInbound(_client, msg) {
106584
107197
  if (!isCronInjectFire(msg.inbound.meta))
@@ -107278,7 +107891,7 @@ async function executeAskUser(rawArgs) {
107278
107891
  }
107279
107892
  }
107280
107893
  const askId = generateAskId();
107281
- const keyboard = new import_grammy16.InlineKeyboard;
107894
+ const keyboard = new import_grammy17.InlineKeyboard;
107282
107895
  for (let i = 0;i < args.options.length; i++) {
107283
107896
  keyboard.text(args.options[i], encodeAskCallback(askId, i));
107284
107897
  if (i < args.options.length - 1)
@@ -107926,7 +108539,6 @@ if (isGatewayMain && !STATIC && FEED_HEARTBEAT_ENABLED) {
107926
108539
  }
107927
108540
  var HANDBACK_PRETURN_ENABLED = !STATIC && process.env.SWITCHROOM_HANDBACK_PRETURN !== "0";
107928
108541
  var HANDBACK_PRETURN_HTML = "\uD83E\uDD1D Reading the worker\u2019s results\u2026";
107929
- var HANDBACK_PRETURN_ORPHAN_HTML = "\uD83E\uDD1D A background worker finished, but the handback never started \u2014 it may need a nudge.";
107930
108542
  async function openHandbackPreTurnCard(chatId, threadId) {
107931
108543
  if (STATIC)
107932
108544
  return null;
@@ -107946,24 +108558,23 @@ async function openHandbackPreTurnCard(chatId, threadId) {
107946
108558
  return null;
107947
108559
  }
107948
108560
  }
107949
- function finalizeHandbackPreTurnCard(record2) {
107950
- return robustApiCall(() => bot.api.editMessageText(record2.chatId, record2.activityMessageId, richMessage2(HANDBACK_PRETURN_ORPHAN_HTML), {}), {
107951
- chat_id: record2.chatId,
107952
- ...record2.threadId != null ? { threadId: record2.threadId } : {},
107953
- verb: "handback-preturn.orphan-finalize"
107954
- }).then(() => {
107955
- return;
107956
- }).catch(() => {
107957
- return;
107958
- });
107959
- }
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
+ });
107960
108569
  var handbackPreturnSignal = createHandbackPreturnSignal({
107961
108570
  chatKey: (chatId, threadId) => chatKey2(chatId, threadId),
107962
108571
  deriveTurnId: (chatId, threadId, messageId) => deriveTurnId2(chatId, threadId, messageId),
107963
108572
  startTypingLoop: (chatId, threadId) => startTurnTypingLoop(chatId, threadId),
107964
108573
  stopTypingLoop: (chatId, threadId) => stopTurnTypingLoop(chatId, threadId),
107965
108574
  openCard: openHandbackPreTurnCard,
107966
- finalizeCard: finalizeHandbackPreTurnCard,
108575
+ deleteCard: handbackOrphanRecovery.deleteCard,
108576
+ reinjectHandback: handbackOrphanRecovery.reinjectHandback,
108577
+ escalateOrphan: handbackOrphanRecovery.escalateOrphan,
107967
108578
  writeCardRecord: (record2) => {
107968
108579
  if (!activityCardPersistEnabled)
107969
108580
  return;
@@ -109732,7 +110343,7 @@ function buildModelDeps(restartCtx) {
109732
110343
  function modelMenuReplyMarkup(reply) {
109733
110344
  if (!reply.keyboard)
109734
110345
  return;
109735
- const kb = new import_grammy16.InlineKeyboard;
110346
+ const kb = new import_grammy17.InlineKeyboard;
109736
110347
  for (const row of reply.keyboard) {
109737
110348
  for (const btn of row)
109738
110349
  kb.text(btn.text, btn.callback_data);
@@ -109872,7 +110483,7 @@ function buildEffortDeps() {
109872
110483
  function effortMenuReplyMarkup(reply) {
109873
110484
  if (!reply.keyboard)
109874
110485
  return;
109875
- const kb = new import_grammy16.InlineKeyboard;
110486
+ const kb = new import_grammy17.InlineKeyboard;
109876
110487
  for (const row of reply.keyboard) {
109877
110488
  for (const btn of row)
109878
110489
  kb.text(btn.text, btn.callback_data);
@@ -111525,7 +112136,7 @@ _Google accounts are connected from the host CLI._`, { html: true });
111525
112136
  return;
111526
112137
  }
111527
112138
  const appNote = started.source === "default" ? "_Using switchroom's shipped Microsoft app._" : "_Using your configured Microsoft app._";
111528
- 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}`);
111529
112140
  const sent = await ctx.replyWithRichMessage(richMessage2(`\uD83D\uDD17 **Connect a Microsoft account**
111530
112141
 
111531
112142
  ` + `1. Tap **Sign in to Microsoft** below.
@@ -111661,7 +112272,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111661
112272
  tz: process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ
111662
112273
  });
111663
112274
  if (reply.keyboard && reply.keyboard.length > 0) {
111664
- const kb = new import_grammy16.InlineKeyboard;
112275
+ const kb = new import_grammy17.InlineKeyboard;
111665
112276
  for (let i = 0;i < reply.keyboard.length; i++) {
111666
112277
  const row = reply.keyboard[i];
111667
112278
  for (const b of row) {
@@ -111803,7 +112414,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111803
112414
  }
111804
112415
  lines.push("");
111805
112416
  const denials = readRecentDenialsForAgent(targetAgent, 604800000, 5);
111806
- const denialKeyboard = new import_grammy16.InlineKeyboard;
112417
+ const denialKeyboard = new import_grammy17.InlineKeyboard;
111807
112418
  if (denials.length > 0) {
111808
112419
  lines.push("**Recent denials (last 7d):**");
111809
112420
  for (const d of denials) {
@@ -111851,7 +112462,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
111851
112462
  byAgent.set(g.agent_slug, list3);
111852
112463
  }
111853
112464
  const lines = ["**\uD83D\uDCDC Active grants**", ""];
111854
- const keyboard = new import_grammy16.InlineKeyboard;
112465
+ const keyboard = new import_grammy17.InlineKeyboard;
111855
112466
  for (const [agentName3, agentGrants] of byAgent) {
111856
112467
  lines.push(`**${escapeHtmlForTg2(agentName3)}:**`);
111857
112468
  for (const g of agentGrants) {
@@ -112048,7 +112659,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
112048
112659
  if (ctx.chat?.type !== "private") {
112049
112660
  kbRows = kbRows.filter((row) => !row.some((b) => b.callbackData?.startsWith("auth:use:")));
112050
112661
  }
112051
- const keyboard = new import_grammy16.InlineKeyboard;
112662
+ const keyboard = new import_grammy17.InlineKeyboard;
112052
112663
  kbRows.forEach((row, ri) => {
112053
112664
  if (ri > 0)
112054
112665
  keyboard.row();
@@ -112400,7 +113011,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
112400
113011
  await ctx.answerCallbackQuery({ text: "No always-allow rule for this tool." }).catch(() => {});
112401
113012
  return;
112402
113013
  }
112403
- 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}`);
112404
113015
  if (choices.specific)
112405
113016
  keyboard.text(choices.specific.buttonLabel, `perm:asn:${request_id}`);
112406
113017
  keyboard.text(`${choices.broad.buttonLabel} \u26A0\uFE0F`, `perm:asb:${request_id}`);
@@ -112703,7 +113314,7 @@ ${labelWithResume}` : labelWithResume,
112703
113314
  verb: "voice-ondemand.sendVoice",
112704
113315
  ...threadId != null ? { threadId } : {}
112705
113316
  }),
112706
- 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), {
112707
113318
  chat_id: chatId,
112708
113319
  verb: "voice-ondemand.sendVoice",
112709
113320
  ...threadId != null ? { threadId } : {}
@@ -112969,7 +113580,7 @@ async function initGatewayBot() {
112969
113580
  `);
112970
113581
  process.exit(1);
112971
113582
  }
112972
- bot = new import_grammy16.Bot(TOKEN);
113583
+ bot = new import_grammy17.Bot(TOKEN);
112973
113584
  installTgPostLogger(bot);
112974
113585
  installRichMarkdownGuard(bot);
112975
113586
  installEditFloodFuse(bot, {
@@ -113941,7 +114552,7 @@ async function startGateway() {
113941
114552
  await runnerHandle.task();
113942
114553
  return;
113943
114554
  } catch (err) {
113944
- if (err instanceof import_grammy16.GrammyError && err.error_code === 409) {
114555
+ if (err instanceof import_grammy17.GrammyError && err.error_code === 409) {
113945
114556
  const delay = Math.min(1000 * attempt, 15000);
113946
114557
  const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
113947
114558
  process.stderr.write(`telegram gateway: poll.409.detected attempt=${attempt} retry_in_ms=${delay} agent=${agentName3}