switchroom 0.14.47 → 0.14.49

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.
@@ -29493,6 +29493,37 @@ function persistSnapshot(path, snapshot) {
29493
29493
  }
29494
29494
  var init_config_snapshot = () => {};
29495
29495
 
29496
+ // gateway/boot-card-msgid.ts
29497
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "node:fs";
29498
+ function bootCardChatKey(chatId, threadId) {
29499
+ return `${chatId}:${threadId ?? ""}`;
29500
+ }
29501
+ function readStore(path) {
29502
+ try {
29503
+ const parsed = JSON.parse(readFileSync27(path, "utf8"));
29504
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
29505
+ return parsed;
29506
+ }
29507
+ } catch {}
29508
+ return {};
29509
+ }
29510
+ function loadBootCardMsgId(path, chatKey3) {
29511
+ const id = readStore(path)[chatKey3];
29512
+ return typeof id === "number" && Number.isFinite(id) && id > 0 ? id : null;
29513
+ }
29514
+ function saveBootCardMsgId(path, chatKey3, messageId) {
29515
+ if (!(Number.isFinite(messageId) && messageId > 0))
29516
+ return;
29517
+ try {
29518
+ const store2 = readStore(path);
29519
+ if (store2[chatKey3] === messageId)
29520
+ return;
29521
+ store2[chatKey3] = messageId;
29522
+ writeFileSync17(path, JSON.stringify(store2), "utf8");
29523
+ } catch {}
29524
+ }
29525
+ var init_boot_card_msgid = () => {};
29526
+
29496
29527
  // gateway/boot-card.ts
29497
29528
  var exports_boot_card = {};
29498
29529
  __export(exports_boot_card, {
@@ -29640,22 +29671,46 @@ async function startBootCard(chatId, threadId, bot, opts, ackMessageId, log) {
29640
29671
  ...opts.updateOutcomeLine ? { updateOutcomeLine: opts.updateOutcomeLine } : {}
29641
29672
  });
29642
29673
  const silentBootCard = true;
29643
- let messageId;
29644
- try {
29645
- const sent = await bot.sendMessage(chatId, ackText, {
29646
- parse_mode: "HTML",
29647
- link_preview_options: { is_disabled: true },
29648
- ...threadId != null ? { message_thread_id: threadId } : {},
29649
- ...ackMessageId != null ? { reply_parameters: { message_id: ackMessageId } } : {},
29650
- ...silentBootCard ? { disable_notification: true } : {}
29651
- });
29652
- messageId = sent.message_id;
29653
- logger2(`telegram gateway: boot-card: posted msgId=${messageId} chatId=${chatId} reason=${opts.restartReason ?? "-"} reason_detail=${opts.restartReasonDetail ?? "-"} silent=${silentBootCard}
29674
+ const chatKey3 = bootCardChatKey(chatId, threadId);
29675
+ const reuseId = ackMessageId == null && opts.bootCardStatePath != null && bot.editMessageTextStrict != null ? loadBootCardMsgId(opts.bootCardStatePath, chatKey3) : null;
29676
+ let messageId = -1;
29677
+ if (reuseId != null && bot.editMessageTextStrict != null) {
29678
+ try {
29679
+ const outcome = await bot.editMessageTextStrict(chatId, reuseId, ackText, {
29680
+ parse_mode: "HTML",
29681
+ link_preview_options: { is_disabled: true },
29682
+ ...threadId != null ? { message_thread_id: threadId } : {}
29683
+ });
29684
+ if (outcome === "edited") {
29685
+ messageId = reuseId;
29686
+ logger2(`telegram gateway: boot-card: reused msgId=${messageId} chatId=${chatId} reason=${opts.restartReason ?? "-"} reason_detail=${opts.restartReasonDetail ?? "-"} edit_in_place=true notify=none
29654
29687
  `);
29655
- } catch (err) {
29656
- logger2(`telegram gateway: boot-card: failed to post ack: ${err?.message ?? String(err)}
29688
+ }
29689
+ } catch (err) {
29690
+ logger2(`telegram gateway: boot-card: edit-in-place probe failed (${err?.message ?? String(err)}) \u2014 sending fresh
29657
29691
  `);
29658
- return { messageId: -1, complete: () => {} };
29692
+ }
29693
+ }
29694
+ if (messageId < 0) {
29695
+ try {
29696
+ const sent = await bot.sendMessage(chatId, ackText, {
29697
+ parse_mode: "HTML",
29698
+ link_preview_options: { is_disabled: true },
29699
+ ...threadId != null ? { message_thread_id: threadId } : {},
29700
+ ...ackMessageId != null ? { reply_parameters: { message_id: ackMessageId } } : {},
29701
+ ...silentBootCard ? { disable_notification: true } : {}
29702
+ });
29703
+ messageId = sent.message_id;
29704
+ logger2(`telegram gateway: boot-card: posted msgId=${messageId} chatId=${chatId} reason=${opts.restartReason ?? "-"} reason_detail=${opts.restartReasonDetail ?? "-"} silent=${silentBootCard}
29705
+ `);
29706
+ } catch (err) {
29707
+ logger2(`telegram gateway: boot-card: failed to post ack: ${err?.message ?? String(err)}
29708
+ `);
29709
+ return { messageId: -1, complete: () => {} };
29710
+ }
29711
+ }
29712
+ if (opts.bootCardStatePath != null && messageId > 0) {
29713
+ saveBootCardMsgId(opts.bootCardStatePath, chatKey3, messageId);
29659
29714
  }
29660
29715
  const liveWindowMs = opts.agentLiveWindowMs ?? AGENT_LIVE_WINDOW_MS;
29661
29716
  setTimeoutFn(() => {
@@ -29822,6 +29877,7 @@ var init_boot_card = __esm(() => {
29822
29877
  init_boot_probes();
29823
29878
  init_boot_issue_cache();
29824
29879
  init_config_snapshot();
29880
+ init_boot_card_msgid();
29825
29881
  init_loader();
29826
29882
  init_merge();
29827
29883
  DOT = {
@@ -30211,8 +30267,8 @@ var init_flock = () => {};
30211
30267
  // ../src/vault/vault.ts
30212
30268
  import { randomBytes as randomBytes5, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
30213
30269
  import {
30214
- readFileSync as readFileSync34,
30215
- writeFileSync as writeFileSync22,
30270
+ readFileSync as readFileSync35,
30271
+ writeFileSync as writeFileSync23,
30216
30272
  existsSync as existsSync36,
30217
30273
  renameSync as renameSync11,
30218
30274
  mkdirSync as mkdirSync23,
@@ -30256,7 +30312,7 @@ function openVault(passphrase, vaultPath) {
30256
30312
  }
30257
30313
  let vaultFile;
30258
30314
  try {
30259
- vaultFile = JSON.parse(readFileSync34(vaultPath, "utf8"));
30315
+ vaultFile = JSON.parse(readFileSync35(vaultPath, "utf8"));
30260
30316
  } catch {
30261
30317
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
30262
30318
  }
@@ -30637,7 +30693,7 @@ __export(exports_tmux, {
30637
30693
  captureAgentPane: () => captureAgentPane
30638
30694
  });
30639
30695
  import { execFileSync as execFileSync4 } from "node:child_process";
30640
- import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync23 } from "node:fs";
30696
+ import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync24 } from "node:fs";
30641
30697
  import { resolve as resolve7 } from "node:path";
30642
30698
  function captureAgentPane(opts) {
30643
30699
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -30683,7 +30739,7 @@ function captureAgentPane(opts) {
30683
30739
  ` + `
30684
30740
  `;
30685
30741
  try {
30686
- writeFileSync23(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30742
+ writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30687
30743
  mode: 420
30688
30744
  });
30689
30745
  } catch (err) {
@@ -31259,8 +31315,8 @@ var import_runner2 = __toESM(require_mod3(), 1);
31259
31315
  import { randomBytes as randomBytes6 } from "crypto";
31260
31316
  import { execFileSync as execFileSync5, execSync as execSync2, spawn as spawn2 } from "child_process";
31261
31317
  import {
31262
- readFileSync as readFileSync35,
31263
- writeFileSync as writeFileSync24,
31318
+ readFileSync as readFileSync36,
31319
+ writeFileSync as writeFileSync25,
31264
31320
  mkdirSync as mkdirSync26,
31265
31321
  readdirSync as readdirSync7,
31266
31322
  rmSync as rmSync4,
@@ -46927,7 +46983,7 @@ function escapeHtml7(s) {
46927
46983
 
46928
46984
  // gateway/pending-inbound-buffer.ts
46929
46985
  var DEFAULT_PENDING_INBOUND_CAP = 32;
46930
- function redeliverBufferedInbound(buffer, agent, send, spool) {
46986
+ function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
46931
46987
  const pending = buffer.drain(agent);
46932
46988
  let redelivered = 0;
46933
46989
  let rebuffered = 0;
@@ -46942,6 +46998,7 @@ function redeliverBufferedInbound(buffer, agent, send, spool) {
46942
46998
  for (const o of originals)
46943
46999
  spool?.ack(o);
46944
47000
  redelivered += originals.length;
47001
+ onDelivered?.(merged, originals);
46945
47002
  } else {
46946
47003
  for (const o of originals)
46947
47004
  buffer.push(agent, o);
@@ -47004,14 +47061,14 @@ function mergeRun(run2) {
47004
47061
  merged.attachment = mediaEntry.attachment;
47005
47062
  return merged;
47006
47063
  }
47007
- function idleDrainTick(buffer, agent, isBridgeAlive, send, spool) {
47064
+ function idleDrainTick(buffer, agent, isBridgeAlive, send, spool, onDelivered) {
47008
47065
  if (!agent)
47009
47066
  return null;
47010
47067
  if (buffer.depth(agent) === 0)
47011
47068
  return null;
47012
47069
  if (!isBridgeAlive())
47013
47070
  return null;
47014
- return redeliverBufferedInbound(buffer, agent, send, spool);
47071
+ return redeliverBufferedInbound(buffer, agent, send, spool, onDelivered);
47015
47072
  }
47016
47073
  function createPendingInboundBuffer(opts = {}) {
47017
47074
  const cap = opts.capPerAgent ?? DEFAULT_PENDING_INBOUND_CAP;
@@ -47618,7 +47675,7 @@ function formatEventDetail(event) {
47618
47675
  }
47619
47676
 
47620
47677
  // gateway/pending-inbound-buffer.ts
47621
- function redeliverBufferedInbound2(buffer, agent, send, spool) {
47678
+ function redeliverBufferedInbound2(buffer, agent, send, spool, onDelivered) {
47622
47679
  const pending = buffer.drain(agent);
47623
47680
  let redelivered = 0;
47624
47681
  let rebuffered = 0;
@@ -47633,6 +47690,7 @@ function redeliverBufferedInbound2(buffer, agent, send, spool) {
47633
47690
  for (const o of originals)
47634
47691
  spool?.ack(o);
47635
47692
  redelivered += originals.length;
47693
+ onDelivered?.(merged, originals);
47636
47694
  } else {
47637
47695
  for (const o of originals)
47638
47696
  buffer.push(agent, o);
@@ -47725,7 +47783,7 @@ function dispatchOne(effect, ctx) {
47725
47783
  }
47726
47784
  return ctx.ipcServer.sendToAgent(ctx.selfAgent, msg);
47727
47785
  };
47728
- const result = redeliverBufferedInbound2(ctx.pendingInboundBuffer, ctx.selfAgent, send, ctx.inboundSpool ?? undefined);
47786
+ const result = redeliverBufferedInbound2(ctx.pendingInboundBuffer, ctx.selfAgent, send, ctx.inboundSpool ?? undefined, ctx.onUserInboundDelivered ? (merged) => ctx.onUserInboundDelivered(merged) : undefined);
47729
47787
  if (result.drained > 0) {
47730
47788
  log(`telegram gateway: dispatch drainBuffer agent=${ctx.selfAgent} ` + `drained=${result.drained} redelivered=${result.redelivered} ` + `rebuffered=${result.rebuffered}
47731
47789
  `);
@@ -50437,7 +50495,7 @@ function determineRestartReason(opts) {
50437
50495
  init_boot_card();
50438
50496
 
50439
50497
  // gateway/update-announce.ts
50440
- import { existsSync as existsSync29, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync27 } from "node:fs";
50498
+ import { existsSync as existsSync29, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync28 } from "node:fs";
50441
50499
  import { join as join26 } from "node:path";
50442
50500
  import { homedir as homedir12 } from "node:os";
50443
50501
 
@@ -50552,7 +50610,7 @@ var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
50552
50610
  function readLastTerminalUpdateAudit(opts = {}) {
50553
50611
  const path = opts.auditLogPath ?? defaultAuditLogPath();
50554
50612
  const exists = opts.exists ?? existsSync29;
50555
- const readFile = opts.readFile ?? ((p) => readFileSync27(p, "utf-8"));
50613
+ const readFile = opts.readFile ?? ((p) => readFileSync28(p, "utf-8"));
50556
50614
  if (!exists(path))
50557
50615
  return null;
50558
50616
  let raw;
@@ -50640,7 +50698,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
50640
50698
  }
50641
50699
 
50642
50700
  // issues-card.ts
50643
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "node:fs";
50701
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
50644
50702
  var SEVERITY_EMOJI = {
50645
50703
  info: "\u2139\ufe0f",
50646
50704
  warn: "\u26a0\ufe0f",
@@ -50732,7 +50790,7 @@ function extractRetryAfterSecs2(err) {
50732
50790
  var COOLDOWN_JITTER_MS2 = 500;
50733
50791
  function readPersistedMessageId(path, log) {
50734
50792
  try {
50735
- const raw = readFileSync28(path, "utf8");
50793
+ const raw = readFileSync29(path, "utf8");
50736
50794
  const parsed = JSON.parse(raw);
50737
50795
  const v = parsed.messageId;
50738
50796
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -50748,7 +50806,7 @@ function readPersistedMessageId(path, log) {
50748
50806
  }
50749
50807
  function writePersistedMessageId(path, messageId, log) {
50750
50808
  try {
50751
- writeFileSync17(path, JSON.stringify({ messageId }) + `
50809
+ writeFileSync18(path, JSON.stringify({ messageId }) + `
50752
50810
  `, { mode: 384 });
50753
50811
  } catch (err) {
50754
50812
  log(`issues-card: persist write failed (${err.message})`);
@@ -50851,11 +50909,11 @@ import {
50851
50909
  mkdirSync as mkdirSync18,
50852
50910
  openSync as openSync4,
50853
50911
  readdirSync as readdirSync5,
50854
- readFileSync as readFileSync29,
50912
+ readFileSync as readFileSync30,
50855
50913
  renameSync as renameSync10,
50856
50914
  statSync as statSync7,
50857
50915
  unlinkSync as unlinkSync10,
50858
- writeFileSync as writeFileSync18,
50916
+ writeFileSync as writeFileSync19,
50859
50917
  writeSync
50860
50918
  } from "node:fs";
50861
50919
  import { join as join27 } from "node:path";
@@ -50882,7 +50940,7 @@ function readAll(stateDir) {
50882
50940
  return [];
50883
50941
  let raw;
50884
50942
  try {
50885
- raw = readFileSync29(path, "utf-8");
50943
+ raw = readFileSync30(path, "utf-8");
50886
50944
  } catch {
50887
50945
  return [];
50888
50946
  }
@@ -50938,7 +50996,7 @@ function writeAll(stateDir, events) {
50938
50996
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
50939
50997
  `) + `
50940
50998
  `;
50941
- writeFileSync18(tmp, body, "utf-8");
50999
+ writeFileSync19(tmp, body, "utf-8");
50942
51000
  renameSync10(tmp, path);
50943
51001
  }
50944
51002
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -51001,7 +51059,7 @@ function withLock(stateDir, fn) {
51001
51059
  function tryStealStaleLock(lockPath) {
51002
51060
  let pidStr;
51003
51061
  try {
51004
- pidStr = readFileSync29(lockPath, "utf-8").trim();
51062
+ pidStr = readFileSync30(lockPath, "utf-8").trim();
51005
51063
  } catch {
51006
51064
  return true;
51007
51065
  }
@@ -51779,7 +51837,7 @@ function extractFlowItems(line) {
51779
51837
  }
51780
51838
 
51781
51839
  // credits-watch.ts
51782
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync32, mkdirSync as mkdirSync19 } from "fs";
51840
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync32, mkdirSync as mkdirSync19 } from "fs";
51783
51841
  import { join as join29 } from "path";
51784
51842
  var STATE_FILE = "credits-watch.json";
51785
51843
  var FATAL_REASONS = new Set([
@@ -51797,7 +51855,7 @@ function readClaudeJsonOverage(claudeConfigDir) {
51797
51855
  return null;
51798
51856
  let raw;
51799
51857
  try {
51800
- raw = readFileSync30(path, "utf-8");
51858
+ raw = readFileSync31(path, "utf-8");
51801
51859
  } catch {
51802
51860
  return null;
51803
51861
  }
@@ -51881,7 +51939,7 @@ function loadCreditState(stateDir) {
51881
51939
  if (!existsSync32(path))
51882
51940
  return emptyCreditState();
51883
51941
  try {
51884
- const raw = readFileSync30(path, "utf-8");
51942
+ const raw = readFileSync31(path, "utf-8");
51885
51943
  const parsed = JSON.parse(raw);
51886
51944
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
51887
51945
  return {
@@ -51895,12 +51953,12 @@ function loadCreditState(stateDir) {
51895
51953
  function saveCreditState(stateDir, state4) {
51896
51954
  mkdirSync19(stateDir, { recursive: true });
51897
51955
  const path = join29(stateDir, STATE_FILE);
51898
- writeFileSync19(path, JSON.stringify(state4, null, 2) + `
51956
+ writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51899
51957
  `, { mode: 384 });
51900
51958
  }
51901
51959
 
51902
51960
  // quota-watch.ts
51903
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync20 } from "fs";
51961
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync21, existsSync as existsSync33, mkdirSync as mkdirSync20 } from "fs";
51904
51962
  import { join as join30 } from "path";
51905
51963
  var STATE_FILE2 = "quota-watch.json";
51906
51964
  function emptyQuotaWatchState() {
@@ -51995,7 +52053,7 @@ function loadQuotaWatchState(stateDir) {
51995
52053
  if (!existsSync33(path))
51996
52054
  return emptyQuotaWatchState();
51997
52055
  try {
51998
- const raw = readFileSync31(path, "utf-8");
52056
+ const raw = readFileSync32(path, "utf-8");
51999
52057
  const parsed = JSON.parse(raw);
52000
52058
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
52001
52059
  return emptyQuotaWatchState();
@@ -52014,7 +52072,7 @@ function loadQuotaWatchState(stateDir) {
52014
52072
  function saveQuotaWatchState(stateDir, state4) {
52015
52073
  mkdirSync20(stateDir, { recursive: true });
52016
52074
  const path = join30(stateDir, STATE_FILE2);
52017
- writeFileSync20(path, JSON.stringify(state4, null, 2) + `
52075
+ writeFileSync21(path, JSON.stringify(state4, null, 2) + `
52018
52076
  `, { mode: 384 });
52019
52077
  }
52020
52078
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -52030,18 +52088,18 @@ import {
52030
52088
  existsSync as existsSync34,
52031
52089
  mkdirSync as mkdirSync21,
52032
52090
  openSync as openSync5,
52033
- readFileSync as readFileSync32,
52091
+ readFileSync as readFileSync33,
52034
52092
  statSync as statSync9,
52035
52093
  unlinkSync as unlinkSync11,
52036
52094
  utimesSync as utimesSync2,
52037
- writeFileSync as writeFileSync21
52095
+ writeFileSync as writeFileSync22
52038
52096
  } from "node:fs";
52039
52097
  import { join as join31 } from "node:path";
52040
52098
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
52041
52099
  function writeTurnActiveMarker(stateDir, marker) {
52042
52100
  try {
52043
52101
  mkdirSync21(stateDir, { recursive: true });
52044
- writeFileSync21(join31(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
52102
+ writeFileSync22(join31(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
52045
52103
  `, { mode: 384 });
52046
52104
  } catch {}
52047
52105
  }
@@ -52078,7 +52136,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
52078
52136
  return false;
52079
52137
  let payload = null;
52080
52138
  try {
52081
- payload = readFileSync32(path, "utf8");
52139
+ payload = readFileSync33(path, "utf8");
52082
52140
  } catch {}
52083
52141
  unlinkSync11(path);
52084
52142
  if (opts.onRemove) {
@@ -52097,10 +52155,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
52097
52155
  }
52098
52156
 
52099
52157
  // ../src/build-info.ts
52100
- var VERSION = "0.14.47";
52101
- var COMMIT_SHA = "fbd2e491";
52102
- var COMMIT_DATE = "2026-06-03T07:22:07Z";
52103
- var LATEST_PR = 2119;
52158
+ var VERSION = "0.14.49";
52159
+ var COMMIT_SHA = "df84be56";
52160
+ var COMMIT_DATE = "2026-06-03T09:45:46Z";
52161
+ var LATEST_PR = 2123;
52104
52162
  var COMMITS_AHEAD_OF_TAG = 0;
52105
52163
 
52106
52164
  // gateway/boot-version.ts
@@ -52665,14 +52723,20 @@ function buildResumeWatchdogReportInbound(ctx) {
52665
52723
  meta
52666
52724
  };
52667
52725
  }
52668
- function selectResumeBuilder(endedVia) {
52726
+ function selectResumeBuilder(endedVia, opts) {
52727
+ let kind;
52669
52728
  if (endedVia === "timeout")
52729
+ kind = "report";
52730
+ else if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
52731
+ kind = "resume";
52732
+ else if (endedVia == null)
52733
+ kind = "resume";
52734
+ else
52735
+ kind = null;
52736
+ if (kind === "resume" && opts?.ageMs != null && opts?.maxAgeMs != null && opts.ageMs > opts.maxAgeMs) {
52670
52737
  return "report";
52671
- if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
52672
- return "resume";
52673
- if (endedVia == null)
52674
- return "resume";
52675
- return null;
52738
+ }
52739
+ return kind;
52676
52740
  }
52677
52741
 
52678
52742
  // registry/subagents-schema.ts
@@ -52841,7 +52905,7 @@ function formatBootVersion() {
52841
52905
  }
52842
52906
  try {
52843
52907
  chmodSync6(ENV_FILE, 384);
52844
- for (const line of readFileSync35(ENV_FILE, "utf8").split(`
52908
+ for (const line of readFileSync36(ENV_FILE, "utf8").split(`
52845
52909
  `)) {
52846
52910
  const m = line.match(/^(\w+)=(.*)$/);
52847
52911
  if (m && process.env[m[1]] === undefined)
@@ -52894,7 +52958,7 @@ installTgPostLogger(bot);
52894
52958
  var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
52895
52959
  var GRAMMY_VERSION = (() => {
52896
52960
  try {
52897
- const raw = readFileSync35(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52961
+ const raw = readFileSync36(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52898
52962
  return JSON.parse(raw).version ?? "unknown";
52899
52963
  } catch {
52900
52964
  return "unknown";
@@ -52986,7 +53050,7 @@ function assertSendable(f) {
52986
53050
  }
52987
53051
  function readAccessFile() {
52988
53052
  try {
52989
- const raw = readFileSync35(ACCESS_FILE, "utf8");
53053
+ const raw = readFileSync36(ACCESS_FILE, "utf8");
52990
53054
  const parsed = JSON.parse(raw);
52991
53055
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
52992
53056
  const groups = {};
@@ -53056,7 +53120,7 @@ function saveAccess(a) {
53056
53120
  return;
53057
53121
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
53058
53122
  const tmp = ACCESS_FILE + ".tmp";
53059
- writeFileSync24(tmp, JSON.stringify(a, null, 2) + `
53123
+ writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
53060
53124
  `, { mode: 384 });
53061
53125
  renameSync12(tmp, ACCESS_FILE);
53062
53126
  }
@@ -53097,7 +53161,7 @@ try {
53097
53161
  const st = statSync13(markerPath);
53098
53162
  markerAgeMs = Date.now() - st.mtimeMs;
53099
53163
  try {
53100
- const payload = JSON.parse(readFileSync35(markerPath, "utf8"));
53164
+ const payload = JSON.parse(readFileSync36(markerPath, "utf8"));
53101
53165
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
53102
53166
  markerTurnKey = payload.turnKey;
53103
53167
  }
@@ -53123,7 +53187,14 @@ try {
53123
53187
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
53124
53188
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
53125
53189
  if (pending2 != null && selfAgent) {
53126
- const kind = selectResumeBuilder(pending2.ended_via);
53190
+ const RESUME_MAX_AGE_MS = (() => {
53191
+ const v = Number(process.env.SWITCHROOM_RESUME_MAX_AGE_MS);
53192
+ return Number.isFinite(v) && v > 0 ? v : 10800000;
53193
+ })();
53194
+ const kind = selectResumeBuilder(pending2.ended_via, {
53195
+ ageMs: Math.max(0, Date.now() - pending2.started_at),
53196
+ maxAgeMs: RESUME_MAX_AGE_MS
53197
+ });
53127
53198
  if (kind === "resume") {
53128
53199
  bootResumeInbound = { agent: selfAgent, msg: buildResumeInterruptedInbound({ turn: pending2 }) };
53129
53200
  } else if (kind === "report") {
@@ -53161,7 +53232,7 @@ try {
53161
53232
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
53162
53233
  ];
53163
53234
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
53164
- writeFileSync24(pendingEnvTmp, lines.join(`
53235
+ writeFileSync25(pendingEnvTmp, lines.join(`
53165
53236
  `) + `
53166
53237
  `, { mode: 384 });
53167
53238
  renameSync12(pendingEnvTmp, pendingEnvPath);
@@ -53390,7 +53461,7 @@ function purgeReactionTracking(key, endingTurn) {
53390
53461
  if (d)
53391
53462
  markClaudeBusyForInbound(m);
53392
53463
  return d;
53393
- }, inboundSpool);
53464
+ }, inboundSpool, trackRedeliveredInbound);
53394
53465
  if (fr.redelivered > 0) {
53395
53466
  process.stderr.write(`telegram gateway: turn-complete flushed ${fr.redelivered}/${fr.drained} held inbound for ${selfAgentForFlush}${fr.rebuffered > 0 ? ` (${fr.rebuffered} re-buffered)` : ""}
53396
53467
  `);
@@ -53420,7 +53491,7 @@ function releaseTurnBufferGate(key) {
53420
53491
  if (d)
53421
53492
  markClaudeBusyForInbound(m);
53422
53493
  return d;
53423
- }, inboundSpool);
53494
+ }, inboundSpool, trackRedeliveredInbound);
53424
53495
  if (fr.redelivered > 0) {
53425
53496
  process.stderr.write(`telegram gateway: reply-released-gate flushed ${fr.redelivered}/${fr.drained} held inbound for ${selfAgentForFlush}${fr.rebuffered > 0 ? ` (${fr.rebuffered} re-buffered)` : ""}
53426
53497
  `);
@@ -53783,7 +53854,18 @@ function wrapBootCardApi(threadId) {
53783
53854
  const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid, text, sendOpts), opts(cid));
53784
53855
  return sent;
53785
53856
  },
53786
- editMessageText: (cid, mid, text, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text, editOpts), opts(cid))
53857
+ editMessageText: (cid, mid, text, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text, editOpts), opts(cid)),
53858
+ editMessageTextStrict: async (cid, mid, text, editOpts) => {
53859
+ try {
53860
+ await lockedBot.api.editMessageText(cid, mid, text, editOpts);
53861
+ return "edited";
53862
+ } catch (err) {
53863
+ const desc = err instanceof import_grammy9.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
53864
+ if (typeof desc === "string" && desc.toLowerCase().includes("not modified"))
53865
+ return "edited";
53866
+ return "gone";
53867
+ }
53868
+ }
53787
53869
  };
53788
53870
  }
53789
53871
  function wrapIssuesCardApi(threadId) {
@@ -54406,7 +54488,7 @@ startTimer({
54406
54488
  if (d)
54407
54489
  markClaudeBusyForInbound(m);
54408
54490
  return d;
54409
- }, inboundSpool);
54491
+ }, inboundSpool, trackRedeliveredInbound);
54410
54492
  process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} currentTurn_nulled=${turnMatchesFallback} drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
54411
54493
  `);
54412
54494
  }
@@ -54416,6 +54498,20 @@ var _deliveryMachineTick = setInterval(() => {
54416
54498
  shadowEmit({ kind: "tick", now: Date.now() });
54417
54499
  }, DELIVERY_MACHINE_TICK_MS);
54418
54500
  _deliveryMachineTick.unref?.();
54501
+ function trackRedeliveredInbound(merged) {
54502
+ if (!DELIVERY_CONFIRM_ENABLED)
54503
+ return;
54504
+ if (!shouldTrackDelivery({
54505
+ isSteering: false,
54506
+ isInterrupt: false,
54507
+ hasSource: merged.meta?.source != null,
54508
+ effectiveText: merged.text
54509
+ })) {
54510
+ return;
54511
+ }
54512
+ const key = chatKey2(merged.chatId, merged.threadId != null ? Number(merged.threadId) : null);
54513
+ trackDelivery(deliveryQueue, key, merged, Date.now(), merged.messageId != null ? String(merged.messageId) : null);
54514
+ }
54419
54515
  async function redeliverStrandedInbound(p) {
54420
54516
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
54421
54517
  process.stderr.write(`telegram gateway: inbound strand (no enqueue ack) key=${p.key} \u2014 re-clearing composer + re-delivering
@@ -54467,8 +54563,8 @@ var inboundSpool = STATIC ? undefined : createInboundSpool({
54467
54563
  path: join35(STATE_DIR, "inbound-spool.jsonl"),
54468
54564
  fs: {
54469
54565
  appendFileSync: (p, d) => appendFileSync5(p, d),
54470
- readFileSync: (p) => readFileSync35(p, "utf8"),
54471
- writeFileSync: (p, d) => writeFileSync24(p, d),
54566
+ readFileSync: (p) => readFileSync36(p, "utf8"),
54567
+ writeFileSync: (p, d) => writeFileSync25(p, d),
54472
54568
  renameSync: (a, b) => renameSync12(a, b),
54473
54569
  existsSync: (p) => existsSync38(p),
54474
54570
  statSizeSync: (p) => statSync13(p).size
@@ -54522,7 +54618,8 @@ var ipcServer = createIpcServer({
54522
54618
  pendingInboundBuffer,
54523
54619
  inboundSpool: inboundSpool ?? null,
54524
54620
  pendingPermissionBuffer,
54525
- client: client3
54621
+ client: client3,
54622
+ onUserInboundDelivered: trackRedeliveredInbound
54526
54623
  });
54527
54624
  } else {
54528
54625
  const pending2 = pendingInboundBuffer.drain(client3.agentName);
@@ -54530,6 +54627,7 @@ var ipcServer = createIpcServer({
54530
54627
  try {
54531
54628
  client3.send(msg);
54532
54629
  inboundSpool?.ack(msg);
54630
+ trackRedeliveredInbound(msg);
54533
54631
  } catch (err) {
54534
54632
  process.stderr.write(`telegram gateway: pending-inbound drain failed agent=${client3.agentName} source=${msg.meta?.source ?? "-"}: ${err.message}
54535
54633
  `);
@@ -54611,6 +54709,7 @@ var ipcServer = createIpcServer({
54611
54709
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
54612
54710
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
54613
54711
  configSnapshotPath: join35(resolvedAgentDirForCard, ".config-snapshot.json"),
54712
+ bootCardStatePath: join35(resolvedAgentDirForCard, ".boot-card-msgid.json"),
54614
54713
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
54615
54714
  }, ackMsgId).then((handle) => {
54616
54715
  activeBootCard = handle;
@@ -55085,7 +55184,7 @@ if (!STATIC) {
55085
55184
  if (d)
55086
55185
  markClaudeBusyForInbound(m);
55087
55186
  return d;
55088
- }, inboundSpool);
55187
+ }, inboundSpool, trackRedeliveredInbound);
55089
55188
  if (r != null && r.redelivered > 0) {
55090
55189
  process.stderr.write(`telegram gateway: idle-drain flushed ${r.redelivered}/${r.drained} buffered inbound for ${selfAgent}${r.rebuffered > 0 ? ` (${r.rebuffered} re-buffered)` : ""}
55091
55190
  `);
@@ -56050,7 +56149,7 @@ async function publishToTelegraph(text, shortName, authorName) {
56050
56149
  let account = null;
56051
56150
  try {
56052
56151
  if (existsSync38(accountPath)) {
56053
- const raw = readFileSync35(accountPath, "utf-8");
56152
+ const raw = readFileSync36(accountPath, "utf-8");
56054
56153
  const parsed = JSON.parse(raw);
56055
56154
  if (parsed.shortName && parsed.accessToken) {
56056
56155
  account = parsed;
@@ -56070,7 +56169,7 @@ async function publishToTelegraph(text, shortName, authorName) {
56070
56169
  account = created.value;
56071
56170
  try {
56072
56171
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
56073
- writeFileSync24(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
56172
+ writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
56074
56173
  } catch (err) {
56075
56174
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
56076
56175
  `);
@@ -56544,7 +56643,7 @@ async function executeDownloadAttachment(args) {
56544
56643
  });
56545
56644
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
56546
56645
  assertInsideInbox(INBOX_DIR, dlPath);
56547
- writeFileSync24(dlPath, buf, { mode: 384 });
56646
+ writeFileSync25(dlPath, buf, { mode: 384 });
56548
56647
  return { content: [{ type: "text", text: dlPath }] };
56549
56648
  }
56550
56649
  async function executeEditMessage(args) {
@@ -58433,7 +58532,7 @@ function writeRestartMarker(marker) {
58433
58532
  if (!p)
58434
58533
  return;
58435
58534
  try {
58436
- writeFileSync24(p, JSON.stringify(marker));
58535
+ writeFileSync25(p, JSON.stringify(marker));
58437
58536
  lastPlannedRestartAt = Date.now();
58438
58537
  process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
58439
58538
  `);
@@ -58452,7 +58551,7 @@ function readRestartMarker() {
58452
58551
  if (!p)
58453
58552
  return null;
58454
58553
  try {
58455
- return JSON.parse(readFileSync35(p, "utf8"));
58554
+ return JSON.parse(readFileSync36(p, "utf8"));
58456
58555
  } catch {
58457
58556
  return null;
58458
58557
  }
@@ -58589,7 +58688,7 @@ function spawnSwitchroomDetached(args, onFailure) {
58589
58688
  try {
58590
58689
  mkdirSync26(STATE_DIR, { recursive: true });
58591
58690
  outFd = openSync8(logPath, "a");
58592
- writeFileSync24(logPath, `
58691
+ writeFileSync25(logPath, `
58593
58692
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
58594
58693
  `, { flag: "a" });
58595
58694
  } catch {}
@@ -58615,7 +58714,7 @@ function spawnSwitchroomDetached(args, onFailure) {
58615
58714
  return;
58616
58715
  let tail = "";
58617
58716
  try {
58618
- const full = readFileSync35(logPath, "utf8");
58717
+ const full = readFileSync36(logPath, "utf8");
58619
58718
  tail = full.split(`
58620
58719
  `).slice(-30).join(`
58621
58720
  `).trim();
@@ -58960,7 +59059,7 @@ function readRecentDenialsForAgent(agentName3, windowMs, limit) {
58960
59059
  const auditPath = join35(homedir14(), ".switchroom", "vault-audit.log");
58961
59060
  if (!existsSync38(auditPath))
58962
59061
  return [];
58963
- const raw = readFileSync35(auditPath, "utf8");
59062
+ const raw = readFileSync36(auditPath, "utf8");
58964
59063
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
58965
59064
  } catch {
58966
59065
  return [];
@@ -59011,7 +59110,7 @@ async function buildAgentMetadata(agentName3) {
59011
59110
  try {
59012
59111
  const agentDir = resolveAgentDirFromEnv();
59013
59112
  if (agentDir) {
59014
- const raw = readFileSync35(join35(agentDir, ".claude", ".claude.json"), "utf8");
59113
+ const raw = readFileSync36(join35(agentDir, ".claude", ".claude.json"), "utf8");
59015
59114
  claudeJson = JSON.parse(raw);
59016
59115
  }
59017
59116
  } catch {}
@@ -59283,7 +59382,7 @@ async function handleNewOrResetCommand(ctx, kind) {
59283
59382
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
59284
59383
  if (agentDir != null) {
59285
59384
  try {
59286
- writeFileSync24(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
59385
+ writeFileSync25(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
59287
59386
  `, "utf8");
59288
59387
  } catch (err) {
59289
59388
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -59647,8 +59746,8 @@ bot.command("interrupt", async (ctx) => {
59647
59746
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
59648
59747
  });
59649
59748
  var lockoutOps = {
59650
- readFileSync: (p, enc) => readFileSync35(p, enc),
59651
- writeFileSync: (p, data, opts) => writeFileSync24(p, data, opts),
59749
+ readFileSync: (p, enc) => readFileSync36(p, enc),
59750
+ writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
59652
59751
  existsSync: (p) => existsSync38(p),
59653
59752
  mkdirSync: (p, opts) => mkdirSync26(p, opts),
59654
59753
  joinPath: (...parts) => join35(...parts)
@@ -60023,7 +60122,7 @@ async function handleVaultRecentDenialCallback(ctx, data) {
60023
60122
  const tokenPath = join35(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
60024
60123
  try {
60025
60124
  mkdirSync26(join35(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
60026
- writeFileSync24(tokenPath, token, { mode: 384 });
60125
+ writeFileSync25(tokenPath, token, { mode: 384 });
60027
60126
  } catch (err) {
60028
60127
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
60029
60128
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -60102,7 +60201,7 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
60102
60201
  const tokenPath = join35(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
60103
60202
  try {
60104
60203
  mkdirSync26(join35(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
60105
- writeFileSync24(tokenPath, token, { mode: 384 });
60204
+ writeFileSync25(tokenPath, token, { mode: 384 });
60106
60205
  } catch (err) {
60107
60206
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
60108
60207
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
@@ -60606,7 +60705,7 @@ async function executeGrantWizard(ctx, chatId, state4) {
60606
60705
  const tokenPath = join35(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
60607
60706
  try {
60608
60707
  mkdirSync26(join35(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
60609
- writeFileSync24(tokenPath, token, { mode: 384 });
60708
+ writeFileSync25(tokenPath, token, { mode: 384 });
60610
60709
  } catch (err) {
60611
60710
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
60612
60711
  return;
@@ -61902,7 +62001,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
61902
62001
  const unifiedDiff = (() => {
61903
62002
  try {
61904
62003
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
61905
- const raw = readFileSync35(cfgPath, "utf8");
62004
+ const raw = readFileSync36(cfgPath, "utf8");
61906
62005
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
61907
62006
  } catch (err) {
61908
62007
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -62050,7 +62149,7 @@ bot.on("message:photo", async (ctx) => {
62050
62149
  });
62051
62150
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
62052
62151
  assertInsideInbox(INBOX_DIR, dlPath);
62053
- writeFileSync24(dlPath, buf, { mode: 384 });
62152
+ writeFileSync25(dlPath, buf, { mode: 384 });
62054
62153
  return dlPath;
62055
62154
  } catch (err) {
62056
62155
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -62091,7 +62190,7 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
62091
62190
  try {
62092
62191
  const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
62093
62192
  if (existsSync38(path)) {
62094
- apiKey = readFileSync35(path, "utf-8").trim();
62193
+ apiKey = readFileSync36(path, "utf-8").trim();
62095
62194
  }
62096
62195
  } catch (err) {
62097
62196
  process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
@@ -62961,6 +63060,7 @@ var didOneTimeSetup = false;
62961
63060
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
62962
63061
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
62963
63062
  configSnapshotPath: join35(resolvedAgentDirForBootCard, ".config-snapshot.json"),
63063
+ bootCardStatePath: join35(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
62964
63064
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
62965
63065
  }, ackMsgId);
62966
63066
  activeBootCard = handle;