switchroom 0.19.30 → 0.19.32

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 (51) hide show
  1. package/dist/cli/switchroom.js +1121 -584
  2. package/dist/host-control/main.js +151 -73
  3. package/package.json +1 -1
  4. package/profiles/_base/cron-session.sh.hbs +5 -1
  5. package/profiles/_base/start.sh.hbs +15 -1
  6. package/telegram-plugin/dist/bridge/bridge.js +3 -0
  7. package/telegram-plugin/dist/gateway/gateway.js +1660 -677
  8. package/telegram-plugin/dist/server.js +3 -0
  9. package/telegram-plugin/edit-flood-fuse.ts +70 -20
  10. package/telegram-plugin/gateway/boot-beacon.ts +364 -0
  11. package/telegram-plugin/gateway/boot-sweep-gate.ts +20 -15
  12. package/telegram-plugin/gateway/gateway.ts +87 -88
  13. package/telegram-plugin/gateway/inbound-spool.ts +39 -0
  14. package/telegram-plugin/gateway/narrative-lane.ts +12 -0
  15. package/telegram-plugin/gateway/obligation-store.ts +28 -0
  16. package/telegram-plugin/gateway/stale-pin-sweep-store.ts +221 -0
  17. package/telegram-plugin/gateway/stale-pin-sweep-wiring.ts +211 -0
  18. package/telegram-plugin/gateway/stale-pin-sweep.test.ts +804 -0
  19. package/telegram-plugin/gateway/stale-pin-sweep.ts +1146 -0
  20. package/telegram-plugin/gateway/status-pin-retarget.ts +15 -2
  21. package/telegram-plugin/gateway/status-pin-store.ts +33 -11
  22. package/telegram-plugin/gateway/stream-render.ts +578 -321
  23. package/telegram-plugin/registry/turns-schema.ts +21 -1
  24. package/telegram-plugin/retry-api-call.ts +46 -21
  25. package/telegram-plugin/session-tail.ts +13 -0
  26. package/telegram-plugin/shared/bot-runtime.ts +61 -17
  27. package/telegram-plugin/shared/gw-trace-gate.ts +18 -2
  28. package/telegram-plugin/tests/activity-card-wiring.test.ts +7 -7
  29. package/telegram-plugin/tests/activity-drain-fuse-drop-not-failure.test.ts +324 -0
  30. package/telegram-plugin/tests/agent-card-result-footer.test.ts +193 -0
  31. package/telegram-plugin/tests/boot-beacon.test.ts +462 -0
  32. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +6 -6
  33. package/telegram-plugin/tests/boot-sweep-gate.test.ts +42 -31
  34. package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +2 -0
  35. package/telegram-plugin/tests/inbound-spool-progress.test.ts +2 -0
  36. package/telegram-plugin/tests/inbound-spool.test.ts +134 -5
  37. package/telegram-plugin/tests/narrative-lane-golden.test.ts +28 -0
  38. package/telegram-plugin/tests/obligation-determinism.test.ts +2 -0
  39. package/telegram-plugin/tests/obligation-store.test.ts +67 -1
  40. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  41. package/telegram-plugin/tests/status-pin-store.test.ts +26 -5
  42. package/telegram-plugin/tests/stream-render-golden.test.ts +20 -5
  43. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +161 -0
  44. package/telegram-plugin/tests/turn-mint-defers-until-dequeue.test.ts +196 -0
  45. package/telegram-plugin/tests/turn-mint-harness.ts +155 -0
  46. package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +124 -0
  47. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +30 -0
  48. package/telegram-plugin/tool-activity-summary.ts +104 -38
  49. package/telegram-plugin/worker-activity-feed.ts +33 -16
  50. package/telegram-plugin/gateway/dm-pin-sweep.test.ts +0 -251
  51. package/telegram-plugin/gateway/dm-pin-sweep.ts +0 -178
@@ -13658,7 +13658,7 @@ import {
13658
13658
  mkdirSync as mkdirSync6,
13659
13659
  chmodSync as chmodSync2,
13660
13660
  unlinkSync as unlinkSync3,
13661
- renameSync as renameSync5
13661
+ renameSync as renameSync6
13662
13662
  } from "node:fs";
13663
13663
  import { dirname as dirname5, join as join5 } from "node:path";
13664
13664
  function floodStatePath(stateDir) {
@@ -23318,17 +23318,17 @@ var init_loader = __esm(() => {
23318
23318
  var init_flock = () => {};
23319
23319
 
23320
23320
  // ../src/vault/vault.ts
23321
- import { randomBytes as randomBytes3, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
23321
+ import { randomBytes as randomBytes4, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
23322
23322
  import {
23323
23323
  readFileSync as readFileSync8,
23324
- writeSync,
23324
+ writeSync as writeSync2,
23325
23325
  existsSync as existsSync7,
23326
- renameSync as renameSync6,
23326
+ renameSync as renameSync7,
23327
23327
  mkdirSync as mkdirSync9,
23328
23328
  unlinkSync as unlinkSync5,
23329
- fsyncSync,
23330
- openSync,
23331
- closeSync,
23329
+ fsyncSync as fsyncSync2,
23330
+ openSync as openSync2,
23331
+ closeSync as closeSync2,
23332
23332
  lstatSync,
23333
23333
  realpathSync
23334
23334
  } from "node:fs";
@@ -23414,13 +23414,13 @@ var init_vault = __esm(() => {
23414
23414
  // ../src/vault/resolver.ts
23415
23415
  import {
23416
23416
  chmodSync as chmodSync4,
23417
- closeSync as closeSync2,
23417
+ closeSync as closeSync3,
23418
23418
  mkdirSync as mkdirSync10,
23419
23419
  mkdtempSync,
23420
- openSync as openSync2,
23421
- rmSync,
23420
+ openSync as openSync3,
23421
+ rmSync as rmSync2,
23422
23422
  statSync as statSync6,
23423
- writeSync as writeSync2
23423
+ writeSync as writeSync3
23424
23424
  } from "node:fs";
23425
23425
  import { join as join8 } from "node:path";
23426
23426
  import { tmpdir } from "node:os";
@@ -23448,13 +23448,13 @@ function registerCleanupHook() {
23448
23448
  const cleanup = () => {
23449
23449
  for (const dir of materializedDirs) {
23450
23450
  try {
23451
- rmSync(dir, { recursive: true, force: true });
23451
+ rmSync2(dir, { recursive: true, force: true });
23452
23452
  } catch {}
23453
23453
  }
23454
23454
  materializedDirs.clear();
23455
23455
  if (cachedRoot) {
23456
23456
  try {
23457
- rmSync(cachedRoot, { recursive: true, force: true });
23457
+ rmSync2(cachedRoot, { recursive: true, force: true });
23458
23458
  } catch {}
23459
23459
  cachedRoot = null;
23460
23460
  }
@@ -23485,18 +23485,18 @@ function materializationRoot() {
23485
23485
  }
23486
23486
  function writeFileExclusive(filePath, content) {
23487
23487
  const buf = typeof content === "string" ? Buffer.from(content, "utf8") : content;
23488
- const fd = openSync2(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
23488
+ const fd = openSync3(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
23489
23489
  try {
23490
- writeSync2(fd, buf);
23490
+ writeSync3(fd, buf);
23491
23491
  } finally {
23492
- closeSync2(fd);
23492
+ closeSync3(fd);
23493
23493
  }
23494
23494
  }
23495
23495
  function materializeFilesEntry(key, files) {
23496
23496
  const dir = join8(materializationRoot(), key);
23497
23497
  if (materializedDirs.has(dir)) {
23498
23498
  try {
23499
- rmSync(dir, { recursive: true, force: true });
23499
+ rmSync2(dir, { recursive: true, force: true });
23500
23500
  } catch {}
23501
23501
  }
23502
23502
  mkdirSync10(dir, { recursive: true, mode: 448 });
@@ -23637,21 +23637,21 @@ var init_resolver = __esm(() => {
23637
23637
  });
23638
23638
 
23639
23639
  // ../src/util/atomic.ts
23640
- import { randomBytes as randomBytes4 } from "node:crypto";
23641
- import { closeSync as closeSync3, constants, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync3, renameSync as renameSync7, rmSync as rmSync2, writeSync as writeSync3 } from "node:fs";
23642
- function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
23640
+ import { randomBytes as randomBytes5 } from "node:crypto";
23641
+ 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";
23642
+ function atomicWriteFileSync2(destPath, contents, modeOrOpts = 384) {
23643
23643
  const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
23644
23644
  const mode = opts.mode ?? 384;
23645
- const tmp = `${destPath}.tmp-${process.pid}-${randomBytes4(4).toString("hex")}`;
23645
+ const tmp = `${destPath}.tmp-${process.pid}-${randomBytes5(4).toString("hex")}`;
23646
23646
  const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
23647
23647
  let fd = null;
23648
23648
  try {
23649
- fd = openSync3(tmp, TMP_OPEN_FLAGS, mode);
23650
- writeSync3(fd, buf, 0, buf.length, 0);
23651
- fchmodSync(fd, mode);
23649
+ fd = openSync4(tmp, TMP_OPEN_FLAGS2, mode);
23650
+ writeSync4(fd, buf, 0, buf.length, 0);
23651
+ fchmodSync2(fd, mode);
23652
23652
  if (opts.uid !== undefined) {
23653
23653
  try {
23654
- fchownSync(fd, opts.uid, opts.gid ?? opts.uid);
23654
+ fchownSync2(fd, opts.uid, opts.gid ?? opts.uid);
23655
23655
  } catch (chownErr) {
23656
23656
  if (opts.onChownError)
23657
23657
  opts.onChownError(chownErr);
@@ -23659,25 +23659,25 @@ function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
23659
23659
  throw chownErr;
23660
23660
  }
23661
23661
  }
23662
- fsyncSync2(fd);
23663
- closeSync3(fd);
23662
+ fsyncSync3(fd);
23663
+ closeSync4(fd);
23664
23664
  fd = null;
23665
- renameSync7(tmp, destPath);
23665
+ renameSync8(tmp, destPath);
23666
23666
  } catch (err) {
23667
23667
  if (fd !== null) {
23668
23668
  try {
23669
- closeSync3(fd);
23669
+ closeSync4(fd);
23670
23670
  } catch {}
23671
23671
  }
23672
23672
  try {
23673
- rmSync2(tmp, { force: true });
23673
+ rmSync3(tmp, { force: true });
23674
23674
  } catch {}
23675
23675
  throw err;
23676
23676
  }
23677
23677
  }
23678
- var TMP_OPEN_FLAGS;
23678
+ var TMP_OPEN_FLAGS2;
23679
23679
  var init_atomic = __esm(() => {
23680
- TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
23680
+ TMP_OPEN_FLAGS2 = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
23681
23681
  });
23682
23682
 
23683
23683
  // gateway/approval-card.ts
@@ -35701,7 +35701,7 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35701
35701
 
35702
35702
  // gateway/quota-cache.ts
35703
35703
  import { existsSync as existsSync44, readFileSync as readFileSync44, writeFileSync as writeFileSync37, mkdirSync as mkdirSync39 } from "fs";
35704
- import { join as join53, dirname as dirname20 } from "path";
35704
+ import { join as join53, dirname as dirname22 } from "path";
35705
35705
  function defaultCachePath() {
35706
35706
  return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join53(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35707
35707
  }
@@ -35738,7 +35738,7 @@ function writeQuotaCache(result, opts = {}) {
35738
35738
  result
35739
35739
  };
35740
35740
  try {
35741
- mkdirSync39(dirname20(path3), { recursive: true });
35741
+ mkdirSync39(dirname22(path3), { recursive: true });
35742
35742
  writeFileSync37(path3, JSON.stringify(entry, null, 2), { mode: 384 });
35743
35743
  } catch {}
35744
35744
  }
@@ -36610,8 +36610,8 @@ var init_boot_probes = __esm(() => {
36610
36610
  });
36611
36611
 
36612
36612
  // gateway/boot-issue-cache.ts
36613
- import { existsSync as existsSync46, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, renameSync as renameSync21 } from "fs";
36614
- import { dirname as dirname21 } from "path";
36613
+ import { existsSync as existsSync46, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, renameSync as renameSync22 } from "fs";
36614
+ import { dirname as dirname23 } from "path";
36615
36615
  function fingerprintProbe(key, r) {
36616
36616
  if (r.status === "ok")
36617
36617
  return `${key}:ok`;
@@ -36703,7 +36703,7 @@ function loadCache(path3, now = Date.now) {
36703
36703
  parsed = JSON.parse(raw);
36704
36704
  } catch {
36705
36705
  try {
36706
- renameSync21(path3, `${path3}.corrupt-${now()}`);
36706
+ renameSync22(path3, `${path3}.corrupt-${now()}`);
36707
36707
  } catch {}
36708
36708
  return { ...EMPTY_CACHE, probes: {} };
36709
36709
  }
@@ -36737,10 +36737,10 @@ function applyAndSave(path3, cache, diff) {
36737
36737
  }
36738
36738
  }
36739
36739
  try {
36740
- mkdirSync40(dirname21(path3), { recursive: true });
36740
+ mkdirSync40(dirname23(path3), { recursive: true });
36741
36741
  const tmp = `${path3}.tmp`;
36742
36742
  writeFileSync38(tmp, JSON.stringify(next), { mode: 384 });
36743
- renameSync21(tmp, path3);
36743
+ renameSync22(tmp, path3);
36744
36744
  } catch {}
36745
36745
  return next;
36746
36746
  }
@@ -36753,8 +36753,8 @@ var init_boot_issue_cache = __esm(() => {
36753
36753
 
36754
36754
  // gateway/config-snapshot.ts
36755
36755
  import { createHash as createHash7 } from "crypto";
36756
- import { existsSync as existsSync47, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync41, renameSync as renameSync22 } from "fs";
36757
- import { dirname as dirname22 } from "path";
36756
+ import { existsSync as existsSync47, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync41, renameSync as renameSync23 } from "fs";
36757
+ import { dirname as dirname24 } from "path";
36758
36758
  function hashStringArray(items) {
36759
36759
  if (!items || items.length === 0)
36760
36760
  return null;
@@ -36831,7 +36831,7 @@ function loadSnapshot(path3, now = Date.now) {
36831
36831
  parsed = JSON.parse(raw);
36832
36832
  } catch {
36833
36833
  try {
36834
- renameSync22(path3, `${path3}.corrupt-${now()}`);
36834
+ renameSync23(path3, `${path3}.corrupt-${now()}`);
36835
36835
  } catch {}
36836
36836
  return null;
36837
36837
  }
@@ -36852,10 +36852,10 @@ function loadSnapshot(path3, now = Date.now) {
36852
36852
  }
36853
36853
  function persistSnapshot(path3, snapshot) {
36854
36854
  try {
36855
- mkdirSync41(dirname22(path3), { recursive: true });
36855
+ mkdirSync41(dirname24(path3), { recursive: true });
36856
36856
  const tmp = `${path3}.tmp`;
36857
36857
  writeFileSync39(tmp, JSON.stringify(snapshot), { mode: 384 });
36858
- renameSync22(tmp, path3);
36858
+ renameSync23(tmp, path3);
36859
36859
  } catch {}
36860
36860
  }
36861
36861
  var init_config_snapshot = __esm(() => {
@@ -37920,7 +37920,7 @@ __export(exports_tmux2, {
37920
37920
  captureAgentPane: () => captureAgentPane2
37921
37921
  });
37922
37922
  import { execFileSync as execFileSync8 } from "node:child_process";
37923
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync49, readdirSync as readdirSync15, statSync as statSync21, unlinkSync as unlinkSync28, writeFileSync as writeFileSync48 } from "node:fs";
37923
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync50, readdirSync as readdirSync15, statSync as statSync21, unlinkSync as unlinkSync29, writeFileSync as writeFileSync49 } from "node:fs";
37924
37924
  import { resolve as resolve12 } from "node:path";
37925
37925
  function captureAgentPane2(opts) {
37926
37926
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -37932,7 +37932,7 @@ function captureAgentPane2(opts) {
37932
37932
  const reasonSlug = sanitizeReason2(reason);
37933
37933
  const outPath = resolve12(outDir, `${ts}-${reasonSlug}.txt`);
37934
37934
  try {
37935
- mkdirSync49(outDir, { recursive: true, mode: 448 });
37935
+ mkdirSync50(outDir, { recursive: true, mode: 448 });
37936
37936
  } catch (err) {
37937
37937
  const msg = `mkdir crash-reports failed: ${err.message}`;
37938
37938
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -37971,7 +37971,7 @@ function captureAgentPane2(opts) {
37971
37971
  ` + `
37972
37972
  `;
37973
37973
  try {
37974
- writeFileSync48(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37974
+ writeFileSync49(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37975
37975
  mode: 384
37976
37976
  });
37977
37977
  } catch (err) {
@@ -38055,7 +38055,7 @@ function pruneOldReports2(dir, retain) {
38055
38055
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
38056
38056
  for (const stale of files.slice(retain)) {
38057
38057
  try {
38058
- unlinkSync28(stale.full);
38058
+ unlinkSync29(stale.full);
38059
38059
  } catch {}
38060
38060
  }
38061
38061
  }
@@ -38078,9 +38078,9 @@ __export(exports_config_approval_handler, {
38078
38078
  _peekPendingConfigApprovalForTest: () => _peekPendingConfigApprovalForTest,
38079
38079
  DIFF_SENTINEL: () => DIFF_SENTINEL
38080
38080
  });
38081
- import { randomBytes as randomBytes11 } from "node:crypto";
38081
+ import { randomBytes as randomBytes12 } from "node:crypto";
38082
38082
  function defaultEpoch() {
38083
- return randomBytes11(4).toString("hex");
38083
+ return randomBytes12(4).toString("hex");
38084
38084
  }
38085
38085
  function truncateDiffForCard(unifiedDiff, maxLines = 50, maxChars = 3000) {
38086
38086
  const sentinel = `
@@ -39021,29 +39021,80 @@ var init_approvals_commands = __esm(() => {
39021
39021
  // gateway/gateway.ts
39022
39022
  var import_grammy16 = __toESM(require_mod(), 1);
39023
39023
  var import_runner3 = __toESM(require_mod3(), 1);
39024
- import { randomBytes as randomBytes12, createHash as createHash8 } from "crypto";
39024
+ import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39025
39025
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
39026
39026
  import {
39027
- readFileSync as readFileSync59,
39028
- writeFileSync as writeFileSync49,
39029
- mkdirSync as mkdirSync50,
39027
+ readFileSync as readFileSync60,
39028
+ writeFileSync as writeFileSync50,
39029
+ mkdirSync as mkdirSync51,
39030
39030
  readdirSync as readdirSync16,
39031
- rmSync as rmSync7,
39031
+ rmSync as rmSync8,
39032
39032
  statSync as statSync22,
39033
- renameSync as renameSync25,
39033
+ renameSync as renameSync27,
39034
39034
  realpathSync as realpathSync4,
39035
39035
  chmodSync as chmodSync14,
39036
- openSync as openSync13,
39037
- closeSync as closeSync13,
39036
+ openSync as openSync15,
39037
+ closeSync as closeSync15,
39038
39038
  existsSync as existsSync57,
39039
- unlinkSync as unlinkSync29,
39039
+ unlinkSync as unlinkSync30,
39040
39040
  appendFileSync as appendFileSync9
39041
39041
  } from "fs";
39042
+
39043
+ // ../src/util/atomic.ts
39044
+ import { randomBytes } from "node:crypto";
39045
+ import { closeSync, constants, fchmodSync, fchownSync, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
39046
+ var TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
39047
+ function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
39048
+ const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
39049
+ const mode = opts.mode ?? 384;
39050
+ const tmp = `${destPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
39051
+ const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
39052
+ let fd = null;
39053
+ try {
39054
+ fd = openSync(tmp, TMP_OPEN_FLAGS, mode);
39055
+ writeSync(fd, buf, 0, buf.length, 0);
39056
+ fchmodSync(fd, mode);
39057
+ if (opts.uid !== undefined) {
39058
+ try {
39059
+ fchownSync(fd, opts.uid, opts.gid ?? opts.uid);
39060
+ } catch (chownErr) {
39061
+ if (opts.onChownError)
39062
+ opts.onChownError(chownErr);
39063
+ else
39064
+ throw chownErr;
39065
+ }
39066
+ }
39067
+ fsyncSync(fd);
39068
+ closeSync(fd);
39069
+ fd = null;
39070
+ renameSync(tmp, destPath);
39071
+ } catch (err) {
39072
+ if (fd !== null) {
39073
+ try {
39074
+ closeSync(fd);
39075
+ } catch {}
39076
+ }
39077
+ try {
39078
+ rmSync(tmp, { force: true });
39079
+ } catch {}
39080
+ throw err;
39081
+ }
39082
+ }
39083
+ function fsyncPathSync(path) {
39084
+ const fd = openSync(path, constants.O_RDONLY);
39085
+ try {
39086
+ fsyncSync(fd);
39087
+ } finally {
39088
+ closeSync(fd);
39089
+ }
39090
+ }
39091
+
39092
+ // gateway/gateway.ts
39042
39093
  import { homedir as homedir19 } from "os";
39043
- import { join as join67, sep as sep4, basename as basename17 } from "path";
39094
+ import { join as join68, sep as sep4, basename as basename17 } from "path";
39044
39095
 
39045
39096
  // plugin-logger.ts
39046
- import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
39097
+ import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
39047
39098
  import { homedir } from "os";
39048
39099
  import { dirname, join } from "path";
39049
39100
  var DEFAULT_LOG_PATH = join(homedir(), ".switchroom", "logs", "telegram-plugin.log");
@@ -39071,11 +39122,11 @@ function rotateIfNeeded(path) {
39071
39122
  const dst = `${path}.${i + 1}`;
39072
39123
  try {
39073
39124
  if (existsSync(src))
39074
- renameSync(src, dst);
39125
+ renameSync2(src, dst);
39075
39126
  } catch {}
39076
39127
  }
39077
39128
  const backup = `${path}.1`;
39078
- renameSync(path, backup);
39129
+ renameSync2(path, backup);
39079
39130
  } catch {}
39080
39131
  }
39081
39132
  function installPluginLogger(env = process.env) {
@@ -39182,7 +39233,7 @@ function redactAuthCodeMessage(api, chatId, messageId, log) {
39182
39233
  }
39183
39234
 
39184
39235
  // ask-user.ts
39185
- import { randomBytes } from "crypto";
39236
+ import { randomBytes as randomBytes2 } from "crypto";
39186
39237
  var ASK_USER_DEFAULT_TIMEOUT_MS = 300000;
39187
39238
  var ASK_USER_MAX_TIMEOUT_MS = 1800000;
39188
39239
  var ASK_USER_MIN_TIMEOUT_MS = 5000;
@@ -39244,7 +39295,7 @@ function validateAskUserArgs(args) {
39244
39295
  };
39245
39296
  }
39246
39297
  function generateAskId() {
39247
- return randomBytes(4).toString("hex");
39298
+ return randomBytes2(4).toString("hex");
39248
39299
  }
39249
39300
  function encodeAskCallback(askId, idx) {
39250
39301
  if (!/^[0-9a-f]{8}$/.test(askId)) {
@@ -39791,7 +39842,7 @@ async function synthesizeViaSidecar(args) {
39791
39842
  }
39792
39843
 
39793
39844
  // voice-ondemand.ts
39794
- import { readFileSync, writeFileSync, renameSync as renameSync2, mkdirSync as mkdirSync2 } from "fs";
39845
+ import { readFileSync, writeFileSync, renameSync as renameSync3, mkdirSync as mkdirSync2 } from "fs";
39795
39846
  import { dirname as dirname2 } from "path";
39796
39847
  var VOICE_ONDEMAND_TTL_MS = 7 * 24 * 60 * 60 * 1000;
39797
39848
  var VOICE_ONDEMAND_MAX_ENTRIES = 500;
@@ -39915,7 +39966,7 @@ class VoiceOnDemandCache {
39915
39966
  try {
39916
39967
  mkdirSync2(dirname2(this.persistPath), { recursive: true });
39917
39968
  writeFileSync(tmp, JSON.stringify(doc), "utf8");
39918
- renameSync2(tmp, this.persistPath);
39969
+ renameSync3(tmp, this.persistPath);
39919
39970
  } catch (err) {
39920
39971
  process.stderr.write(`voice-ondemand: failed to persist cache ${this.persistPath}: ${String(err)}
39921
39972
  `);
@@ -39924,8 +39975,8 @@ class VoiceOnDemandCache {
39924
39975
  }
39925
39976
 
39926
39977
  // voice-ondemand.ts
39927
- import { randomBytes as randomBytes2 } from "crypto";
39928
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync3, mkdirSync as mkdirSync3 } from "fs";
39978
+ import { randomBytes as randomBytes3 } from "crypto";
39979
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync4, mkdirSync as mkdirSync3 } from "fs";
39929
39980
  import { dirname as dirname3 } from "path";
39930
39981
  var VOICE_ONDEMAND_CALLBACK_PREFIX = "voice:";
39931
39982
  var VOICE_ONDEMAND_TTL_MS2 = 7 * 24 * 60 * 60 * 1000;
@@ -40050,7 +40101,7 @@ class VoiceOnDemandCache2 {
40050
40101
  try {
40051
40102
  mkdirSync3(dirname3(this.persistPath), { recursive: true });
40052
40103
  writeFileSync2(tmp, JSON.stringify(doc), "utf8");
40053
- renameSync3(tmp, this.persistPath);
40104
+ renameSync4(tmp, this.persistPath);
40054
40105
  } catch (err) {
40055
40106
  process.stderr.write(`voice-ondemand: failed to persist cache ${this.persistPath}: ${String(err)}
40056
40107
  `);
@@ -40058,7 +40109,7 @@ class VoiceOnDemandCache2 {
40058
40109
  }
40059
40110
  }
40060
40111
  function mintVoiceOnDemandToken() {
40061
- return randomBytes2(4).toString("hex");
40112
+ return randomBytes3(4).toString("hex");
40062
40113
  }
40063
40114
  function isVoiceOnDemandCallback(data) {
40064
40115
  return data.startsWith(VOICE_ONDEMAND_CALLBACK_PREFIX);
@@ -40124,7 +40175,7 @@ function makeOutboxListenMarkupResolver(deps) {
40124
40175
  }
40125
40176
 
40126
40177
  // voice-presynth.ts
40127
- import { readdirSync, statSync as statSync2, unlinkSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, renameSync as renameSync4 } from "fs";
40178
+ import { readdirSync, statSync as statSync2, unlinkSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, renameSync as renameSync5 } from "fs";
40128
40179
  import { join as join2 } from "path";
40129
40180
  var VOICE_FILE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
40130
40181
  var VOICE_CACHE_MAX_BYTES = 500 * 1024 * 1024;
@@ -40190,7 +40241,7 @@ function writeVoiceCacheFile(dir, token, audio) {
40190
40241
  const final = voiceCacheFilePath(dir, token);
40191
40242
  const tmp = `${final}.tmp`;
40192
40243
  writeFileSync3(tmp, audio);
40193
- renameSync4(tmp, final);
40244
+ renameSync5(tmp, final);
40194
40245
  return final;
40195
40246
  }
40196
40247
  function sweepVoiceCacheDir(options) {
@@ -42779,6 +42830,18 @@ import { AsyncLocalStorage } from "async_hooks";
42779
42830
 
42780
42831
  // retry-api-call.ts
42781
42832
  var import_grammy2 = __toESM(require_mod2(), 1);
42833
+ function classifyBenignTelegram400(errorCode, description) {
42834
+ if (errorCode !== 400)
42835
+ return null;
42836
+ const desc = (description ?? "").toLowerCase();
42837
+ if (desc.includes("not modified"))
42838
+ return "not_modified";
42839
+ if (desc.includes("message to edit not found"))
42840
+ return "message_not_found";
42841
+ if (desc.includes("message to delete not found"))
42842
+ return "delete_not_found";
42843
+ return null;
42844
+ }
42782
42845
  function isLocalResourceError(err) {
42783
42846
  const code = err?.code;
42784
42847
  if (typeof code === "string" && ["ENOSPC", "EDQUOT", "EIO", "ENOMEM"].includes(code)) {
@@ -42863,14 +42926,9 @@ function createRetryApiCall(config = {}) {
42863
42926
  await sleep(delayMs);
42864
42927
  continue;
42865
42928
  }
42866
- if (isGrammyErr && err.error_code === 400 && desc.includes("not modified")) {
42867
- observer?.onBenign?.({ kind: "not_modified" });
42868
- return;
42869
- }
42870
- if (isGrammyErr && err.error_code === 400 && (desc.includes("message to edit not found") || desc.includes("message to delete not found"))) {
42871
- observer?.onBenign?.({
42872
- kind: desc.includes("edit") ? "message_not_found" : "delete_not_found"
42873
- });
42929
+ const benignKind = isGrammyErr ? classifyBenignTelegram400(err.error_code, desc) : null;
42930
+ if (benignKind !== null) {
42931
+ observer?.onBenign?.({ kind: benignKind });
42874
42932
  return;
42875
42933
  }
42876
42934
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
@@ -44674,12 +44732,12 @@ async function handleVoiceMessage(ctx, deps) {
44674
44732
  }
44675
44733
 
44676
44734
  // gateway/status-pin-store.ts
44677
- var SNAPSHOT_VERSIONS = new Set([1, 2, 3]);
44735
+ var SNAPSHOT_VERSIONS = new Set([1, 2, 3, 4]);
44678
44736
  function isPinRow(x) {
44679
44737
  if (x == null || typeof x !== "object")
44680
44738
  return false;
44681
44739
  const o = x;
44682
- return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number") && (o.pinnedAt === undefined || typeof o.pinnedAt === "number");
44740
+ return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && (o.threadId === undefined || typeof o.threadId === "number") && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number") && (o.pinnedAt === undefined || typeof o.pinnedAt === "number");
44683
44741
  }
44684
44742
  function loadStatusPins(path, fs2) {
44685
44743
  if (!fs2.existsSync(path))
@@ -44704,7 +44762,7 @@ function loadStatusPins(path, fs2) {
44704
44762
  return env.pins.filter(isPinRow);
44705
44763
  }
44706
44764
  function persistStatusPins(path, fs2, snapshot, log = (l) => process.stderr.write(l)) {
44707
- const env = { v: 3, pins: [...snapshot] };
44765
+ const env = { v: 4, pins: [...snapshot] };
44708
44766
  const tmp = path + ".tmp";
44709
44767
  try {
44710
44768
  fs2.writeFileSync(tmp, JSON.stringify(env));
@@ -44740,7 +44798,7 @@ function applyStatusPinRow(path, fs2, pinKey, row, log) {
44740
44798
  persistStatusPins(path, fs2, next, log);
44741
44799
  }
44742
44800
  function reconcileAndPersistStatusPin(args) {
44743
- const { path, fs: fs2, pinKey, chatId, op } = args;
44801
+ const { path, fs: fs2, pinKey, chatId, threadId, op } = args;
44744
44802
  const log = args.log ?? ((l) => process.stderr.write(l));
44745
44803
  const now = args.now ?? Date.now();
44746
44804
  const claimAge = (messageId) => {
@@ -44750,20 +44808,20 @@ function reconcileAndPersistStatusPin(args) {
44750
44808
  return withStoreLock(path, async () => {
44751
44809
  if (op.kind === "pin") {
44752
44810
  const pinnedAt = claimAge(op.messageId);
44753
- applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: op.messageId, pending: true, pinnedAt }, log);
44811
+ applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, threadId, messageId: op.messageId, pending: true, pinnedAt }, log);
44754
44812
  const next2 = await args.applyPin();
44755
44813
  if (next2 == null) {
44756
44814
  applyStatusPinRow(path, fs2, pinKey, null, log);
44757
44815
  return null;
44758
44816
  }
44759
- applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: next2.messageId, pinnedAt: claimAge(next2.messageId) }, log);
44817
+ applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, threadId, messageId: next2.messageId, pinnedAt: claimAge(next2.messageId) }, log);
44760
44818
  return next2;
44761
44819
  }
44762
44820
  const next = await args.applyPin();
44763
44821
  if (next == null) {
44764
44822
  applyStatusPinRow(path, fs2, pinKey, null, log);
44765
44823
  } else {
44766
- applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: next.messageId, pinnedAt: claimAge(next.messageId) }, log);
44824
+ applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, threadId, messageId: next.messageId, pinnedAt: claimAge(next.messageId) }, log);
44767
44825
  }
44768
44826
  return next;
44769
44827
  });
@@ -47361,6 +47419,7 @@ if (isMain)
47361
47419
 
47362
47420
  // tool-activity-summary.ts
47363
47421
  init_card_format();
47422
+ init_redact();
47364
47423
 
47365
47424
  // tool-names.ts
47366
47425
  var TELEGRAM_TOOL_PREFIX_RE = /^mcp__[^_].*?telegram__/;
@@ -47491,12 +47550,21 @@ function renderStatusCard(opts) {
47491
47550
  }
47492
47551
  var WORKER_RESULT_RULE = "\u2500\u2500\u2500\u2500\u2500";
47493
47552
  var WORKER_RESULT_MAX = 320;
47494
- function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, header) {
47495
- if (lines.length === 0 && header == null)
47553
+ function deriveCardResult(state, summary) {
47554
+ if (state !== "done" && state !== "failed")
47555
+ return;
47556
+ const text = cleanWorkerResultParagraph(redact(summary ?? ""));
47557
+ if (text.length === 0)
47558
+ return;
47559
+ return { emoji: state === "done" ? "\u2705" : "\u26a0\ufe0f", text };
47560
+ }
47561
+ var AGENT_CARD_EMOJI = "\uD83E\uDD16";
47562
+ function renderAgentCard(lines, children, final, liveSuffix, stepCount, header) {
47563
+ if (lines.length === 0 && children.length === 0 && header == null)
47496
47564
  return null;
47497
47565
  return renderStatusCard({
47498
47566
  header: header != null ? {
47499
- emoji: "\uD83E\uDD16",
47567
+ emoji: AGENT_CARD_EMOJI,
47500
47568
  label: header.label,
47501
47569
  elapsedMs: header.elapsedMs,
47502
47570
  toolCount: header.toolCount,
@@ -47505,31 +47573,19 @@ function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, he
47505
47573
  totalTokens: header.totalTokens
47506
47574
  } : undefined,
47507
47575
  steps: lines,
47576
+ ...children.length > 0 ? { childSteps: children } : {},
47508
47577
  final,
47509
47578
  liveSuffix,
47510
- stepCount
47579
+ stepCount,
47580
+ result: final && header != null ? deriveCardResult(header.state, header.resultText) : undefined
47511
47581
  });
47512
47582
  }
47583
+ function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, header) {
47584
+ return renderAgentCard(lines, [], final, liveSuffix, stepCount, header);
47585
+ }
47513
47586
  function renderActivityFeedWithNested(lines, childLines, final = false, liveSuffix = "", stepCount, header) {
47514
47587
  const children = childLines.map((s) => s.trim()).filter((s) => s.length > 0);
47515
- if (children.length === 0)
47516
- return renderActivityFeed(lines, final, liveSuffix, stepCount, header);
47517
- return renderStatusCard({
47518
- header: header != null ? {
47519
- emoji: "\uD83E\uDD16",
47520
- label: header.label,
47521
- elapsedMs: header.elapsedMs,
47522
- toolCount: header.toolCount,
47523
- state: header.state,
47524
- model: header.model,
47525
- totalTokens: header.totalTokens
47526
- } : undefined,
47527
- steps: lines,
47528
- childSteps: children,
47529
- final,
47530
- liveSuffix,
47531
- stepCount
47532
- });
47588
+ return renderAgentCard(lines, children, final, liveSuffix, stepCount, header);
47533
47589
  }
47534
47590
  var COMBINED_ROW_DESC_MAX = 72;
47535
47591
  var PER_WORKER_HEADER_COST = 1;
@@ -47651,12 +47707,7 @@ function renderWorkerActivity(v, liveSuffix = "") {
47651
47707
  model: v.model,
47652
47708
  totalTokens: v.totalTokens
47653
47709
  };
47654
- let result;
47655
- if (finished && v.state !== "incomplete") {
47656
- const text = cleanWorkerResultParagraph(v.latestSummary);
47657
- if (text.length > 0)
47658
- result = { emoji: v.state === "done" ? "\u2705" : "\u26a0\ufe0f", text };
47659
- }
47710
+ const result = deriveCardResult(v.state, v.latestSummary);
47660
47711
  const card = renderStatusCard({
47661
47712
  header,
47662
47713
  steps,
@@ -47734,7 +47785,7 @@ function createWorkerActivityFeed(opts) {
47734
47785
  }
47735
47786
  let heartbeatTimer = null;
47736
47787
  function feedKeyOf(chatId, threadId) {
47737
- return `${chatId} ${threadId ?? ""}`;
47788
+ return `${chatId}:${threadId ?? "-"}`;
47738
47789
  }
47739
47790
  function groupOfAgent(agentId) {
47740
47791
  const key = agentIndex.get(agentId);
@@ -48768,8 +48819,8 @@ import { unlinkSync as unlinkSync7 } from "node:fs";
48768
48819
  import { join as join12 } from "node:path";
48769
48820
 
48770
48821
  // gateway/store-file.ts
48771
- import { randomBytes as randomBytes5 } from "node:crypto";
48772
- import { readFileSync as readFileSync12, readdirSync as readdirSync4, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync7 } from "node:fs";
48822
+ import { randomBytes as randomBytes6 } from "node:crypto";
48823
+ import { readFileSync as readFileSync12, readdirSync as readdirSync4, renameSync as renameSync9, rmSync as rmSync4, statSync as statSync7 } from "node:fs";
48773
48824
  import { basename as basename6, dirname as dirname6, join as join11 } from "node:path";
48774
48825
  var defaultLog = (line) => {
48775
48826
  process.stderr.write(line);
@@ -48778,7 +48829,7 @@ var MAX_QUARANTINED_COPIES = 5;
48778
48829
  var quarantineSeq = 0;
48779
48830
  function quarantinePath(filePath) {
48780
48831
  const seq = String(quarantineSeq++).padStart(6, "0");
48781
- return `${filePath}.corrupt-${Date.now()}-${seq}-${randomBytes5(4).toString("hex")}`;
48832
+ return `${filePath}.corrupt-${Date.now()}-${seq}-${randomBytes6(4).toString("hex")}`;
48782
48833
  }
48783
48834
  function reapOldQuarantines(filePath, log) {
48784
48835
  const dir = dirname6(filePath);
@@ -48792,7 +48843,7 @@ function reapOldQuarantines(filePath, log) {
48792
48843
  return { name, mtimeNs };
48793
48844
  }).sort((a, b) => a.mtimeNs === b.mtimeNs ? a.name.localeCompare(b.name) : a.mtimeNs < b.mtimeNs ? -1 : 1);
48794
48845
  for (const stale of copies.slice(0, Math.max(0, copies.length - MAX_QUARANTINED_COPIES))) {
48795
- rmSync3(join11(dir, stale.name), { force: true });
48846
+ rmSync4(join11(dir, stale.name), { force: true });
48796
48847
  }
48797
48848
  } catch (err) {
48798
48849
  log(`telegram gateway: quarantine reap failed dir=${dir}: ${err.message}
@@ -48803,7 +48854,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
48803
48854
  const target = quarantinePath(filePath);
48804
48855
  let preserved = target;
48805
48856
  try {
48806
- renameSync8(filePath, target);
48857
+ renameSync9(filePath, target);
48807
48858
  } catch (err) {
48808
48859
  preserved = `NOT preserved (${err.message})`;
48809
48860
  }
@@ -48814,7 +48865,7 @@ function quarantineCorruptStoreFile(filePath, store2, reason, log = defaultLog)
48814
48865
  function preserveUnreadableStoreFile(filePath, store2, log = defaultLog) {
48815
48866
  const target = quarantinePath(filePath);
48816
48867
  try {
48817
- renameSync8(filePath, target);
48868
+ renameSync9(filePath, target);
48818
48869
  } catch (err) {
48819
48870
  if (err.code === "ENOENT") {
48820
48871
  return;
@@ -48867,7 +48918,7 @@ function createPendingCardStore(stateDir, log = (l) => process.stderr.write(l))
48867
48918
  preserveUnreadableStoreFile(filePath, "pending-card-store", log);
48868
48919
  unreadable = false;
48869
48920
  }
48870
- atomicWriteFileSync(filePath, JSON.stringify(entries), 384);
48921
+ atomicWriteFileSync2(filePath, JSON.stringify(entries), 384);
48871
48922
  } catch (err) {
48872
48923
  log(`telegram gateway: pending-card-store write failed: ${err.message}
48873
48924
  `);
@@ -49517,12 +49568,12 @@ function buildSkillProposalApplyInbound(opts) {
49517
49568
 
49518
49569
  // ../src/self-improve/skill-proposals.ts
49519
49570
  import {
49520
- closeSync as closeSync4,
49571
+ closeSync as closeSync5,
49521
49572
  existsSync as existsSync10,
49522
49573
  mkdirSync as mkdirSync13,
49523
- openSync as openSync4,
49574
+ openSync as openSync5,
49524
49575
  readFileSync as readFileSync13,
49525
- writeSync as writeSync4
49576
+ writeSync as writeSync5
49526
49577
  } from "node:fs";
49527
49578
  import { join as join14 } from "node:path";
49528
49579
  var PROPOSALS_FILE = "skill-proposals.jsonl";
@@ -49540,12 +49591,12 @@ function ensureDir2(stateDir) {
49540
49591
  }
49541
49592
  }
49542
49593
  function appendLine(path, obj) {
49543
- const fd = openSync4(path, "a");
49594
+ const fd = openSync5(path, "a");
49544
49595
  try {
49545
- writeSync4(fd, JSON.stringify(obj) + `
49596
+ writeSync5(fd, JSON.stringify(obj) + `
49546
49597
  `);
49547
49598
  } finally {
49548
- closeSync4(fd);
49599
+ closeSync5(fd);
49549
49600
  }
49550
49601
  }
49551
49602
  function readLines(path, isValid2) {
@@ -49696,7 +49747,7 @@ async function addAccountViaBroker(label, credentials, opts = {}) {
49696
49747
 
49697
49748
  // gateway/hostd-dispatch.ts
49698
49749
  import { existsSync as existsSync11 } from "node:fs";
49699
- import { randomBytes as randomBytes6 } from "node:crypto";
49750
+ import { randomBytes as randomBytes7 } from "node:crypto";
49700
49751
 
49701
49752
  // ../src/host-control/client.ts
49702
49753
  import { connect } from "node:net";
@@ -50042,7 +50093,7 @@ async function tryHostdDispatch(agentName, req, timeoutMs = 5000) {
50042
50093
  }
50043
50094
  }
50044
50095
  function hostdRequestId(prefix) {
50045
- return `${prefix}-${Date.now()}-${randomBytes6(4).toString("hex")}`;
50096
+ return `${prefix}-${Date.now()}-${randomBytes7(4).toString("hex")}`;
50046
50097
  }
50047
50098
  async function hostdGetStatusOnce(agentName, targetRequestId) {
50048
50099
  if (!isHostdEnabled())
@@ -51684,7 +51735,7 @@ ${trimmed.replace(/```/g, "`\u200b``")}
51684
51735
 
51685
51736
  // gateway/card-tool-handlers.ts
51686
51737
  init_rich_send();
51687
- import { randomBytes as randomBytes7 } from "crypto";
51738
+ import { randomBytes as randomBytes8 } from "crypto";
51688
51739
 
51689
51740
  // gateway/vault-request-access-card.ts
51690
51741
  init_approval_card();
@@ -51819,7 +51870,7 @@ function createCardToolHandlers(deps) {
51819
51870
  throw new Error(`vault_request_save: key must match ${VAULT_KEY_REGEX_LABEL}`);
51820
51871
  }
51821
51872
  const agentSlug = process.env.SWITCHROOM_AGENT_NAME || "agent";
51822
- const stageId = randomBytes7(4).toString("hex");
51873
+ const stageId = randomBytes8(4).toString("hex");
51823
51874
  const pending = {
51824
51875
  agent: agentSlug,
51825
51876
  chat_id,
@@ -51909,7 +51960,7 @@ function createCardToolHandlers(deps) {
51909
51960
  }
51910
51961
  } catch {}
51911
51962
  }
51912
- const stageId = randomBytes7(4).toString("hex");
51963
+ const stageId = randomBytes8(4).toString("hex");
51913
51964
  const pending = {
51914
51965
  agent: agentSlug,
51915
51966
  chat_id,
@@ -51971,7 +52022,7 @@ function createCardToolHandlers(deps) {
51971
52022
  pendingCardStore.remove(sid);
51972
52023
  }
51973
52024
  }
51974
- const stageId = randomBytes7(4).toString("hex");
52025
+ const stageId = randomBytes8(4).toString("hex");
51975
52026
  const pending = { agent: agentSlug, chat_id, key, reason, staged_at: Date.now() };
51976
52027
  pendingSecretRequests.set(stageId, pending);
51977
52028
  sweepSecretRequests();
@@ -52070,7 +52121,7 @@ function createCardToolHandlers(deps) {
52070
52121
  process.stderr.write(`telegram gateway: mental_model_propose dup pre-check read failed: ${err.message}
52071
52122
  `);
52072
52123
  }
52073
- const stageId = randomBytes7(4).toString("hex");
52124
+ const stageId = randomBytes8(4).toString("hex");
52074
52125
  const pending = {
52075
52126
  agent: agentSlug,
52076
52127
  chat_id,
@@ -52271,7 +52322,7 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
52271
52322
  preserveUnreadableStoreFile(filePath, "missed-approvals-store", log);
52272
52323
  unreadable = false;
52273
52324
  }
52274
- atomicWriteFileSync(filePath, JSON.stringify(f), 384);
52325
+ atomicWriteFileSync2(filePath, JSON.stringify(f), 384);
52275
52326
  } catch (err) {
52276
52327
  log(`telegram gateway: missed-approvals-store write failed: ${err.message}
52277
52328
  `);
@@ -52351,7 +52402,7 @@ function computeBackoffMs(attempts, retryAfterMs) {
52351
52402
  }
52352
52403
  var atomicWriteSeam = (path, data, opts) => {
52353
52404
  const mode = typeof opts === "object" && opts !== null && typeof opts.mode === "number" ? opts.mode : 384;
52354
- atomicWriteFileSync(path, data, mode);
52405
+ atomicWriteFileSync2(path, data, mode);
52355
52406
  };
52356
52407
  function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSeam, log = (l) => process.stderr.write(l)) {
52357
52408
  const filePath = join17(stateDir, "always-allow-persist-queue.json");
@@ -52802,6 +52853,7 @@ function isTelegramSurfaceTool2(toolName) {
52802
52853
 
52803
52854
  // tool-activity-summary.ts
52804
52855
  init_card_format();
52856
+ init_redact();
52805
52857
  function clipNarrative2(s) {
52806
52858
  return s.split(`
52807
52859
  `)[0].trim().slice(0, STATUS_LINE_MAX);
@@ -55860,7 +55912,7 @@ var codes = {
55860
55912
  replacementCharacter: 65533
55861
55913
  };
55862
55914
  // ../node_modules/.bun/micromark-util-symbol@2.0.1/node_modules/micromark-util-symbol/lib/constants.js
55863
- var constants2 = {
55915
+ var constants3 = {
55864
55916
  attentionSideAfter: 2,
55865
55917
  attentionSideBefore: 1,
55866
55918
  atxHeadingOpeningFenceSizeMax: 6,
@@ -56116,7 +56168,7 @@ function splice(list, start, remove, items) {
56116
56168
  start = start > end ? end : start;
56117
56169
  }
56118
56170
  remove = remove > 0 ? remove : 0;
56119
- if (items.length < constants2.v8MaxSafeChunkSize) {
56171
+ if (items.length < constants3.v8MaxSafeChunkSize) {
56120
56172
  parameters = Array.from(items);
56121
56173
  parameters.unshift(start, remove);
56122
56174
  list.splice(...parameters);
@@ -56124,11 +56176,11 @@ function splice(list, start, remove, items) {
56124
56176
  if (remove)
56125
56177
  list.splice(start, remove);
56126
56178
  while (chunkStart < items.length) {
56127
- parameters = items.slice(chunkStart, chunkStart + constants2.v8MaxSafeChunkSize);
56179
+ parameters = items.slice(chunkStart, chunkStart + constants3.v8MaxSafeChunkSize);
56128
56180
  parameters.unshift(start, 0);
56129
56181
  list.splice(...parameters);
56130
- chunkStart += constants2.v8MaxSafeChunkSize;
56131
- start += constants2.v8MaxSafeChunkSize;
56182
+ chunkStart += constants3.v8MaxSafeChunkSize;
56183
+ start += constants3.v8MaxSafeChunkSize;
56132
56184
  }
56133
56185
  }
56134
56186
  }
@@ -56264,7 +56316,7 @@ function initializeContent(effects) {
56264
56316
  }
56265
56317
  function lineStart(code) {
56266
56318
  const token = effects.enter(types2.chunkText, {
56267
- contentType: constants2.contentTypeText,
56319
+ contentType: constants3.contentTypeText,
56268
56320
  previous
56269
56321
  });
56270
56322
  if (previous) {
@@ -56386,7 +56438,7 @@ function initializeDocument(effects) {
56386
56438
  childFlow = childFlow || self.parser.flow(self.now());
56387
56439
  effects.enter(types2.chunkFlow, {
56388
56440
  _tokenizer: childFlow,
56389
- contentType: constants2.contentTypeFlow,
56441
+ contentType: constants3.contentTypeFlow,
56390
56442
  previous: childToken
56391
56443
  });
56392
56444
  return flowContinue(code);
@@ -56471,16 +56523,16 @@ function initializeDocument(effects) {
56471
56523
  }
56472
56524
  function tokenizeContainer(effects, ok2, nok) {
56473
56525
  ok(this.parser.constructs.disable.null, "expected `disable.null` to be populated");
56474
- return factorySpace(effects, effects.attempt(this.parser.constructs.document, ok2, nok), types2.linePrefix, this.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize);
56526
+ return factorySpace(effects, effects.attempt(this.parser.constructs.document, ok2, nok), types2.linePrefix, this.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize);
56475
56527
  }
56476
56528
 
56477
56529
  // ../node_modules/.bun/micromark-util-classify-character@2.0.1/node_modules/micromark-util-classify-character/dev/index.js
56478
56530
  function classifyCharacter(code) {
56479
56531
  if (code === codes.eof || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
56480
- return constants2.characterGroupWhitespace;
56532
+ return constants3.characterGroupWhitespace;
56481
56533
  }
56482
56534
  if (unicodePunctuation(code)) {
56483
- return constants2.characterGroupPunctuation;
56535
+ return constants3.characterGroupPunctuation;
56484
56536
  }
56485
56537
  }
56486
56538
 
@@ -56614,8 +56666,8 @@ function tokenizeAttention(effects, ok2) {
56614
56666
  const token = effects.exit("attentionSequence");
56615
56667
  const after = classifyCharacter(code);
56616
56668
  ok(attentionMarkers, "expected `attentionMarkers` to be populated");
56617
- const open = !after || after === constants2.characterGroupPunctuation && before || attentionMarkers.includes(code);
56618
- const close = !before || before === constants2.characterGroupPunctuation && after || attentionMarkers.includes(previous);
56669
+ const open = !after || after === constants3.characterGroupPunctuation && before || attentionMarkers.includes(code);
56670
+ const close = !before || before === constants3.characterGroupPunctuation && after || attentionMarkers.includes(previous);
56619
56671
  token._open = Boolean(marker === codes.asterisk ? open : open && (before || !close));
56620
56672
  token._close = Boolean(marker === codes.asterisk ? close : close && (after || !open));
56621
56673
  return ok2(code);
@@ -56663,7 +56715,7 @@ function tokenizeAutolink(effects, ok2, nok) {
56663
56715
  size = 0;
56664
56716
  return urlInside;
56665
56717
  }
56666
- if ((code === codes.plusSign || code === codes.dash || code === codes.dot || asciiAlphanumeric(code)) && size++ < constants2.autolinkSchemeSizeMax) {
56718
+ if ((code === codes.plusSign || code === codes.dash || code === codes.dot || asciiAlphanumeric(code)) && size++ < constants3.autolinkSchemeSizeMax) {
56667
56719
  effects.consume(code);
56668
56720
  return schemeInsideOrEmailAtext;
56669
56721
  }
@@ -56716,7 +56768,7 @@ function tokenizeAutolink(effects, ok2, nok) {
56716
56768
  return emailValue(code);
56717
56769
  }
56718
56770
  function emailValue(code) {
56719
- if ((code === codes.dash || asciiAlphanumeric(code)) && size++ < constants2.autolinkDomainSizeMax) {
56771
+ if ((code === codes.dash || asciiAlphanumeric(code)) && size++ < constants3.autolinkDomainSizeMax) {
56720
56772
  const next = code === codes.dash ? emailValue : emailLabel;
56721
56773
  effects.consume(code);
56722
56774
  return next;
@@ -56779,7 +56831,7 @@ function tokenizeBlockQuoteContinuation(effects, ok2, nok) {
56779
56831
  function contStart(code) {
56780
56832
  if (markdownSpace(code)) {
56781
56833
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
56782
- return factorySpace(effects, contBefore, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize)(code);
56834
+ return factorySpace(effects, contBefore, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize)(code);
56783
56835
  }
56784
56836
  return contBefore(code);
56785
56837
  }
@@ -56843,7 +56895,7 @@ function tokenizeCharacterReference(effects, ok2, nok) {
56843
56895
  return numeric;
56844
56896
  }
56845
56897
  effects.enter(types2.characterReferenceValue);
56846
- max = constants2.characterReferenceNamedSizeMax;
56898
+ max = constants3.characterReferenceNamedSizeMax;
56847
56899
  test = asciiAlphanumeric;
56848
56900
  return value(code);
56849
56901
  }
@@ -56853,12 +56905,12 @@ function tokenizeCharacterReference(effects, ok2, nok) {
56853
56905
  effects.consume(code);
56854
56906
  effects.exit(types2.characterReferenceMarkerHexadecimal);
56855
56907
  effects.enter(types2.characterReferenceValue);
56856
- max = constants2.characterReferenceHexadecimalSizeMax;
56908
+ max = constants3.characterReferenceHexadecimalSizeMax;
56857
56909
  test = asciiHexDigit;
56858
56910
  return value;
56859
56911
  }
56860
56912
  effects.enter(types2.characterReferenceValue);
56861
- max = constants2.characterReferenceDecimalSizeMax;
56913
+ max = constants3.characterReferenceDecimalSizeMax;
56862
56914
  test = asciiDigit;
56863
56915
  return value(code);
56864
56916
  }
@@ -56917,7 +56969,7 @@ function tokenizeCodeFenced(effects, ok2, nok) {
56917
56969
  effects.consume(code);
56918
56970
  return sequenceOpen;
56919
56971
  }
56920
- if (sizeOpen < constants2.codeFencedSequenceSizeMin) {
56972
+ if (sizeOpen < constants3.codeFencedSequenceSizeMin) {
56921
56973
  return nok(code);
56922
56974
  }
56923
56975
  effects.exit(types2.codeFencedFenceSequence);
@@ -56929,7 +56981,7 @@ function tokenizeCodeFenced(effects, ok2, nok) {
56929
56981
  return self.interrupt ? ok2(code) : effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
56930
56982
  }
56931
56983
  effects.enter(types2.codeFencedFenceInfo);
56932
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
56984
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
56933
56985
  return info(code);
56934
56986
  }
56935
56987
  function info(code) {
@@ -56954,7 +57006,7 @@ function tokenizeCodeFenced(effects, ok2, nok) {
56954
57006
  return infoBefore(code);
56955
57007
  }
56956
57008
  effects.enter(types2.codeFencedFenceMeta);
56957
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
57009
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
56958
57010
  return meta(code);
56959
57011
  }
56960
57012
  function meta(code) {
@@ -57015,7 +57067,7 @@ function tokenizeCodeFenced(effects, ok2, nok) {
57015
57067
  function start2(code) {
57016
57068
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
57017
57069
  effects2.enter(types2.codeFencedFence);
57018
- return markdownSpace(code) ? factorySpace(effects2, beforeSequenceClose, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize)(code) : beforeSequenceClose(code);
57070
+ return markdownSpace(code) ? factorySpace(effects2, beforeSequenceClose, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize)(code) : beforeSequenceClose(code);
57019
57071
  }
57020
57072
  function beforeSequenceClose(code) {
57021
57073
  if (code === marker) {
@@ -57074,11 +57126,11 @@ function tokenizeCodeIndented(effects, ok2, nok) {
57074
57126
  function start(code) {
57075
57127
  ok(markdownSpace(code));
57076
57128
  effects.enter(types2.codeIndented);
57077
- return factorySpace(effects, afterPrefix, types2.linePrefix, constants2.tabSize + 1)(code);
57129
+ return factorySpace(effects, afterPrefix, types2.linePrefix, constants3.tabSize + 1)(code);
57078
57130
  }
57079
57131
  function afterPrefix(code) {
57080
57132
  const tail = self.events[self.events.length - 1];
57081
- return tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants2.tabSize ? atBreak(code) : nok(code);
57133
+ return tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants3.tabSize ? atBreak(code) : nok(code);
57082
57134
  }
57083
57135
  function atBreak(code) {
57084
57136
  if (code === codes.eof) {
@@ -57116,11 +57168,11 @@ function tokenizeFurtherStart(effects, ok2, nok) {
57116
57168
  effects.exit(types2.lineEnding);
57117
57169
  return furtherStart2;
57118
57170
  }
57119
- return factorySpace(effects, afterPrefix, types2.linePrefix, constants2.tabSize + 1)(code);
57171
+ return factorySpace(effects, afterPrefix, types2.linePrefix, constants3.tabSize + 1)(code);
57120
57172
  }
57121
57173
  function afterPrefix(code) {
57122
57174
  const tail = self.events[self.events.length - 1];
57123
- return tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants2.tabSize ? ok2(code) : markdownLineEnding(code) ? furtherStart2(code) : nok(code);
57175
+ return tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants3.tabSize ? ok2(code) : markdownLineEnding(code) ? furtherStart2(code) : nok(code);
57124
57176
  }
57125
57177
  }
57126
57178
  // ../node_modules/.bun/micromark-core-commonmark@2.0.3/node_modules/micromark-core-commonmark/dev/lib/code-text.js
@@ -57312,12 +57364,12 @@ class SpliceBuffer {
57312
57364
  }
57313
57365
  function chunkedPush(list, right) {
57314
57366
  let chunkStart = 0;
57315
- if (right.length < constants2.v8MaxSafeChunkSize) {
57367
+ if (right.length < constants3.v8MaxSafeChunkSize) {
57316
57368
  list.push(...right);
57317
57369
  } else {
57318
57370
  while (chunkStart < right.length) {
57319
- list.push(...right.slice(chunkStart, chunkStart + constants2.v8MaxSafeChunkSize));
57320
- chunkStart += constants2.v8MaxSafeChunkSize;
57371
+ list.push(...right.slice(chunkStart, chunkStart + constants3.v8MaxSafeChunkSize));
57372
+ chunkStart += constants3.v8MaxSafeChunkSize;
57321
57373
  }
57322
57374
  }
57323
57375
  }
@@ -57489,7 +57541,7 @@ function tokenizeContent(effects, ok2) {
57489
57541
  ok(code !== codes.eof && !markdownLineEnding(code), "expected no eof or eol");
57490
57542
  effects.enter(types2.content);
57491
57543
  previous2 = effects.enter(types2.chunkContent, {
57492
- contentType: constants2.contentTypeContent
57544
+ contentType: constants3.contentTypeContent
57493
57545
  });
57494
57546
  return chunkInside(code);
57495
57547
  }
@@ -57514,7 +57566,7 @@ function tokenizeContent(effects, ok2) {
57514
57566
  effects.exit(types2.chunkContent);
57515
57567
  ok(previous2, "expected previous token");
57516
57568
  previous2.next = effects.enter(types2.chunkContent, {
57517
- contentType: constants2.contentTypeContent,
57569
+ contentType: constants3.contentTypeContent,
57518
57570
  previous: previous2
57519
57571
  });
57520
57572
  previous2 = previous2.next;
@@ -57538,7 +57590,7 @@ function tokenizeContinuation(effects, ok2, nok) {
57538
57590
  }
57539
57591
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
57540
57592
  const tail = self.events[self.events.length - 1];
57541
- if (!self.parser.constructs.disable.null.includes("codeIndented") && tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants2.tabSize) {
57593
+ if (!self.parser.constructs.disable.null.includes("codeIndented") && tail && tail[1].type === types2.linePrefix && tail[2].sliceSerialize(tail[1], true).length >= constants3.tabSize) {
57542
57594
  return ok2(code);
57543
57595
  }
57544
57596
  return effects.interrupt(self.parser.constructs.flow, nok, ok2)(code);
@@ -57564,7 +57616,7 @@ function factoryDestination(effects, ok2, nok, type, literalType2, literalMarker
57564
57616
  effects.enter(type);
57565
57617
  effects.enter(rawType);
57566
57618
  effects.enter(stringType2);
57567
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
57619
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
57568
57620
  return raw(code);
57569
57621
  }
57570
57622
  function enclosedBefore(code) {
@@ -57577,7 +57629,7 @@ function factoryDestination(effects, ok2, nok, type, literalType2, literalMarker
57577
57629
  return ok2;
57578
57630
  }
57579
57631
  effects.enter(stringType2);
57580
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
57632
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
57581
57633
  return enclosed(code);
57582
57634
  }
57583
57635
  function enclosed(code) {
@@ -57648,7 +57700,7 @@ function factoryLabel(effects, ok2, nok, type, markerType, stringType2) {
57648
57700
  return atBreak;
57649
57701
  }
57650
57702
  function atBreak(code) {
57651
- if (size > constants2.linkReferenceSizeMax || code === codes.eof || code === codes.leftSquareBracket || code === codes.rightSquareBracket && !seen || code === codes.caret && !size && "_hiddenFootnoteSupport" in self.parser.constructs) {
57703
+ if (size > constants3.linkReferenceSizeMax || code === codes.eof || code === codes.leftSquareBracket || code === codes.rightSquareBracket && !seen || code === codes.caret && !size && "_hiddenFootnoteSupport" in self.parser.constructs) {
57652
57704
  return nok(code);
57653
57705
  }
57654
57706
  if (code === codes.rightSquareBracket) {
@@ -57665,11 +57717,11 @@ function factoryLabel(effects, ok2, nok, type, markerType, stringType2) {
57665
57717
  effects.exit(types2.lineEnding);
57666
57718
  return atBreak;
57667
57719
  }
57668
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
57720
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
57669
57721
  return labelInside(code);
57670
57722
  }
57671
57723
  function labelInside(code) {
57672
- if (code === codes.eof || code === codes.leftSquareBracket || code === codes.rightSquareBracket || markdownLineEnding(code) || size++ > constants2.linkReferenceSizeMax) {
57724
+ if (code === codes.eof || code === codes.leftSquareBracket || code === codes.rightSquareBracket || markdownLineEnding(code) || size++ > constants3.linkReferenceSizeMax) {
57673
57725
  effects.exit(types2.chunkString);
57674
57726
  return atBreak(code);
57675
57727
  }
@@ -57728,7 +57780,7 @@ function factoryTitle(effects, ok2, nok, type, markerType, stringType2) {
57728
57780
  effects.exit(types2.lineEnding);
57729
57781
  return factorySpace(effects, atBreak, types2.linePrefix);
57730
57782
  }
57731
- effects.enter(types2.chunkString, { contentType: constants2.contentTypeString });
57783
+ effects.enter(types2.chunkString, { contentType: constants3.contentTypeString });
57732
57784
  return inside(code);
57733
57785
  }
57734
57786
  function inside(code) {
@@ -57879,7 +57931,7 @@ function resolveHeadingAtx(events, context) {
57879
57931
  type: types2.chunkText,
57880
57932
  start: events[contentStart][1].start,
57881
57933
  end: events[contentEnd][1].end,
57882
- contentType: constants2.contentTypeText
57934
+ contentType: constants3.contentTypeText
57883
57935
  };
57884
57936
  splice(events, contentStart, contentEnd - contentStart + 1, [
57885
57937
  ["enter", content3, context],
@@ -57903,7 +57955,7 @@ function tokenizeHeadingAtx(effects, ok2, nok) {
57903
57955
  return sequenceOpen(code);
57904
57956
  }
57905
57957
  function sequenceOpen(code) {
57906
- if (code === codes.numberSign && size++ < constants2.atxHeadingOpeningFenceSizeMax) {
57958
+ if (code === codes.numberSign && size++ < constants3.atxHeadingOpeningFenceSizeMax) {
57907
57959
  effects.consume(code);
57908
57960
  return sequenceOpen;
57909
57961
  }
@@ -58068,7 +58120,7 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58068
58120
  }
58069
58121
  if (code === codes.questionMark) {
58070
58122
  effects.consume(code);
58071
- marker = constants2.htmlInstruction;
58123
+ marker = constants3.htmlInstruction;
58072
58124
  return self.interrupt ? ok2 : continuationDeclarationInside;
58073
58125
  }
58074
58126
  if (asciiAlpha(code)) {
@@ -58082,18 +58134,18 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58082
58134
  function declarationOpen(code) {
58083
58135
  if (code === codes.dash) {
58084
58136
  effects.consume(code);
58085
- marker = constants2.htmlComment;
58137
+ marker = constants3.htmlComment;
58086
58138
  return commentOpenInside;
58087
58139
  }
58088
58140
  if (code === codes.leftSquareBracket) {
58089
58141
  effects.consume(code);
58090
- marker = constants2.htmlCdata;
58142
+ marker = constants3.htmlCdata;
58091
58143
  index = 0;
58092
58144
  return cdataOpenInside;
58093
58145
  }
58094
58146
  if (asciiAlpha(code)) {
58095
58147
  effects.consume(code);
58096
- marker = constants2.htmlDeclaration;
58148
+ marker = constants3.htmlDeclaration;
58097
58149
  return self.interrupt ? ok2 : continuationDeclarationInside;
58098
58150
  }
58099
58151
  return nok(code);
@@ -58106,7 +58158,7 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58106
58158
  return nok(code);
58107
58159
  }
58108
58160
  function cdataOpenInside(code) {
58109
- const value = constants2.cdataOpeningString;
58161
+ const value = constants3.cdataOpeningString;
58110
58162
  if (code === value.charCodeAt(index++)) {
58111
58163
  effects.consume(code);
58112
58164
  if (index === value.length) {
@@ -58130,18 +58182,18 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58130
58182
  const slash = code === codes.slash;
58131
58183
  const name = buffer.toLowerCase();
58132
58184
  if (!slash && !closingTag && htmlRawNames.includes(name)) {
58133
- marker = constants2.htmlRaw;
58185
+ marker = constants3.htmlRaw;
58134
58186
  return self.interrupt ? ok2(code) : continuation(code);
58135
58187
  }
58136
58188
  if (htmlBlockNames.includes(buffer.toLowerCase())) {
58137
- marker = constants2.htmlBasic;
58189
+ marker = constants3.htmlBasic;
58138
58190
  if (slash) {
58139
58191
  effects.consume(code);
58140
58192
  return basicSelfClosing;
58141
58193
  }
58142
58194
  return self.interrupt ? ok2(code) : continuation(code);
58143
58195
  }
58144
- marker = constants2.htmlComplete;
58196
+ marker = constants3.htmlComplete;
58145
58197
  return self.interrupt && !self.parser.lazy[self.now().line] ? nok(code) : closingTag ? completeClosingTagAfter(code) : completeAttributeNameBefore(code);
58146
58198
  }
58147
58199
  if (code === codes.dash || asciiAlphanumeric(code)) {
@@ -58256,27 +58308,27 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58256
58308
  return nok(code);
58257
58309
  }
58258
58310
  function continuation(code) {
58259
- if (code === codes.dash && marker === constants2.htmlComment) {
58311
+ if (code === codes.dash && marker === constants3.htmlComment) {
58260
58312
  effects.consume(code);
58261
58313
  return continuationCommentInside;
58262
58314
  }
58263
- if (code === codes.lessThan && marker === constants2.htmlRaw) {
58315
+ if (code === codes.lessThan && marker === constants3.htmlRaw) {
58264
58316
  effects.consume(code);
58265
58317
  return continuationRawTagOpen;
58266
58318
  }
58267
- if (code === codes.greaterThan && marker === constants2.htmlDeclaration) {
58319
+ if (code === codes.greaterThan && marker === constants3.htmlDeclaration) {
58268
58320
  effects.consume(code);
58269
58321
  return continuationClose;
58270
58322
  }
58271
- if (code === codes.questionMark && marker === constants2.htmlInstruction) {
58323
+ if (code === codes.questionMark && marker === constants3.htmlInstruction) {
58272
58324
  effects.consume(code);
58273
58325
  return continuationDeclarationInside;
58274
58326
  }
58275
- if (code === codes.rightSquareBracket && marker === constants2.htmlCdata) {
58327
+ if (code === codes.rightSquareBracket && marker === constants3.htmlCdata) {
58276
58328
  effects.consume(code);
58277
58329
  return continuationCdataInside;
58278
58330
  }
58279
- if (markdownLineEnding(code) && (marker === constants2.htmlBasic || marker === constants2.htmlComplete)) {
58331
+ if (markdownLineEnding(code) && (marker === constants3.htmlBasic || marker === constants3.htmlComplete)) {
58280
58332
  effects.exit(types2.htmlFlowData);
58281
58333
  return effects.check(blankLineBefore, continuationAfter, continuationStart)(code);
58282
58334
  }
@@ -58328,7 +58380,7 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58328
58380
  }
58329
58381
  return continuation(code);
58330
58382
  }
58331
- if (asciiAlpha(code) && buffer.length < constants2.htmlRawSizeMax) {
58383
+ if (asciiAlpha(code) && buffer.length < constants3.htmlRawSizeMax) {
58332
58384
  ok(code !== null);
58333
58385
  effects.consume(code);
58334
58386
  buffer += String.fromCharCode(code);
@@ -58348,7 +58400,7 @@ function tokenizeHtmlFlow(effects, ok2, nok) {
58348
58400
  effects.consume(code);
58349
58401
  return continuationClose;
58350
58402
  }
58351
- if (code === codes.dash && marker === constants2.htmlComment) {
58403
+ if (code === codes.dash && marker === constants3.htmlComment) {
58352
58404
  effects.consume(code);
58353
58405
  return continuationDeclarationInside;
58354
58406
  }
@@ -58476,7 +58528,7 @@ function tokenizeHtmlText(effects, ok2, nok) {
58476
58528
  return code === codes.greaterThan ? end(code) : code === codes.dash ? commentClose(code) : comment(code);
58477
58529
  }
58478
58530
  function cdataOpenInside(code) {
58479
- const value = constants2.cdataOpeningString;
58531
+ const value = constants3.cdataOpeningString;
58480
58532
  if (code === value.charCodeAt(index++)) {
58481
58533
  effects.consume(code);
58482
58534
  return index === value.length ? cdata : cdataOpenInside;
@@ -58692,7 +58744,7 @@ function tokenizeHtmlText(effects, ok2, nok) {
58692
58744
  }
58693
58745
  function lineEndingAfter(code) {
58694
58746
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
58695
- return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize)(code) : lineEndingAfterPrefix(code);
58747
+ return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize)(code) : lineEndingAfterPrefix(code);
58696
58748
  }
58697
58749
  function lineEndingAfterPrefix(code) {
58698
58750
  effects.enter(types2.htmlTextData);
@@ -58855,7 +58907,7 @@ function tokenizeResource(effects, ok2, nok) {
58855
58907
  if (code === codes.rightParenthesis) {
58856
58908
  return resourceEnd(code);
58857
58909
  }
58858
- return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, types2.resourceDestination, types2.resourceDestinationLiteral, types2.resourceDestinationLiteralMarker, types2.resourceDestinationRaw, types2.resourceDestinationString, constants2.linkResourceDestinationBalanceMax)(code);
58910
+ return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, types2.resourceDestination, types2.resourceDestinationLiteral, types2.resourceDestinationLiteralMarker, types2.resourceDestinationRaw, types2.resourceDestinationString, constants3.linkResourceDestinationBalanceMax)(code);
58859
58911
  }
58860
58912
  function resourceDestinationAfter(code) {
58861
58913
  return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
@@ -59006,7 +59058,7 @@ function tokenizeThematicBreak(effects, ok2, nok) {
59006
59058
  effects.enter(types2.thematicBreakSequence);
59007
59059
  return sequence(code);
59008
59060
  }
59009
- if (size >= constants2.thematicBreakMarkerCountMin && (code === codes.eof || markdownLineEnding(code))) {
59061
+ if (size >= constants3.thematicBreakMarkerCountMin && (code === codes.eof || markdownLineEnding(code))) {
59010
59062
  effects.exit(types2.thematicBreak);
59011
59063
  return ok2(code);
59012
59064
  }
@@ -59063,7 +59115,7 @@ function tokenizeListStart(effects, ok2, nok) {
59063
59115
  }
59064
59116
  function inside(code) {
59065
59117
  ok(self.containerState, "expected state");
59066
- if (asciiDigit(code) && ++size < constants2.listItemValueSizeMax) {
59118
+ if (asciiDigit(code) && ++size < constants3.listItemValueSizeMax) {
59067
59119
  effects.consume(code);
59068
59120
  return inside;
59069
59121
  }
@@ -59130,7 +59182,7 @@ function tokenizeListContinuation(effects, ok2, nok) {
59130
59182
  self.containerState._closeFlow = true;
59131
59183
  self.interrupt = undefined;
59132
59184
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
59133
- return factorySpace(effects, effects.attempt(list, ok2, nok), types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize)(code);
59185
+ return factorySpace(effects, effects.attempt(list, ok2, nok), types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize)(code);
59134
59186
  }
59135
59187
  }
59136
59188
  function tokenizeIndent(effects, ok2, nok) {
@@ -59152,7 +59204,7 @@ function tokenizeListEnd(effects) {
59152
59204
  function tokenizeListItemPrefixWhitespace(effects, ok2, nok) {
59153
59205
  const self = this;
59154
59206
  ok(self.parser.constructs.disable.null, "expected `disable.null` to be populated");
59155
- return factorySpace(effects, afterPrefix, types2.listItemPrefixWhitespace, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize + 1);
59207
+ return factorySpace(effects, afterPrefix, types2.listItemPrefixWhitespace, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize + 1);
59156
59208
  function afterPrefix(code) {
59157
59209
  const tail = self.events[self.events.length - 1];
59158
59210
  return !markdownSpace(code) && tail && tail[1].type === types2.listItemPrefixWhitespace ? ok2(code) : nok(code);
@@ -59390,7 +59442,7 @@ function resolveAllLineSuffixes(events, context) {
59390
59442
  }
59391
59443
  if (size) {
59392
59444
  const token = {
59393
- type: eventIndex === events.length || tabs || size < constants2.hardBreakPrefixSizeMin ? types2.lineSuffix : types2.hardBreakTrailing,
59445
+ type: eventIndex === events.length || tabs || size < constants3.hardBreakPrefixSizeMin ? types2.lineSuffix : types2.hardBreakTrailing,
59394
59446
  start: {
59395
59447
  _bufferIndex: index ? bufferIndex : data.start._bufferIndex + bufferIndex,
59396
59448
  _index: data.start._index + index,
@@ -59880,7 +59932,7 @@ function preprocess() {
59880
59932
  break;
59881
59933
  }
59882
59934
  case codes.ht: {
59883
- next = Math.ceil(column / constants2.tabSize) * constants2.tabSize;
59935
+ next = Math.ceil(column / constants3.tabSize) * constants3.tabSize;
59884
59936
  chunks.push(codes.horizontalTab);
59885
59937
  while (column++ < next)
59886
59938
  chunks.push(codes.virtualSpace);
@@ -59922,7 +59974,7 @@ function decode($0, $1, $2) {
59922
59974
  if (head === codes.numberSign) {
59923
59975
  const head2 = $2.charCodeAt(1);
59924
59976
  const hex = head2 === codes.lowercaseX || head2 === codes.uppercaseX;
59925
- return decodeNumericCharacterReference($2.slice(hex ? 2 : 1), hex ? constants2.numericBaseHexadecimal : constants2.numericBaseDecimal);
59977
+ return decodeNumericCharacterReference($2.slice(hex ? 2 : 1), hex ? constants3.numericBaseHexadecimal : constants3.numericBaseDecimal);
59926
59978
  }
59927
59979
  return decodeNamedCharacterReference($2) || $0;
59928
59980
  }
@@ -60259,7 +60311,7 @@ function compiler(options) {
60259
60311
  const ancestor = this.stack[this.stack.length - 2];
60260
60312
  ok(ancestor, "expected nodes on stack");
60261
60313
  ok(ancestor.type === "list", "expected list on stack");
60262
- ancestor.start = Number.parseInt(this.sliceSerialize(token), constants2.numericBaseDecimal);
60314
+ ancestor.start = Number.parseInt(this.sliceSerialize(token), constants3.numericBaseDecimal);
60263
60315
  this.data.expectingFirstListItemValue = undefined;
60264
60316
  }
60265
60317
  }
@@ -60500,7 +60552,7 @@ function compiler(options) {
60500
60552
  const type = this.data.characterReferenceType;
60501
60553
  let value;
60502
60554
  if (type) {
60503
- value = decodeNumericCharacterReference(data2, type === types2.characterReferenceMarkerNumeric ? constants2.numericBaseDecimal : constants2.numericBaseHexadecimal);
60555
+ value = decodeNumericCharacterReference(data2, type === types2.characterReferenceMarkerNumeric ? constants3.numericBaseDecimal : constants3.numericBaseHexadecimal);
60504
60556
  this.data.characterReferenceType = undefined;
60505
60557
  } else {
60506
60558
  const result = decodeNamedCharacterReference(data2);
@@ -61116,7 +61168,7 @@ function tokenizeGfmFootnoteCall(effects, ok2, nok) {
61116
61168
  return callData;
61117
61169
  }
61118
61170
  function callData(code2) {
61119
- if (size > constants2.linkReferenceSizeMax || code2 === codes.rightSquareBracket && !data || code2 === codes.eof || code2 === codes.leftSquareBracket || markdownLineEndingOrSpace(code2)) {
61171
+ if (size > constants3.linkReferenceSizeMax || code2 === codes.rightSquareBracket && !data || code2 === codes.eof || code2 === codes.leftSquareBracket || markdownLineEndingOrSpace(code2)) {
61120
61172
  return nok(code2);
61121
61173
  }
61122
61174
  if (code2 === codes.rightSquareBracket) {
@@ -61175,7 +61227,7 @@ function tokenizeDefinitionStart(effects, ok2, nok) {
61175
61227
  return nok(code2);
61176
61228
  }
61177
61229
  function labelInside(code2) {
61178
- if (size > constants2.linkReferenceSizeMax || code2 === codes.rightSquareBracket && !data || code2 === codes.eof || code2 === codes.leftSquareBracket || markdownLineEndingOrSpace(code2)) {
61230
+ if (size > constants3.linkReferenceSizeMax || code2 === codes.rightSquareBracket && !data || code2 === codes.eof || code2 === codes.leftSquareBracket || markdownLineEndingOrSpace(code2)) {
61179
61231
  return nok(code2);
61180
61232
  }
61181
61233
  if (code2 === codes.rightSquareBracket) {
@@ -61227,10 +61279,10 @@ function gfmFootnoteDefinitionEnd(effects) {
61227
61279
  }
61228
61280
  function tokenizeIndent2(effects, ok2, nok) {
61229
61281
  const self = this;
61230
- return factorySpace(effects, afterPrefix, "gfmFootnoteDefinitionIndent", constants2.tabSize + 1);
61282
+ return factorySpace(effects, afterPrefix, "gfmFootnoteDefinitionIndent", constants3.tabSize + 1);
61231
61283
  function afterPrefix(code2) {
61232
61284
  const tail = self.events[self.events.length - 1];
61233
- return tail && tail[1].type === "gfmFootnoteDefinitionIndent" && tail[2].sliceSerialize(tail[1], true).length === constants2.tabSize ? ok2(code2) : nok(code2);
61285
+ return tail && tail[1].type === "gfmFootnoteDefinitionIndent" && tail[2].sliceSerialize(tail[1], true).length === constants3.tabSize ? ok2(code2) : nok(code2);
61234
61286
  }
61235
61287
  }
61236
61288
  // ../node_modules/.bun/micromark-extension-gfm-strikethrough@2.1.0/node_modules/micromark-extension-gfm-strikethrough/dev/lib/syntax.js
@@ -61326,8 +61378,8 @@ function gfmStrikethrough(options) {
61326
61378
  return nok(code2);
61327
61379
  const token = effects.exit("strikethroughSequenceTemporary");
61328
61380
  const after = classifyCharacter(code2);
61329
- token._open = !after || after === constants2.attentionSideAfter && Boolean(before);
61330
- token._close = !before || before === constants2.attentionSideAfter && Boolean(after);
61381
+ token._open = !after || after === constants3.attentionSideAfter && Boolean(before);
61382
+ token._close = !before || before === constants3.attentionSideAfter && Boolean(after);
61331
61383
  return ok2(code2);
61332
61384
  }
61333
61385
  }
@@ -61511,7 +61563,7 @@ function tokenizeTable(effects, ok2, nok) {
61511
61563
  seen = false;
61512
61564
  if (markdownSpace(code2)) {
61513
61565
  ok(self.parser.constructs.disable.null, "expected `disabled.null`");
61514
- return factorySpace(effects, headDelimiterBefore, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants2.tabSize)(code2);
61566
+ return factorySpace(effects, headDelimiterBefore, types2.linePrefix, self.parser.constructs.disable.null.includes("codeIndented") ? undefined : constants3.tabSize)(code2);
61515
61567
  }
61516
61568
  return headDelimiterBefore(code2);
61517
61569
  }
@@ -61762,7 +61814,7 @@ function flushCell(map, context, range, rowKind, rowEnd, previousCell) {
61762
61814
  const end = context.events[range[3]];
61763
61815
  start[1].end = Object.assign({}, end[1].end);
61764
61816
  start[1].type = types2.chunkText;
61765
- start[1].contentType = constants2.contentTypeText;
61817
+ start[1].contentType = constants3.contentTypeText;
61766
61818
  if (range[3] > range[2] + 1) {
61767
61819
  const a = range[2] + 1;
61768
61820
  const b = range[3] - range[2] - 1;
@@ -63186,6 +63238,18 @@ function createChatLock() {
63186
63238
 
63187
63239
  // retry-api-call.ts
63188
63240
  var import_grammy6 = __toESM(require_mod2(), 1);
63241
+ function classifyBenignTelegram4002(errorCode, description) {
63242
+ if (errorCode !== 400)
63243
+ return null;
63244
+ const desc = (description ?? "").toLowerCase();
63245
+ if (desc.includes("not modified"))
63246
+ return "not_modified";
63247
+ if (desc.includes("message to edit not found"))
63248
+ return "message_not_found";
63249
+ if (desc.includes("message to delete not found"))
63250
+ return "delete_not_found";
63251
+ return null;
63252
+ }
63189
63253
  function isLocalResourceError2(err) {
63190
63254
  const code2 = err?.code;
63191
63255
  if (typeof code2 === "string" && ["ENOSPC", "EDQUOT", "EIO", "ENOMEM"].includes(code2)) {
@@ -63270,14 +63334,9 @@ function createRetryApiCall2(config = {}) {
63270
63334
  await sleep(delayMs);
63271
63335
  continue;
63272
63336
  }
63273
- if (isGrammyErr && err.error_code === 400 && desc.includes("not modified")) {
63274
- observer?.onBenign?.({ kind: "not_modified" });
63275
- return;
63276
- }
63277
- if (isGrammyErr && err.error_code === 400 && (desc.includes("message to edit not found") || desc.includes("message to delete not found"))) {
63278
- observer?.onBenign?.({
63279
- kind: desc.includes("edit") ? "message_not_found" : "delete_not_found"
63280
- });
63337
+ const benignKind = isGrammyErr ? classifyBenignTelegram4002(err.error_code, desc) : null;
63338
+ if (benignKind !== null) {
63339
+ observer?.onBenign?.({ kind: benignKind });
63281
63340
  return;
63282
63341
  }
63283
63342
  if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
@@ -63425,7 +63484,7 @@ function editFloodFuseConfigFromEnv(env) {
63425
63484
  }
63426
63485
  return cfg;
63427
63486
  }
63428
- var DROPPED_RESULT = true;
63487
+ var DROPPED_RESULT = { ok: true, result: true };
63429
63488
  function createEditFloodFuse(config = {}) {
63430
63489
  const enabled2 = config.enabled ?? true;
63431
63490
  const clock = config.clock ?? systemClock;
@@ -63683,12 +63742,12 @@ function createEditFloodFuse(config = {}) {
63683
63742
  const msgKey = `m:${chat}:${msg}`;
63684
63743
  const mw = win(msgKey);
63685
63744
  mw.inflight++;
63745
+ const dropGuard = () => mw.inflight > 1;
63746
+ const lateKey = cls === "cosmetic" ? `lr:${chat}` : undefined;
63686
63747
  try {
63687
- const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerMessageMax, t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, undefined, deadline);
63748
+ const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerMessageMax, t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, dropGuard, deadline, lateKey);
63688
63749
  if (msgSlot === null)
63689
63750
  return DROPPED_RESULT;
63690
- const dropGuard = () => mw.inflight > 1;
63691
- const lateKey = cls === "cosmetic" ? `lr:${chat}` : undefined;
63692
63751
  const reserved = [[msgKey, msgSlot]];
63693
63752
  const giveBack = () => {
63694
63753
  for (const [k, at] of reserved)
@@ -64607,6 +64666,11 @@ function formatTgPostTags(tags) {
64607
64666
  }
64608
64667
  return parts.length > 0 ? " " + parts.join(" ") : "";
64609
64668
  }
64669
+ function shortDesc(raw) {
64670
+ if (!raw)
64671
+ return "-";
64672
+ return raw.replace(/\s+/g, " ").slice(0, 80).replace(/[\r\n]/g, " ") || "-";
64673
+ }
64610
64674
  function installTgPostLogger(bot) {
64611
64675
  bot.api.config.use(async (prev, method, payload, signal) => {
64612
64676
  const p = payload ?? {};
@@ -64617,20 +64681,28 @@ function installTgPostLogger(bot) {
64617
64681
  const bytes = text4.length;
64618
64682
  const hash = bytes > 0 ? createHash3("sha1").update(text4).digest("hex").slice(0, 12) : "-";
64619
64683
  const tagSuffix = formatTgPostTags(_getTgPostTags());
64684
+ const emit = (status, errClass, code2, desc) => {
64685
+ if (!shouldEmitTgPost(method, status))
64686
+ return;
64687
+ process.stderr.write(`tg-post method=${method} chat=${chat} thread=${thread} parse_mode=${parseMode} bytes=${bytes} hash=${hash} status=${status} err=${errClass} code=${code2} desc=${desc}${tagSuffix}
64688
+ `);
64689
+ };
64620
64690
  try {
64621
64691
  const res = await prev(method, payload, signal);
64622
- if (shouldEmitTgPost(method, "ok")) {
64623
- process.stderr.write(`tg-post method=${method} chat=${chat} thread=${thread} parse_mode=${parseMode} bytes=${bytes} hash=${hash} status=ok err=- code=- desc=-${tagSuffix}
64624
- `);
64692
+ const r = res;
64693
+ if (r != null && typeof r === "object" && r.ok === false) {
64694
+ const code2 = r.error_code;
64695
+ const benign = classifyBenignTelegram400(code2, r.description);
64696
+ emit(benign !== null ? "benign" : "err", `telegram_${code2 ?? "unknown"}`, code2 != null ? String(code2) : "-", shortDesc(r.description));
64697
+ return res;
64625
64698
  }
64699
+ emit("ok", "-", "-", "-");
64626
64700
  return res;
64627
64701
  } catch (err) {
64628
64702
  const errClass = err instanceof import_grammy7.GrammyError ? `grammy_${err.error_code}` : err?.constructor?.name ?? "Error";
64629
64703
  const code2 = err instanceof import_grammy7.GrammyError ? String(err.error_code) : "-";
64630
64704
  const rawDesc = err instanceof import_grammy7.GrammyError ? err.description : err instanceof Error ? err.message : "";
64631
- const desc = rawDesc ? rawDesc.replace(/\s+/g, " ").slice(0, 80).replace(/[\r\n]/g, " ") || "-" : "-";
64632
- process.stderr.write(`tg-post method=${method} chat=${chat} thread=${thread} parse_mode=${parseMode} bytes=${bytes} hash=${hash} status=err err=${errClass} code=${code2} desc=${desc}${tagSuffix}
64633
- `);
64705
+ emit("err", errClass, code2, shortDesc(rawDesc));
64634
64706
  throw err;
64635
64707
  }
64636
64708
  });
@@ -64660,7 +64732,7 @@ import {
64660
64732
  mkdirSync as mkdirSync16,
64661
64733
  chmodSync as chmodSync6,
64662
64734
  unlinkSync as unlinkSync11,
64663
- renameSync as renameSync9
64735
+ renameSync as renameSync10
64664
64736
  } from "node:fs";
64665
64737
  import { dirname as dirname9, join as join19 } from "node:path";
64666
64738
  var FLOOD_STATE_FILE2 = "flood-wait.json";
@@ -64867,7 +64939,7 @@ function writeFloodWindow(path2, record, now) {
64867
64939
  const arr = [...byScope.values()];
64868
64940
  const tmp = `${path2}.tmp-${process.pid}`;
64869
64941
  writeFileSync13(tmp, JSON.stringify(arr), { mode: FLOOD_STATE_MODE2 });
64870
- renameSync9(tmp, path2);
64942
+ renameSync10(tmp, path2);
64871
64943
  try {
64872
64944
  chmodSync6(path2, FLOOD_STATE_MODE2);
64873
64945
  } catch {}
@@ -71141,6 +71213,9 @@ function projectTranscriptLine(line) {
71141
71213
  if (op === "dequeue") {
71142
71214
  return [{ kind: "dequeue" }];
71143
71215
  }
71216
+ if (op === "remove") {
71217
+ return [{ kind: "queue_remove", rawContent: obj.content ?? "" }];
71218
+ }
71144
71219
  return [];
71145
71220
  }
71146
71221
  if (type === "assistant") {
@@ -72946,10 +73021,10 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
72946
73021
 
72947
73022
  // gateway/auth-add-flow.ts
72948
73023
  import { execFileSync as execFileSync4 } from "node:child_process";
72949
- import { existsSync as existsSync21, mkdirSync as mkdirSync24, readFileSync as readFileSync22, readdirSync as readdirSync6, rmSync as rmSync5, statSync as statSync9, writeFileSync as writeFileSync20 } from "node:fs";
73024
+ import { existsSync as existsSync21, mkdirSync as mkdirSync24, readFileSync as readFileSync22, readdirSync as readdirSync6, rmSync as rmSync6, statSync as statSync9, writeFileSync as writeFileSync20 } from "node:fs";
72950
73025
  import { homedir as homedir8 } from "node:os";
72951
73026
  import { join as join29 } from "node:path";
72952
- import { randomBytes as randomBytes8 } from "node:crypto";
73027
+ import { randomBytes as randomBytes9 } from "node:crypto";
72953
73028
 
72954
73029
  // ../src/auth/manager.ts
72955
73030
  import {
@@ -72959,7 +73034,7 @@ import {
72959
73034
  writeFileSync as writeFileSync19,
72960
73035
  mkdirSync as mkdirSync23,
72961
73036
  mkdtempSync as mkdtempSync2,
72962
- rmSync as rmSync4,
73037
+ rmSync as rmSync5,
72963
73038
  chmodSync as chmodSync7,
72964
73039
  statSync as statSync8
72965
73040
  } from "node:fs";
@@ -73054,12 +73129,12 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
73054
73129
  }
73055
73130
  var pendingAuthAddFlows = new Map;
73056
73131
  function pickScratchDir(label, home2 = homedir8()) {
73057
- const suffix = randomBytes8(8).toString("hex");
73132
+ const suffix = randomBytes9(8).toString("hex");
73058
73133
  return join29(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
73059
73134
  }
73060
73135
  function cleanScratchDir(scratchDir) {
73061
73136
  try {
73062
- rmSync5(scratchDir, { recursive: true, force: true });
73137
+ rmSync6(scratchDir, { recursive: true, force: true });
73063
73138
  } catch {}
73064
73139
  }
73065
73140
  var AUTH_TMUX_SESSION_FILE = ".auth-tmux-session";
@@ -76377,7 +76452,7 @@ import {
76377
76452
  mkdirSync as mkdirSync26,
76378
76453
  readFileSync as readFileSync23,
76379
76454
  readdirSync as readdirSync7,
76380
- renameSync as renameSync10,
76455
+ renameSync as renameSync11,
76381
76456
  statSync as statSync10,
76382
76457
  unlinkSync as unlinkSync13,
76383
76458
  writeFileSync as writeFileSync21,
@@ -76413,7 +76488,7 @@ function writeOutboxRecordAtomic(record, stateDir) {
76413
76488
  return true;
76414
76489
  const tmpPath = join31(dir, `.${record.turnNonce}.${process.pid}.tmp`);
76415
76490
  writeFileSync21(tmpPath, JSON.stringify(record), { mode: 384 });
76416
- renameSync10(tmpPath, finalPath);
76491
+ renameSync11(tmpPath, finalPath);
76417
76492
  return true;
76418
76493
  } catch {
76419
76494
  return false;
@@ -76440,7 +76515,7 @@ function claimRecord(nonce, stateDir) {
76440
76515
  const from = join31(dir, `${nonce}.json`);
76441
76516
  const to = join31(dir, `${nonce}.sending`);
76442
76517
  try {
76443
- renameSync10(from, to);
76518
+ renameSync11(from, to);
76444
76519
  return to;
76445
76520
  } catch {
76446
76521
  return null;
@@ -76449,7 +76524,7 @@ function claimRecord(nonce, stateDir) {
76449
76524
  function releaseClaim(nonce, stateDir) {
76450
76525
  const dir = resolveOutboxDir(stateDir);
76451
76526
  try {
76452
- renameSync10(join31(dir, `${nonce}.sending`), join31(dir, `${nonce}.json`));
76527
+ renameSync11(join31(dir, `${nonce}.sending`), join31(dir, `${nonce}.json`));
76453
76528
  } catch {}
76454
76529
  }
76455
76530
  function removeClaimed(nonce, stateDir) {
@@ -76469,7 +76544,7 @@ function reclaimStaleSending(stateDir, now = Date.now()) {
76469
76544
  try {
76470
76545
  const st = statSync10(join31(dir, f));
76471
76546
  if (now - st.mtimeMs > OUTBOX_SENDING_TIMEOUT_MS) {
76472
- renameSync10(join31(dir, f), join31(dir, f.replace(/\.sending$/, ".json")));
76547
+ renameSync11(join31(dir, f), join31(dir, f.replace(/\.sending$/, ".json")));
76473
76548
  }
76474
76549
  } catch {}
76475
76550
  }
@@ -76543,7 +76618,7 @@ function compactJournalIfLarge(path2) {
76543
76618
  writeFileSync21(tmp, kept.join(`
76544
76619
  `) + `
76545
76620
  `, { mode: 384 });
76546
- renameSync10(tmp, path2);
76621
+ renameSync11(tmp, path2);
76547
76622
  } catch {}
76548
76623
  }
76549
76624
  function clearOutboxRecord(nonce, stateDir) {
@@ -76607,7 +76682,7 @@ import {
76607
76682
  existsSync as existsSync24,
76608
76683
  mkdirSync as mkdirSync27,
76609
76684
  readFileSync as readFileSync24,
76610
- renameSync as renameSync11,
76685
+ renameSync as renameSync12,
76611
76686
  writeFileSync as writeFileSync22,
76612
76687
  appendFileSync as appendFileSync5
76613
76688
  } from "node:fs";
@@ -76741,7 +76816,7 @@ function compactIfLarge(path2) {
76741
76816
  writeFileSync22(tmp, kept.join(`
76742
76817
  `) + `
76743
76818
  `, { mode: 384 });
76744
- renameSync11(tmp, path2);
76819
+ renameSync12(tmp, path2);
76745
76820
  } catch {}
76746
76821
  }
76747
76822
 
@@ -77198,7 +77273,7 @@ function eagerVoiceEnabled2() {
77198
77273
  }
77199
77274
 
77200
77275
  // photo-precheck.ts
77201
- import { closeSync as closeSync5, fstatSync, openSync as openSync5, readSync } from "node:fs";
77276
+ import { closeSync as closeSync6, fstatSync, openSync as openSync6, readSync } from "node:fs";
77202
77277
  var PHOTO_MAX_DIMENSION_SUM = 1e4;
77203
77278
  var PHOTO_MAX_ASPECT_RATIO = 10;
77204
77279
  var PHOTO_MAX_BYTES = 10 * 1024 * 1024;
@@ -77279,7 +77354,7 @@ var JPEG_PROBE_BYTES = 64 * 1024;
77279
77354
  function probeImageFile(path2) {
77280
77355
  let fd;
77281
77356
  try {
77282
- fd = openSync5(path2, "r");
77357
+ fd = openSync6(path2, "r");
77283
77358
  const bytes = fstatSync(fd).size;
77284
77359
  const headBuf = Buffer.alloc(HEADER_PROBE_BYTES);
77285
77360
  const headRead = readSync(fd, headBuf, 0, HEADER_PROBE_BYTES, 0);
@@ -77298,7 +77373,7 @@ function probeImageFile(path2) {
77298
77373
  } finally {
77299
77374
  if (fd !== undefined) {
77300
77375
  try {
77301
- closeSync5(fd);
77376
+ closeSync6(fd);
77302
77377
  } catch {}
77303
77378
  }
77304
77379
  }
@@ -79526,10 +79601,10 @@ function detectStatusSurfaceDegraded(t) {
79526
79601
 
79527
79602
  // gateway/turn-active-marker.ts
79528
79603
  import {
79529
- closeSync as closeSync6,
79604
+ closeSync as closeSync7,
79530
79605
  existsSync as existsSync28,
79531
79606
  mkdirSync as mkdirSync31,
79532
- openSync as openSync6,
79607
+ openSync as openSync7,
79533
79608
  readFileSync as readFileSync26,
79534
79609
  statSync as statSync12,
79535
79610
  unlinkSync as unlinkSync15,
@@ -79555,8 +79630,8 @@ function touchTurnActiveMarker(stateDir) {
79555
79630
  utimesSync(path2, now, now);
79556
79631
  } catch {
79557
79632
  try {
79558
- const fd = openSync6(path2, "r+");
79559
- closeSync6(fd);
79633
+ const fd = openSync7(path2, "r+");
79634
+ closeSync7(fd);
79560
79635
  } catch {}
79561
79636
  }
79562
79637
  }
@@ -79742,6 +79817,195 @@ function decideFallbackTeardownNotice2(input) {
79742
79817
  }
79743
79818
 
79744
79819
  // gateway/stream-render.ts
79820
+ var PARKED_TURN_START_MAX = 16;
79821
+ var PARKED_TURN_START_TTL_MS = 30 * 60000;
79822
+ var parkedTurnStarts = [];
79823
+ function pruneParkedTurnStarts(now) {
79824
+ for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
79825
+ if (now - parkedTurnStarts[i].parkedAt > PARKED_TURN_START_TTL_MS) {
79826
+ const [dropped] = parkedTurnStarts.splice(i, 1);
79827
+ process.stderr.write(`telegram gateway: parked-turn-start expired chat=${dropped.chatId ?? "-"} ` + `msg=${dropped.messageId ?? "-"} age_ms=${now - dropped.parkedAt}
79828
+ `);
79829
+ }
79830
+ }
79831
+ }
79832
+ function parkTurnStart(env, now) {
79833
+ parkedTurnStarts.push({ ...env, parkedAt: now });
79834
+ while (parkedTurnStarts.length > PARKED_TURN_START_MAX) {
79835
+ const [dropped] = parkedTurnStarts.splice(0, 1);
79836
+ process.stderr.write(`telegram gateway: parked-turn-start evicted (cap ${PARKED_TURN_START_MAX}) ` + `chat=${dropped.chatId ?? "-"} msg=${dropped.messageId ?? "-"}
79837
+ `);
79838
+ }
79839
+ }
79840
+ function takeParkedTurnStart() {
79841
+ return parkedTurnStarts.pop() ?? null;
79842
+ }
79843
+ function discardParkedTurnStart(rawContent) {
79844
+ for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
79845
+ if (parkedTurnStarts[i].rawContent === rawContent) {
79846
+ parkedTurnStarts.splice(i, 1);
79847
+ return true;
79848
+ }
79849
+ }
79850
+ return false;
79851
+ }
79852
+ function beginTurn(deps, ev) {
79853
+ const {
79854
+ HANDBACK_PRETURN_ENABLED,
79855
+ STATE_DIR,
79856
+ clearActivitySummary,
79857
+ extractUserPromptPreview,
79858
+ getCurrentTurn,
79859
+ getPendingPtyPartial,
79860
+ handbackPreturnSignal,
79861
+ handlePtyPartial,
79862
+ isDmChatId,
79863
+ makeNarrativeGate,
79864
+ pendingCrossTurnGate,
79865
+ preambleSuppressor,
79866
+ promoteQueuedStatus,
79867
+ rememberRecentTurn,
79868
+ scheduleEarlyLivenessOpen,
79869
+ setCurrentTurn,
79870
+ setPendingPtyPartial,
79871
+ startTurnTypingLoop,
79872
+ statusKey,
79873
+ turnsDb,
79874
+ typingWrapper
79875
+ } = deps;
79876
+ typingWrapper.drainAll();
79877
+ if (ev.chatId) {
79878
+ const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
79879
+ clearPending2(statusKey(ev.chatId, enqThreadId), "handback");
79880
+ }
79881
+ if (ev.chatId) {
79882
+ const prior = getCurrentTurn();
79883
+ if (prior?.answerStream != null) {
79884
+ prior.answerStream.forceNewMessage();
79885
+ prior.answerStream.stop();
79886
+ prior.answerStream = null;
79887
+ }
79888
+ if (prior?.orphanedReplyTimeoutId != null) {
79889
+ clearTimeout(prior.orphanedReplyTimeoutId);
79890
+ prior.orphanedReplyTimeoutId = null;
79891
+ }
79892
+ prior?.narrativeGate?.teardown();
79893
+ if (prior != null && prior.endedAt == null) {
79894
+ clearActivitySummary(prior);
79895
+ process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "superseded", prior, Date.now())}
79896
+ `);
79897
+ }
79898
+ const startedAt = Date.now();
79899
+ const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
79900
+ const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
79901
+ const xTurnGateKey = turnId;
79902
+ const consumedCrossTurnGate = pendingCrossTurnGate.get(xTurnGateKey);
79903
+ if (consumedCrossTurnGate != null)
79904
+ pendingCrossTurnGate.delete(xTurnGateKey);
79905
+ const next = {
79906
+ sessionChatId: ev.chatId,
79907
+ sessionThreadId: enqThreadIdNum,
79908
+ sourceMessageId: parseSourceMessageId(ev.messageId),
79909
+ startedAt,
79910
+ gatewayReceiveAt: startedAt,
79911
+ role: deriveTurnRole(ev.rawContent),
79912
+ ...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
79913
+ replyCalled: false,
79914
+ finalAnswerDelivered: false,
79915
+ finalAnswerSubstantive: false,
79916
+ finalAnswerEverDelivered: false,
79917
+ answerDelivered: false,
79918
+ flushedAnswerText: null,
79919
+ endedAt: null,
79920
+ firstPingAt: null,
79921
+ firstPingWasSubstantive: false,
79922
+ silentAnchorMessageId: null,
79923
+ silentAnchorText: "",
79924
+ capturedText: [],
79925
+ capturedBlockMeta: [],
79926
+ orphanedReplyTimeoutId: null,
79927
+ answerReadyFlushTimeoutId: null,
79928
+ liveness: new LivenessTracker(startedAt),
79929
+ turnId,
79930
+ registryKey: null,
79931
+ noReplyDrainTimer: null,
79932
+ lastAssistantMsgId: null,
79933
+ lastAssistantDone: false,
79934
+ toolCallCount: 0,
79935
+ labeledToolCount: 0,
79936
+ totalTokens: 0,
79937
+ seenUsageMessageIds: new Set,
79938
+ activityMessageId: null,
79939
+ activityInFlight: null,
79940
+ activityPendingRender: null,
79941
+ activityLastSentRender: null,
79942
+ activityEverOpened: false,
79943
+ activityDrainFailures: 0,
79944
+ mirrorLines: [],
79945
+ narrativeGate: undefined,
79946
+ lastReplyText: "",
79947
+ foregroundSubAgents: new Map,
79948
+ answerStream: null,
79949
+ isDm: isDmChatId(ev.chatId),
79950
+ emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
79951
+ };
79952
+ next.narrativeGate = makeNarrativeGate(next);
79953
+ if (HANDBACK_PRETURN_ENABLED) {
79954
+ const handbackAdoption = handbackPreturnSignal.tryAdopt(turnId);
79955
+ if (handbackAdoption != null) {
79956
+ if (handbackAdoption.activityMessageId != null) {
79957
+ next.activityMessageId = handbackAdoption.activityMessageId;
79958
+ next.activityEverOpened = true;
79959
+ }
79960
+ process.stderr.write(`telegram gateway: handback pre-turn adopted turnId=${turnId} ` + `key=${handbackAdoption.statusKey} card=${handbackAdoption.activityMessageId ?? "none"}
79961
+ `);
79962
+ }
79963
+ }
79964
+ startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
79965
+ setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
79966
+ scheduleEarlyLivenessOpen(next);
79967
+ process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
79968
+ `);
79969
+ rememberRecentTurn(next);
79970
+ promoteQueuedStatus(ev.chatId, enqThreadIdNum);
79971
+ shadowEmit({
79972
+ kind: "turnStart",
79973
+ key: statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : undefined),
79974
+ at: startedAt
79975
+ });
79976
+ preambleSuppressor.reset();
79977
+ clearSilentEndState2(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
79978
+ if (turnsDb != null) {
79979
+ const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
79980
+ const turnKey3 = chatKeyWithSuffix(ev.chatId, evThreadIdNum, String(startedAt));
79981
+ next.registryKey = turnKey3;
79982
+ const userPromptPreview = extractUserPromptPreview(ev.rawContent);
79983
+ try {
79984
+ recordTurnStart(turnsDb, {
79985
+ turnKey: turnKey3,
79986
+ chatId: String(ev.chatId),
79987
+ threadId: ev.threadId != null ? String(ev.threadId) : null,
79988
+ lastUserMsgId: ev.messageId != null ? String(ev.messageId) : null,
79989
+ userPromptPreview
79990
+ });
79991
+ } catch (err) {
79992
+ process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${turnKey3}: ${err.message}
79993
+ `);
79994
+ }
79995
+ writeTurnActiveMarker(STATE_DIR, {
79996
+ turnKey: turnKey3,
79997
+ chatId: String(ev.chatId),
79998
+ threadId: ev.threadId != null ? String(ev.threadId) : null,
79999
+ startedAt
80000
+ });
80001
+ }
80002
+ const pending = getPendingPtyPartial();
80003
+ if (pending != null) {
80004
+ setPendingPtyPartial(null);
80005
+ handlePtyPartial(pending);
80006
+ }
80007
+ }
80008
+ }
79745
80009
  function handleSessionEvent(deps, ev) {
79746
80010
  const {
79747
80011
  ANSWER_LANE,
@@ -79838,140 +80102,47 @@ function handleSessionEvent(deps, ev) {
79838
80102
  }
79839
80103
  switch (ev.kind) {
79840
80104
  case "enqueue": {
79841
- typingWrapper.drainAll();
79842
- if (ev.chatId) {
79843
- const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
79844
- clearPending2(statusKey(ev.chatId, enqThreadId), "handback");
79845
- }
80105
+ const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
80106
+ const now = Date.now();
79846
80107
  if (ev.chatId) {
79847
- const prior = getCurrentTurn();
79848
- if (prior?.answerStream != null) {
79849
- prior.answerStream.forceNewMessage();
79850
- prior.answerStream.stop();
79851
- prior.answerStream = null;
79852
- }
79853
- if (prior?.orphanedReplyTimeoutId != null) {
79854
- clearTimeout(prior.orphanedReplyTimeoutId);
79855
- prior.orphanedReplyTimeoutId = null;
79856
- }
79857
- prior?.narrativeGate?.teardown();
79858
- const startedAt = Date.now();
79859
- const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
79860
- const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
79861
- const xTurnGateKey = turnId;
79862
- const consumedCrossTurnGate = pendingCrossTurnGate.get(xTurnGateKey);
79863
- if (consumedCrossTurnGate != null)
79864
- pendingCrossTurnGate.delete(xTurnGateKey);
79865
- const next = {
79866
- sessionChatId: ev.chatId,
79867
- sessionThreadId: enqThreadIdNum,
79868
- sourceMessageId: parseSourceMessageId(ev.messageId),
79869
- startedAt,
79870
- gatewayReceiveAt: startedAt,
79871
- role: deriveTurnRole(ev.rawContent),
79872
- ...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
79873
- replyCalled: false,
79874
- finalAnswerDelivered: false,
79875
- finalAnswerSubstantive: false,
79876
- finalAnswerEverDelivered: false,
79877
- answerDelivered: false,
79878
- flushedAnswerText: null,
79879
- endedAt: null,
79880
- firstPingAt: null,
79881
- firstPingWasSubstantive: false,
79882
- silentAnchorMessageId: null,
79883
- silentAnchorText: "",
79884
- capturedText: [],
79885
- capturedBlockMeta: [],
79886
- orphanedReplyTimeoutId: null,
79887
- answerReadyFlushTimeoutId: null,
79888
- liveness: new LivenessTracker(startedAt),
79889
- turnId,
79890
- registryKey: null,
79891
- noReplyDrainTimer: null,
79892
- lastAssistantMsgId: null,
79893
- lastAssistantDone: false,
79894
- toolCallCount: 0,
79895
- labeledToolCount: 0,
79896
- totalTokens: 0,
79897
- seenUsageMessageIds: new Set,
79898
- activityMessageId: null,
79899
- activityInFlight: null,
79900
- activityPendingRender: null,
79901
- activityLastSentRender: null,
79902
- activityEverOpened: false,
79903
- activityDrainFailures: 0,
79904
- mirrorLines: [],
79905
- narrativeGate: undefined,
79906
- lastReplyText: "",
79907
- foregroundSubAgents: new Map,
79908
- answerStream: null,
79909
- isDm: isDmChatId(ev.chatId),
79910
- emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
79911
- };
79912
- next.narrativeGate = makeNarrativeGate(next);
79913
- if (HANDBACK_PRETURN_ENABLED) {
79914
- const handbackAdoption = handbackPreturnSignal.tryAdopt(turnId);
79915
- if (handbackAdoption != null) {
79916
- if (handbackAdoption.activityMessageId != null) {
79917
- next.activityMessageId = handbackAdoption.activityMessageId;
79918
- next.activityEverOpened = true;
79919
- }
79920
- process.stderr.write(`telegram gateway: handback pre-turn adopted turnId=${turnId} ` + `key=${handbackAdoption.statusKey} card=${handbackAdoption.activityMessageId ?? "none"}
79921
- `);
79922
- }
79923
- }
79924
- startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
79925
- setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
79926
- scheduleEarlyLivenessOpen(next);
79927
- process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
79928
- `);
79929
- rememberRecentTurn(next);
79930
- promoteQueuedStatus(ev.chatId, enqThreadIdNum);
79931
80108
  if (DELIVERY_CONFIRM_ENABLED) {
79932
80109
  ackDelivery(deliveryQueue, chatKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null), ev.messageId, ev.rawContent);
79933
80110
  }
79934
- shadowEmit({
79935
- kind: "turnStart",
79936
- key: statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : undefined),
79937
- at: startedAt
79938
- });
79939
- preambleSuppressor.reset();
79940
- clearSilentEndState2(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
79941
- if (turnsDb != null) {
79942
- const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
79943
- const turnKey3 = chatKeyWithSuffix(ev.chatId, evThreadIdNum, String(startedAt));
79944
- next.registryKey = turnKey3;
79945
- const userPromptPreview = extractUserPromptPreview(ev.rawContent);
79946
- try {
79947
- recordTurnStart(turnsDb, {
79948
- turnKey: turnKey3,
79949
- chatId: String(ev.chatId),
79950
- threadId: ev.threadId != null ? String(ev.threadId) : null,
79951
- lastUserMsgId: ev.messageId != null ? String(ev.messageId) : null,
79952
- userPromptPreview
79953
- });
79954
- } catch (err) {
79955
- process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${turnKey3}: ${err.message}
79956
- `);
79957
- }
79958
- writeTurnActiveMarker(STATE_DIR, {
79959
- turnKey: turnKey3,
79960
- chatId: String(ev.chatId),
79961
- threadId: ev.threadId != null ? String(ev.threadId) : null,
79962
- startedAt
79963
- });
79964
- }
79965
- const pending = getPendingPtyPartial();
79966
- if (pending != null) {
79967
- setPendingPtyPartial(null);
79968
- handlePtyPartial(pending);
79969
- }
79970
80111
  }
80112
+ pruneParkedTurnStarts(now);
80113
+ if (!ev.chatId) {
80114
+ beginTurn(deps, ev);
80115
+ return;
80116
+ }
80117
+ const live = getCurrentTurn();
80118
+ const sessionBusy = live != null && live.endedAt == null;
80119
+ if (!sessionBusy && parkedTurnStarts.length === 0) {
80120
+ beginTurn(deps, ev);
80121
+ return;
80122
+ }
80123
+ parkTurnStart({
80124
+ chatId: ev.chatId,
80125
+ messageId: ev.messageId,
80126
+ threadId: ev.threadId,
80127
+ rawContent: ev.rawContent
80128
+ }, now);
80129
+ process.stderr.write(`telegram gateway: turn-start parked (session busy) chat=${ev.chatId} ` + `thread=${enqThreadIdNum ?? "-"} msg=${ev.messageId ?? "-"} ` + `parked=${parkedTurnStarts.length}
80130
+ `);
80131
+ return;
80132
+ }
80133
+ case "dequeue": {
80134
+ pruneParkedTurnStarts(Date.now());
80135
+ const parked = takeParkedTurnStart();
80136
+ if (parked == null)
80137
+ return;
80138
+ beginTurn(deps, parked);
79971
80139
  return;
79972
80140
  }
79973
- case "dequeue":
80141
+ case "queue_remove": {
80142
+ pruneParkedTurnStarts(Date.now());
80143
+ discardParkedTurnStart(ev.rawContent);
79974
80144
  return;
80145
+ }
79975
80146
  case "model": {
79976
80147
  const turn = getCurrentTurn();
79977
80148
  if (turn != null) {
@@ -80882,7 +81053,8 @@ function createNarrativeLane(deps) {
80882
81053
  toolCount: turn.labeledToolCount,
80883
81054
  state: final ? "done" : "running",
80884
81055
  model: turn.currentModel,
80885
- totalTokens: turn.totalTokens
81056
+ totalTokens: turn.totalTokens,
81057
+ resultText: final ? turn.lastReplyText : undefined
80886
81058
  };
80887
81059
  return renderActivityFeedWithNested(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
80888
81060
  }
@@ -81199,7 +81371,8 @@ function createNarrativeLane(deps) {
81199
81371
  elapsedMs: livenessElapsed,
81200
81372
  toolCount: turn.labeledToolCount,
81201
81373
  state: "done",
81202
- model: turn.currentModel
81374
+ model: turn.currentModel,
81375
+ resultText: turn.lastReplyText
81203
81376
  };
81204
81377
  finalHtml = renderActivityFeedWithNested(["Working\u2026"], [], true, "", undefined, livenessHeader);
81205
81378
  }
@@ -81569,7 +81742,7 @@ function resolveAgentDirFromEnv() {
81569
81742
  }
81570
81743
 
81571
81744
  // active-reactions.ts
81572
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync25, renameSync as renameSync12, existsSync as existsSync29, unlinkSync as unlinkSync16 } from "node:fs";
81745
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync25, renameSync as renameSync13, existsSync as existsSync29, unlinkSync as unlinkSync16 } from "node:fs";
81573
81746
  import { join as join37 } from "node:path";
81574
81747
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
81575
81748
  function reactionsPath(agentDir) {
@@ -81615,7 +81788,7 @@ function writeActiveReactions(agentDir, reactions) {
81615
81788
  try {
81616
81789
  writeFileSync25(tmp, JSON.stringify(reactions) + `
81617
81790
  `, "utf-8");
81618
- renameSync12(tmp, p);
81791
+ renameSync13(tmp, p);
81619
81792
  } catch {}
81620
81793
  }
81621
81794
  function addActiveReaction(agentDir, reaction) {
@@ -81637,7 +81810,7 @@ function clearActiveReactions(agentDir) {
81637
81810
  }
81638
81811
 
81639
81812
  // active-reactions.ts
81640
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync26, renameSync as renameSync13, existsSync as existsSync30, unlinkSync as unlinkSync17 } from "node:fs";
81813
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync26, renameSync as renameSync14, existsSync as existsSync30, unlinkSync as unlinkSync17 } from "node:fs";
81641
81814
  import { join as join38 } from "node:path";
81642
81815
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
81643
81816
  function reactionsPath2(agentDir) {
@@ -83866,7 +84039,7 @@ async function menuWithBannerStatic(deps, banner) {
83866
84039
  }
83867
84040
 
83868
84041
  // gateway/session-model-file.ts
83869
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync27, renameSync as renameSync14, rmSync as rmSync6 } from "node:fs";
84042
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync27, renameSync as renameSync15, rmSync as rmSync7 } from "node:fs";
83870
84043
  import { join as join40 } from "node:path";
83871
84044
  var SESSION_MODEL_FILE = ".session-model";
83872
84045
  var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
@@ -83874,7 +84047,7 @@ var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
83874
84047
  function atomicWrite(path2, content3) {
83875
84048
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
83876
84049
  writeFileSync27(tmp, content3, "utf8");
83877
- renameSync14(tmp, path2);
84050
+ renameSync15(tmp, path2);
83878
84051
  }
83879
84052
  function serializeSessionModel(rec) {
83880
84053
  return `${JSON.stringify({
@@ -83889,7 +84062,7 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
83889
84062
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
83890
84063
  }
83891
84064
  try {
83892
- rmSync6(join40(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
84065
+ rmSync7(join40(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
83893
84066
  } catch {}
83894
84067
  atomicWrite(join40(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
83895
84068
  }
@@ -83902,15 +84075,15 @@ function readSessionModelFileRaw(agentDir) {
83902
84075
  }
83903
84076
  function clearSessionModelFile(agentDir) {
83904
84077
  try {
83905
- rmSync6(join40(agentDir, SESSION_MODEL_FILE), { force: true });
84078
+ rmSync7(join40(agentDir, SESSION_MODEL_FILE), { force: true });
83906
84079
  } catch {}
83907
84080
  }
83908
84081
  function consumeSessionModelCarrierOnHealthyBoot(agentDir) {
83909
84082
  try {
83910
- rmSync6(join40(agentDir, SESSION_MODEL_FILE), { force: true });
84083
+ rmSync7(join40(agentDir, SESSION_MODEL_FILE), { force: true });
83911
84084
  } catch {}
83912
84085
  try {
83913
- rmSync6(join40(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
84086
+ rmSync7(join40(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
83914
84087
  } catch {}
83915
84088
  }
83916
84089
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -83941,7 +84114,7 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
83941
84114
  }
83942
84115
  function clearSessionEffortFile(agentDir) {
83943
84116
  try {
83944
- rmSync6(join40(agentDir, SESSION_EFFORT_FILE), { force: true });
84117
+ rmSync7(join40(agentDir, SESSION_EFFORT_FILE), { force: true });
83945
84118
  } catch {}
83946
84119
  }
83947
84120
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -83983,7 +84156,7 @@ function readPremiumRecoveryFile(agentDir) {
83983
84156
  }
83984
84157
  function clearPremiumRecoveryFile(agentDir) {
83985
84158
  try {
83986
- rmSync6(join40(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
84159
+ rmSync7(join40(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
83987
84160
  } catch {}
83988
84161
  }
83989
84162
 
@@ -86239,7 +86412,7 @@ function nextCompactNotify(state6, ev) {
86239
86412
 
86240
86413
  // gateway/hostd-dispatch.ts
86241
86414
  import { existsSync as existsSync36 } from "node:fs";
86242
- import { randomBytes as randomBytes9 } from "node:crypto";
86415
+ import { randomBytes as randomBytes10 } from "node:crypto";
86243
86416
  init_loader();
86244
86417
  var _hostdEnabled2;
86245
86418
  function isHostdEnabled2() {
@@ -86286,7 +86459,7 @@ async function tryHostdDispatch2(agentName3, req, timeoutMs = 5000) {
86286
86459
  }
86287
86460
  }
86288
86461
  function hostdRequestId2(prefix) {
86289
- return `${prefix}-${Date.now()}-${randomBytes9(4).toString("hex")}`;
86462
+ return `${prefix}-${Date.now()}-${randomBytes10(4).toString("hex")}`;
86290
86463
  }
86291
86464
  function withOperatorAttestation(req, passphrase) {
86292
86465
  if (!passphrase)
@@ -86348,6 +86521,56 @@ function shouldSweepChatAtBoot(chatId) {
86348
86521
  return n < 0;
86349
86522
  }
86350
86523
 
86524
+ // gateway/stale-pin-sweep-store.ts
86525
+ var SWEEP_MAX_ATTEMPTS = 8;
86526
+ var SWEEP_ENVELOPE_VERSIONS = new Set([1]);
86527
+ function sweepTargetKey(chatId, threadId) {
86528
+ return `${chatId}:${threadId ?? "-"}`;
86529
+ }
86530
+ function isCursorRow(x) {
86531
+ if (x == null || typeof x !== "object")
86532
+ return false;
86533
+ const o = x;
86534
+ return typeof o.chatId === "string" && o.chatId.length > 0 && (o.threadId === undefined || typeof o.threadId === "number") && (o.kind === "dm" || o.kind === "forum-topic" || o.kind === "supergroup") && typeof o.popped === "number" && typeof o.done === "boolean" && typeof o.attempts === "number" && (o.doneIds === undefined || Array.isArray(o.doneIds) && o.doneIds.every((n) => typeof n === "number")) && (o.lastStatus === undefined || typeof o.lastStatus === "string") && typeof o.updatedAt === "number";
86535
+ }
86536
+ function loadSweepCursors(path2, fs2) {
86537
+ if (!fs2.existsSync(path2))
86538
+ return [];
86539
+ let raw = "";
86540
+ try {
86541
+ raw = fs2.readFileSync(path2);
86542
+ } catch {
86543
+ return [];
86544
+ }
86545
+ let parsed;
86546
+ try {
86547
+ parsed = JSON.parse(raw);
86548
+ } catch {
86549
+ return [];
86550
+ }
86551
+ if (parsed == null || typeof parsed !== "object")
86552
+ return [];
86553
+ const env = parsed;
86554
+ if (typeof env.v !== "number" || !SWEEP_ENVELOPE_VERSIONS.has(env.v) || !Array.isArray(env.cursors)) {
86555
+ return [];
86556
+ }
86557
+ return env.cursors.filter(isCursorRow);
86558
+ }
86559
+ function persistSweepCursors(path2, fs2, cursors, log = (l) => process.stderr.write(l)) {
86560
+ const env = { v: 1, cursors: [...cursors] };
86561
+ try {
86562
+ fs2.writeFileSync(path2, JSON.stringify(env));
86563
+ } catch (err) {
86564
+ log(`stale-pin-sweep-store: persist FAILED path=${path2}: ${err.message} \u2014 ` + `durability degraded; a restart mid-sweep will re-seed instead of resume
86565
+ `);
86566
+ }
86567
+ }
86568
+ function upsertSweepCursor(path2, fs2, cursor, log = (l) => process.stderr.write(l)) {
86569
+ const key = sweepTargetKey(cursor.chatId, cursor.threadId);
86570
+ const others = loadSweepCursors(path2, fs2).filter((c) => sweepTargetKey(c.chatId, c.threadId) !== key);
86571
+ persistSweepCursors(path2, fs2, [...others, cursor], log);
86572
+ }
86573
+
86351
86574
  // gateway/boot-sweep-gate.ts
86352
86575
  function createBootSweepGate(args) {
86353
86576
  let armed = false;
@@ -86392,21 +86615,22 @@ async function step(name, fn, log) {
86392
86615
  }
86393
86616
  async function runBootPinSweepSteps(deps) {
86394
86617
  const log = deps.log ?? ((l) => process.stderr.write(l));
86395
- let dmChatIds = [];
86396
- await step("dm-chat-scan", async () => {
86397
- dmChatIds = deps.scanDmChatIds();
86618
+ let targets = [];
86619
+ await step("sweep-target-scan", async () => {
86620
+ targets = deps.scanSweepTargets();
86398
86621
  }, log);
86399
86622
  await step("status-pin-cleanup", deps.statusPinCleanup, log);
86400
86623
  await step("activity-card-reaper", deps.activityCardReaper, log);
86401
86624
  await step("queued-card-reaper", deps.queuedCardReaper, log);
86402
- await step("enable-dm-sweep", async () => deps.enableDmSweep(), log);
86403
- for (const id of dmChatIds)
86404
- await step(`dm-sweep:${id}`, () => deps.sweepDm(id), log);
86625
+ await step("enable-sweep", async () => deps.enableSweep(), log);
86626
+ for (const t of targets) {
86627
+ await step(`stale-pin-sweep:${sweepTargetKey(t.chatId, t.threadId)}`, () => deps.sweepTarget(t), log);
86628
+ }
86405
86629
  }
86406
86630
 
86407
86631
  // gateway/status-pin-retarget.ts
86408
86632
  async function runStatusPinReconcile(args) {
86409
- const { pinKey, chatId, prev, desired, persist, runPin, claims } = args;
86633
+ const { pinKey, chatId, threadId, prev, desired, persist, runPin, claims } = args;
86410
86634
  const now = args.now ?? Date.now;
86411
86635
  const commit = (next) => {
86412
86636
  if (next == null) {
@@ -86414,7 +86638,7 @@ async function runStatusPinReconcile(args) {
86414
86638
  return;
86415
86639
  }
86416
86640
  const pinnedAt = claims.get(pinKey)?.pinnedAt ?? now();
86417
- claims.set(pinKey, { messageId: next.messageId, chatId, pinnedAt });
86641
+ claims.set(pinKey, { messageId: next.messageId, chatId, threadId, pinnedAt });
86418
86642
  };
86419
86643
  const runLeg = (legAction, from) => {
86420
86644
  if (persist == null)
@@ -86425,6 +86649,7 @@ async function runStatusPinReconcile(args) {
86425
86649
  fs: persist.fs,
86426
86650
  pinKey,
86427
86651
  chatId,
86652
+ threadId,
86428
86653
  op,
86429
86654
  applyPin: () => runPin(legAction, from),
86430
86655
  now: now()
@@ -86524,24 +86749,77 @@ function createStatusPinApi(getBot, robust) {
86524
86749
  };
86525
86750
  }
86526
86751
 
86527
- // gateway/dm-pin-sweep.ts
86528
- function isDmChatId(chatId) {
86529
- const n = Number(chatId);
86530
- return Number.isFinite(n) && Number.isInteger(n) && n > 0;
86531
- }
86532
- function collectDmChatIdsFromStores(input) {
86533
- const out = new Set;
86752
+ // gateway/stale-pin-sweep.ts
86753
+ var GROUP_SWEEP_GATE = {
86754
+ minCallDelayMs: 6000,
86755
+ maxPopsPerChatPerSweep: Number.POSITIVE_INFINITY,
86756
+ maxPinOpsPerMinute: 10
86757
+ };
86758
+ function collectSweepTargets(input) {
86759
+ const out = new Map;
86534
86760
  const add = (rows) => {
86535
86761
  if (rows == null)
86536
86762
  return;
86537
- for (const r of rows)
86538
- if (isDmChatId(r.chatId))
86539
- out.add(r.chatId);
86763
+ for (const r of rows) {
86764
+ if (typeof r.chatId !== "string" || r.chatId.length === 0)
86765
+ continue;
86766
+ const threadId = r.threadId ?? undefined;
86767
+ const key = sweepTargetKey(r.chatId, threadId);
86768
+ let target = out.get(key);
86769
+ if (target == null) {
86770
+ target = { chatId: r.chatId, threadId, messageIds: [] };
86771
+ out.set(key, target);
86772
+ }
86773
+ const id = r.messageId ?? (r.pinned === true ? r.activityMessageId : undefined);
86774
+ if (typeof id === "number" && !target.messageIds.includes(id))
86775
+ target.messageIds.push(id);
86776
+ }
86540
86777
  };
86541
86778
  add(input.statusPins);
86542
86779
  add(input.activityCards);
86543
86780
  add(input.queuedCards);
86544
- return [...out];
86781
+ return [...out.values()];
86782
+ }
86783
+
86784
+ // gateway/stale-pin-sweep.ts
86785
+ var DM_SWEEP_GATE = {
86786
+ minCallDelayMs: 1500,
86787
+ maxPopsPerChatPerSweep: 40,
86788
+ maxPinOpsPerMinute: 30
86789
+ };
86790
+ var GROUP_SWEEP_GATE2 = {
86791
+ minCallDelayMs: 6000,
86792
+ maxPopsPerChatPerSweep: Number.POSITIVE_INFINITY,
86793
+ maxPinOpsPerMinute: 10
86794
+ };
86795
+ var FLOOD_BACKOFF_FACTOR = 2;
86796
+ var MAX_CONSECUTIVE_FLOOD_WAITS = 3;
86797
+ var MAX_IDENTICAL_RETRY_AFTER_NO_PROGRESS = 2;
86798
+ var CIRCUIT_BREAKER_RETRY_AFTER_SEC = 60;
86799
+ var VERIFY_READ_GAP_MS = 1800;
86800
+ var VERIFY_READ_MAX_PAIRS = 3;
86801
+ var UNPIN_ALL_FORUM_TOPIC_ENABLED = false;
86802
+ function mayUnpinAllForumTopic(kind, override) {
86803
+ if (kind !== "forum-topic")
86804
+ return false;
86805
+ return override ?? UNPIN_ALL_FORUM_TOPIC_ENABLED;
86806
+ }
86807
+ function gateFor(kind) {
86808
+ return kind === "dm" ? DM_SWEEP_GATE : GROUP_SWEEP_GATE2;
86809
+ }
86810
+ function budgetClassFor(kind) {
86811
+ return kind === "dm" ? "dm" : "group";
86812
+ }
86813
+ function isDmChatId(chatId) {
86814
+ const n = Number(chatId);
86815
+ return Number.isFinite(n) && Number.isInteger(n) && n > 0;
86816
+ }
86817
+ function classifyChatForSweep(args) {
86818
+ if (isDmChatId(args.chatId))
86819
+ return "dm";
86820
+ if (args.isForum === true && args.threadId != null)
86821
+ return "forum-topic";
86822
+ return "supergroup";
86545
86823
  }
86546
86824
  function unexpiredStoreRepinIds(rows, chatId, now) {
86547
86825
  const out = [];
@@ -86553,41 +86831,571 @@ function unexpiredStoreRepinIds(rows, chatId, now) {
86553
86831
  }
86554
86832
  return out;
86555
86833
  }
86556
- function createDmPinSweeper(deps) {
86557
- const swept = new Set;
86834
+ function description(err) {
86835
+ if (err != null && typeof err === "object") {
86836
+ const o = err;
86837
+ if (typeof o.description === "string" && o.description.length > 0) {
86838
+ return o.description.toLowerCase();
86839
+ }
86840
+ if (typeof o.message === "string" && o.message.length > 0)
86841
+ return o.message.toLowerCase();
86842
+ }
86843
+ return String(err).toLowerCase();
86844
+ }
86845
+ function retryAfterSeconds(err) {
86846
+ if (err == null || typeof err !== "object")
86847
+ return null;
86848
+ const o = err;
86849
+ const fromParams = o.parameters?.retry_after;
86850
+ if (typeof fromParams === "number" && fromParams >= 0)
86851
+ return fromParams;
86852
+ if (o.error_code === 429) {
86853
+ const m = /retry after (\d+)/.exec(description(err));
86854
+ if (m != null)
86855
+ return Number(m[1]);
86856
+ return 0;
86857
+ }
86858
+ return null;
86859
+ }
86860
+ function isPeerFloodError(err) {
86861
+ return description(err).includes("peer_flood");
86862
+ }
86863
+ function isPinRightsError2(err) {
86864
+ return description(err).includes("not enough rights");
86865
+ }
86866
+ function isNothingToUnpinError(err) {
86867
+ return description(err).includes("message to unpin not found");
86868
+ }
86869
+ function createPinOpBudget(now) {
86870
+ const windows = { dm: [], group: [] };
86871
+ const limits = {
86872
+ dm: DM_SWEEP_GATE.maxPinOpsPerMinute,
86873
+ group: GROUP_SWEEP_GATE2.maxPinOpsPerMinute
86874
+ };
86875
+ const prune = (cls, t) => {
86876
+ const w = windows[cls];
86877
+ while (w.length > 0 && t - w[0] >= 60000)
86878
+ w.shift();
86879
+ };
86558
86880
  return {
86559
- hasSwept: (chatId) => swept.has(chatId),
86560
- async sweep(chatId) {
86561
- if (!isDmChatId(chatId))
86562
- return;
86563
- if (!deps.eligible())
86564
- return;
86565
- if (swept.has(chatId))
86566
- return;
86567
- swept.add(chatId);
86568
- const liveIds = [...new Set(deps.liveTrackedMessageIds(chatId))];
86881
+ waitMs(cls) {
86882
+ const t = now();
86883
+ prune(cls, t);
86884
+ const w = windows[cls];
86885
+ if (w.length < limits[cls])
86886
+ return 0;
86887
+ return Math.max(0, 60000 - (t - w[0]));
86888
+ },
86889
+ record(cls) {
86890
+ windows[cls].push(now());
86891
+ }
86892
+ };
86893
+ }
86894
+ function createStalePinSweeper(deps) {
86895
+ const log = deps.log ?? ((l) => process.stderr.write(l));
86896
+ const budget = createPinOpBudget(deps.now);
86897
+ let circuitOpen = false;
86898
+ let lastWriteAt = 0;
86899
+ const commit = (cursor) => {
86900
+ try {
86901
+ upsertSweepCursor(deps.store.path, deps.store.fs, cursor, log);
86902
+ } catch (err) {
86903
+ log(`stale-pin-sweep: cursor persist failed: ${err.message}
86904
+ `);
86905
+ }
86906
+ };
86907
+ const loadCursor = (t, kind) => {
86908
+ const key = sweepTargetKey(t.chatId, t.threadId);
86909
+ let rows = [];
86910
+ try {
86911
+ rows = loadSweepCursors(deps.store.path, deps.store.fs);
86912
+ } catch {
86913
+ rows = [];
86914
+ }
86915
+ const existing = rows.find((c) => sweepTargetKey(c.chatId, c.threadId) === key);
86916
+ if (existing != null)
86917
+ return { ...existing, kind };
86918
+ return {
86919
+ chatId: t.chatId,
86920
+ threadId: t.threadId,
86921
+ kind,
86922
+ popped: 0,
86923
+ done: false,
86924
+ attempts: 0,
86925
+ updatedAt: deps.now()
86926
+ };
86927
+ };
86928
+ const awaitWriteSlot = async (kind) => {
86929
+ const cls = budgetClassFor(kind);
86930
+ const wait = budget.waitMs(cls);
86931
+ if (wait > 0)
86932
+ return false;
86933
+ const gate = gateFor(kind);
86934
+ const since = deps.now() - lastWriteAt;
86935
+ if (lastWriteAt > 0 && since < gate.minCallDelayMs) {
86936
+ await deps.sleep(gate.minCallDelayMs - since);
86937
+ }
86938
+ lastWriteAt = deps.now();
86939
+ budget.record(cls);
86940
+ return true;
86941
+ };
86942
+ const readTopVerified = async (chatId) => {
86943
+ const read = async () => {
86569
86944
  try {
86570
- await deps.unpinAll(chatId);
86945
+ return { v: await deps.getTopPinnedMessageId(chatId) };
86946
+ } catch {
86947
+ return null;
86948
+ }
86949
+ };
86950
+ let prev = await read();
86951
+ if (prev == null)
86952
+ return null;
86953
+ for (let i = 0;i < VERIFY_READ_MAX_PAIRS; i++) {
86954
+ await deps.sleep(VERIFY_READ_GAP_MS);
86955
+ const next = await read();
86956
+ if (next == null)
86957
+ return null;
86958
+ if (next.v === prev.v)
86959
+ return { top: next.v };
86960
+ prev = next;
86961
+ }
86962
+ return null;
86963
+ };
86964
+ const classify = (err) => {
86965
+ if (isPeerFloodError(err))
86966
+ return { kind: "breaker", detail: "PEER_FLOOD" };
86967
+ const retry = retryAfterSeconds(err);
86968
+ if (retry != null) {
86969
+ if (retry > CIRCUIT_BREAKER_RETRY_AFTER_SEC) {
86970
+ return { kind: "breaker", detail: `retry_after=${retry}s > ${CIRCUIT_BREAKER_RETRY_AFTER_SEC}s` };
86971
+ }
86972
+ return { kind: "flood", seconds: retry };
86973
+ }
86974
+ if (isPinRightsError2(err))
86975
+ return { kind: "rights" };
86976
+ return { kind: "other", detail: err?.message ?? String(err) };
86977
+ };
86978
+ const repinUnpinLoop = async (target, kind, cursor) => {
86979
+ const gate = gateFor(kind);
86980
+ let popped = 0;
86981
+ let consecutiveFloods = 0;
86982
+ let lastRetryAfter = null;
86983
+ let noProgressAtSameRetryAfter = 0;
86984
+ let lastObservedTop = null;
86985
+ let unverifiedPop = false;
86986
+ const creditPop = () => {
86987
+ popped++;
86988
+ cursor.popped++;
86989
+ cursor.updatedAt = deps.now();
86990
+ commit(cursor);
86991
+ };
86992
+ for (let i = 0;i < gate.maxPopsPerChatPerSweep; i++) {
86993
+ if (circuitOpen) {
86994
+ return { status: "aborted-circuit-breaker", popped, detail: "breaker open" };
86995
+ }
86996
+ const observed = await readTopVerified(target.chatId);
86997
+ if (observed == null) {
86998
+ return { status: "incomplete", popped, detail: "pin-stack read unverifiable" };
86999
+ }
87000
+ if (unverifiedPop) {
87001
+ if (observed.top != null && observed.top === lastObservedTop) {
87002
+ return {
87003
+ status: "incomplete",
87004
+ popped,
87005
+ detail: `no progress: message ${observed.top} still on top after a repin+unpin`
87006
+ };
87007
+ }
87008
+ creditPop();
87009
+ unverifiedPop = false;
87010
+ }
87011
+ if (observed.top == null) {
87012
+ return { status: "drained", popped };
87013
+ }
87014
+ lastObservedTop = observed.top;
87015
+ if (!await awaitWriteSlot(kind)) {
87016
+ return { status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" };
87017
+ }
87018
+ try {
87019
+ await deps.pinSilent(target.chatId, observed.top);
86571
87020
  } catch (err) {
86572
- const msg = err instanceof Error ? err.message : String(err);
86573
- deps.log?.(`telegram gateway: dm-pin-sweep: unpin-all failed (chat=${chatId}): ${msg}
86574
- `);
87021
+ const d = classify(err);
87022
+ if (d.kind === "breaker") {
87023
+ circuitOpen = true;
87024
+ return { status: "aborted-circuit-breaker", popped, detail: d.detail };
87025
+ }
87026
+ if (d.kind === "rights")
87027
+ return { status: "skipped-no-rights", popped };
87028
+ if (d.kind === "flood") {
87029
+ consecutiveFloods++;
87030
+ if (lastRetryAfter === d.seconds)
87031
+ noProgressAtSameRetryAfter++;
87032
+ else
87033
+ noProgressAtSameRetryAfter = 1;
87034
+ lastRetryAfter = d.seconds;
87035
+ if (consecutiveFloods >= MAX_CONSECUTIVE_FLOOD_WAITS || noProgressAtSameRetryAfter >= MAX_IDENTICAL_RETRY_AFTER_NO_PROGRESS) {
87036
+ return {
87037
+ status: "deferred-flood",
87038
+ popped,
87039
+ detail: `flood: ${consecutiveFloods} consecutive 429s, retry_after=${d.seconds}s`
87040
+ };
87041
+ }
87042
+ await deps.sleep(d.seconds * 1000 * Math.pow(FLOOD_BACKOFF_FACTOR, consecutiveFloods - 1));
87043
+ continue;
87044
+ }
87045
+ return { status: "error", popped, detail: d.detail };
87046
+ }
87047
+ if (!await awaitWriteSlot(kind)) {
87048
+ return { status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" };
87049
+ }
87050
+ try {
87051
+ await deps.unpin(target.chatId, observed.top);
87052
+ } catch (err) {
87053
+ const d = classify(err);
87054
+ if (d.kind === "breaker") {
87055
+ circuitOpen = true;
87056
+ return { status: "aborted-circuit-breaker", popped, detail: d.detail };
87057
+ }
87058
+ if (d.kind === "rights")
87059
+ return { status: "skipped-no-rights", popped };
87060
+ if (isNothingToUnpinError(err))
87061
+ return { status: "drained", popped };
87062
+ if (d.kind === "flood") {
87063
+ consecutiveFloods++;
87064
+ if (lastRetryAfter === d.seconds)
87065
+ noProgressAtSameRetryAfter++;
87066
+ else
87067
+ noProgressAtSameRetryAfter = 1;
87068
+ lastRetryAfter = d.seconds;
87069
+ if (consecutiveFloods >= MAX_CONSECUTIVE_FLOOD_WAITS || noProgressAtSameRetryAfter >= MAX_IDENTICAL_RETRY_AFTER_NO_PROGRESS) {
87070
+ return {
87071
+ status: "deferred-flood",
87072
+ popped,
87073
+ detail: `flood: ${consecutiveFloods} consecutive 429s, retry_after=${d.seconds}s`
87074
+ };
87075
+ }
87076
+ await deps.sleep(d.seconds * 1000 * Math.pow(FLOOD_BACKOFF_FACTOR, consecutiveFloods - 1));
87077
+ continue;
87078
+ }
87079
+ return { status: "error", popped, detail: d.detail };
86575
87080
  }
86576
- for (const messageId of liveIds) {
87081
+ consecutiveFloods = 0;
87082
+ unverifiedPop = true;
87083
+ }
87084
+ const finalRead = await readTopVerified(target.chatId);
87085
+ if (unverifiedPop && finalRead != null && finalRead.top !== lastObservedTop)
87086
+ creditPop();
87087
+ if (finalRead != null && finalRead.top == null)
87088
+ return { status: "drained", popped };
87089
+ return {
87090
+ status: "deferred-budget",
87091
+ popped,
87092
+ detail: `hit the ${gate.maxPopsPerChatPerSweep}-pop cap for this chat; yielding to next boot`
87093
+ };
87094
+ };
87095
+ const targetedUnpinDrain = async (target, kind, cursor, ids) => {
87096
+ const alreadyDone = new Set(cursor.doneIds ?? []);
87097
+ const pending = ids.filter((id) => !alreadyDone.has(id));
87098
+ if (pending.length === 0) {
87099
+ return { status: "drained", popped: 0, issued: 0, detail: "every recorded id already unpinned" };
87100
+ }
87101
+ const before = await readTopVerified(target.chatId);
87102
+ let issued = 0;
87103
+ let deferred = null;
87104
+ for (const id of pending) {
87105
+ if (circuitOpen) {
87106
+ deferred = { status: "aborted-circuit-breaker", popped: 0, issued, detail: "breaker open" };
87107
+ break;
87108
+ }
87109
+ if (!await awaitWriteSlot(kind)) {
87110
+ deferred = {
87111
+ status: "deferred-budget",
87112
+ popped: 0,
87113
+ issued,
87114
+ detail: `per-minute pin-op budget spent with ${pending.length - issued} ids left`
87115
+ };
87116
+ break;
87117
+ }
87118
+ try {
87119
+ await deps.unpin(target.chatId, id);
87120
+ } catch (err) {
87121
+ const d = classify(err);
87122
+ if (d.kind === "breaker") {
87123
+ circuitOpen = true;
87124
+ deferred = { status: "aborted-circuit-breaker", popped: 0, issued, detail: d.detail };
87125
+ break;
87126
+ }
87127
+ if (d.kind === "rights") {
87128
+ deferred = { status: "skipped-no-rights", popped: 0, issued };
87129
+ break;
87130
+ }
87131
+ if (d.kind === "flood") {
87132
+ deferred = {
87133
+ status: "deferred-flood",
87134
+ popped: 0,
87135
+ issued,
87136
+ detail: `flood: retry_after=${d.seconds}s`
87137
+ };
87138
+ break;
87139
+ }
87140
+ if (!isNothingToUnpinError(err)) {
87141
+ deferred = { status: "error", popped: 0, issued, detail: d.detail };
87142
+ break;
87143
+ }
87144
+ }
87145
+ issued++;
87146
+ alreadyDone.add(id);
87147
+ cursor.doneIds = [...alreadyDone];
87148
+ cursor.updatedAt = deps.now();
87149
+ commit(cursor);
87150
+ }
87151
+ const after = await readTopVerified(target.chatId);
87152
+ if (after == null) {
87153
+ return {
87154
+ status: deferred?.status ?? "incomplete",
87155
+ popped: 0,
87156
+ issued,
87157
+ detail: "pin-stack read unverifiable after the unpin pass"
87158
+ };
87159
+ }
87160
+ if (after.top == null) {
87161
+ const popped2 = issued;
87162
+ cursor.popped += popped2;
87163
+ commit(cursor);
87164
+ return { status: "drained", popped: popped2, issued };
87165
+ }
87166
+ if (deferred != null)
87167
+ return { ...deferred, popped: 0 };
87168
+ const ours = new Set(ids);
87169
+ if (ours.has(after.top)) {
87170
+ return {
87171
+ status: "incomplete",
87172
+ popped: 0,
87173
+ issued,
87174
+ detail: `recorded pin ${after.top} is still on top after a targeted unpin`
87175
+ };
87176
+ }
87177
+ const popped = before != null && before.top !== after.top ? 1 : 0;
87178
+ cursor.popped += popped;
87179
+ commit(cursor);
87180
+ return {
87181
+ status: "drained",
87182
+ popped,
87183
+ issued,
87184
+ detail: `unpinned ${issued} recorded id(s); top ${after.top} is not ours and was left alone`
87185
+ };
87186
+ };
87187
+ const forumTopicDrain = async (target, cursor) => {
87188
+ const threadId = target.threadId;
87189
+ if (threadId == null) {
87190
+ return { status: "skipped-nothing-recorded", popped: 0, detail: "no message_thread_id" };
87191
+ }
87192
+ if (!await awaitWriteSlot("forum-topic")) {
87193
+ return { status: "deferred-budget", popped: 0, detail: "per-minute pin-op budget spent" };
87194
+ }
87195
+ try {
87196
+ await deps.unpinAllForumTopicMessages(target.chatId, threadId);
87197
+ } catch (err) {
87198
+ const d = classify(err);
87199
+ if (d.kind === "breaker") {
87200
+ circuitOpen = true;
87201
+ return { status: "aborted-circuit-breaker", popped: 0, detail: d.detail };
87202
+ }
87203
+ if (d.kind === "rights")
87204
+ return { status: "skipped-no-rights", popped: 0 };
87205
+ if (d.kind === "flood") {
87206
+ return {
87207
+ status: "deferred-flood",
87208
+ popped: 0,
87209
+ detail: `unpinAllForumTopicMessages pegged at retry_after=${d.seconds}s; not looping`
87210
+ };
87211
+ }
87212
+ return { status: "error", popped: 0, detail: d.detail };
87213
+ }
87214
+ cursor.popped++;
87215
+ cursor.updatedAt = deps.now();
87216
+ commit(cursor);
87217
+ const observed = await readTopVerified(target.chatId);
87218
+ if (observed != null && observed.top == null)
87219
+ return { status: "drained", popped: 1 };
87220
+ return { status: "drained", popped: 1, detail: "topic drained; chat-wide stack may hold other topics" };
87221
+ };
87222
+ const sweepOnce = async (target) => {
87223
+ if (!deps.eligible())
87224
+ return { status: "skipped-not-eligible", popped: 0 };
87225
+ if (circuitOpen) {
87226
+ return { status: "aborted-circuit-breaker", popped: 0, detail: "breaker already open" };
87227
+ }
87228
+ const kind = classifyChatForSweep(target);
87229
+ const cursor = loadCursor(target, kind);
87230
+ if (cursor.done)
87231
+ return { status: "already-drained", popped: 0 };
87232
+ if (cursor.attempts >= SWEEP_MAX_ATTEMPTS) {
87233
+ return { status: "forfeited", popped: 0, detail: `${cursor.attempts} attempts exhausted` };
87234
+ }
87235
+ if (kind !== "dm") {
87236
+ let allowed = false;
87237
+ try {
87238
+ allowed = await deps.canPinInChat(target.chatId);
87239
+ } catch {
87240
+ allowed = false;
87241
+ }
87242
+ if (!allowed) {
87243
+ cursor.attempts++;
87244
+ cursor.lastStatus = "skipped-no-rights";
87245
+ cursor.updatedAt = deps.now();
87246
+ commit(cursor);
87247
+ return { status: "skipped-no-rights", popped: 0 };
87248
+ }
87249
+ }
87250
+ cursor.attempts++;
87251
+ cursor.updatedAt = deps.now();
87252
+ commit(cursor);
87253
+ const protectedIds = [...new Set(deps.protectedMessageIds(target.chatId))];
87254
+ let result;
87255
+ try {
87256
+ if (kind === "dm") {
87257
+ result = await repinUnpinLoop(target, kind, cursor);
87258
+ } else if (mayUnpinAllForumTopic(kind, deps.allowUnpinAllForumTopic)) {
87259
+ result = await forumTopicDrain(target, cursor);
87260
+ } else {
87261
+ const keep = new Set(protectedIds);
87262
+ const ids = [
87263
+ ...new Set([
87264
+ ...target.messageIds ?? [],
87265
+ ...deps.recordedPinIds(target.chatId, target.threadId)
87266
+ ])
87267
+ ].filter((id) => !keep.has(id));
87268
+ result = ids.length === 0 ? {
87269
+ status: "skipped-nothing-recorded",
87270
+ popped: 0,
87271
+ issued: 0,
87272
+ detail: "no recorded pin ids for this target \u2014 nothing this gateway may remove"
87273
+ } : await targetedUnpinDrain(target, kind, cursor, ids);
87274
+ }
87275
+ } catch (err) {
87276
+ result = { status: "error", popped: 0, detail: err.message };
87277
+ }
87278
+ if (result.status === "drained" || result.status === "skipped-nothing-recorded") {
87279
+ cursor.done = true;
87280
+ }
87281
+ cursor.lastStatus = result.status;
87282
+ cursor.updatedAt = deps.now();
87283
+ commit(cursor);
87284
+ if (kind === "dm" && (result.status === "drained" || result.popped > 0)) {
87285
+ for (const messageId of protectedIds) {
87286
+ if (circuitOpen)
87287
+ break;
87288
+ if (!await awaitWriteSlot(kind))
87289
+ break;
86577
87290
  try {
86578
- await deps.pinSilent(chatId, messageId);
87291
+ await deps.pinSilent(target.chatId, messageId);
86579
87292
  } catch (err) {
86580
- const msg = err instanceof Error ? err.message : String(err);
86581
- deps.log?.(`telegram gateway: dm-pin-sweep: re-pin failed ` + `(chat=${chatId} msg=${messageId}): ${msg}
87293
+ log(`telegram gateway: stale-pin-sweep: re-pin of protected message failed ` + `(chat=${target.chatId} msg=${messageId}): ${err.message}
86582
87294
  `);
86583
87295
  }
86584
87296
  }
86585
- if (liveIds.length > 0) {
86586
- deps.log?.(`telegram gateway: dm-pin-sweep: cleared stacked pins in DM ${chatId}, ` + `re-pinned ${liveIds.length} live card(s)
86587
- `);
86588
- }
86589
87297
  }
87298
+ log(`telegram gateway: stale-pin-sweep: chat=${target.chatId} ` + `thread=${target.threadId ?? "-"} kind=${kind} status=${result.status} ` + `popped=${result.popped} issued=${result.issued ?? 0} total=${cursor.popped}` + (result.detail != null ? ` (${result.detail})` : "") + `
87299
+ `);
87300
+ return result;
86590
87301
  };
87302
+ const attempted = new Set;
87303
+ const inFlight = new Map;
87304
+ const sweepTarget = (target) => {
87305
+ const key = sweepTargetKey(target.chatId, target.threadId);
87306
+ const running = inFlight.get(key);
87307
+ if (running != null)
87308
+ return running;
87309
+ if (!deps.eligible())
87310
+ return Promise.resolve({ status: "skipped-not-eligible", popped: 0 });
87311
+ if (attempted.has(key))
87312
+ return Promise.resolve({ status: "already-attempted", popped: 0 });
87313
+ attempted.add(key);
87314
+ const p = sweepOnce(target).finally(() => inFlight.delete(key));
87315
+ inFlight.set(key, p);
87316
+ return p;
87317
+ };
87318
+ return { sweepTarget, isCircuitOpen: () => circuitOpen };
87319
+ }
87320
+
87321
+ // gateway/stale-pin-sweep-wiring.ts
87322
+ function protectedPinIds(args) {
87323
+ const ids = [];
87324
+ for (const claim of args.claims) {
87325
+ if (claim.chatId === args.chatId)
87326
+ ids.push(claim.messageId);
87327
+ }
87328
+ try {
87329
+ ids.push(...unexpiredStoreRepinIds(args.loadPinRows(), args.chatId, args.now));
87330
+ } catch (err) {
87331
+ args.log(`telegram gateway: stale-pin-sweep: store repin scan failed ` + `(chat=${args.chatId}): ${err.message}
87332
+ `);
87333
+ }
87334
+ return ids;
87335
+ }
87336
+ function recordedPinIdsFor(rows, chatId, threadId) {
87337
+ const out = [];
87338
+ for (const r of rows) {
87339
+ if (r.chatId !== chatId)
87340
+ continue;
87341
+ if ((r.threadId ?? undefined) !== threadId)
87342
+ continue;
87343
+ if (typeof r.messageId === "number" && !out.includes(r.messageId))
87344
+ out.push(r.messageId);
87345
+ }
87346
+ return out;
87347
+ }
87348
+ function createGatewayStalePinSweeper(w) {
87349
+ const log = w.log ?? ((line) => process.stderr.write(line));
87350
+ const now = w.now ?? Date.now;
87351
+ const { handle: bot, call } = w.telegram;
87352
+ return createStalePinSweeper({
87353
+ getTopPinnedMessageId: async (chatId) => {
87354
+ const chat = await call(() => bot().api.getChat(chatId), {
87355
+ chat_id: chatId,
87356
+ verb: "stale-pin-sweep.get-chat"
87357
+ });
87358
+ return chat?.pinned_message?.message_id ?? null;
87359
+ },
87360
+ pinSilent: (chatId, messageId) => call(() => bot().api.pinChatMessage(chatId, messageId, { disable_notification: true }), { chat_id: chatId, verb: "stale-pin-sweep.repin" }),
87361
+ unpin: (chatId, messageId) => call(() => bot().api.unpinChatMessage(chatId, messageId), { chat_id: chatId, verb: "stale-pin-sweep.unpin" }),
87362
+ unpinAllForumTopicMessages: (chatId, threadId) => call(() => bot().api.unpinAllForumTopicMessages(chatId, threadId), {
87363
+ chat_id: chatId,
87364
+ verb: "stale-pin-sweep.unpin-all-topic"
87365
+ }),
87366
+ canPinInChat: async (chatId) => {
87367
+ const self = bot().botInfo?.id;
87368
+ if (self == null)
87369
+ return false;
87370
+ const member = await call(() => bot().api.getChatMember(chatId, self), {
87371
+ chat_id: chatId,
87372
+ verb: "stale-pin-sweep.get-chat-member"
87373
+ });
87374
+ return member?.status === "administrator" && member.can_pin_messages === true;
87375
+ },
87376
+ recordedPinIds: (chatId, threadId) => {
87377
+ try {
87378
+ return recordedPinIdsFor(w.loadPinRows(), chatId, threadId);
87379
+ } catch (err) {
87380
+ log(`telegram gateway: stale-pin-sweep: recorded-pin scan failed ` + `(chat=${chatId}): ${err.message}
87381
+ `);
87382
+ return [];
87383
+ }
87384
+ },
87385
+ protectedMessageIds: (chatId) => protectedPinIds({
87386
+ chatId,
87387
+ claims: w.claims(),
87388
+ loadPinRows: w.loadPinRows,
87389
+ now: now(),
87390
+ log
87391
+ }),
87392
+ eligible: w.eligible,
87393
+ sleep: w.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),
87394
+ now,
87395
+ store: w.store,
87396
+ allowUnpinAllForumTopic: w.allowUnpinAllForumTopic,
87397
+ log
87398
+ });
86591
87399
  }
86592
87400
 
86593
87401
  // gateway/webhook-ingest-server.ts
@@ -87751,7 +88559,7 @@ function recordWebhookEvent(rec, deps = {}) {
87751
88559
 
87752
88560
  // gateway/ipc-server.ts
87753
88561
  init_format();
87754
- import { renameSync as renameSync16, unlinkSync as unlinkSync20, chmodSync as chmodSync11 } from "fs";
88562
+ import { renameSync as renameSync17, unlinkSync as unlinkSync20, chmodSync as chmodSync11 } from "fs";
87755
88563
  var MAX_BUFFER_SIZE = 1024 * 1024;
87756
88564
  var VALID_OPERATOR_KINDS = new Set([
87757
88565
  "credentials-expired",
@@ -87962,7 +88770,7 @@ function createIpcServer(options) {
87962
88770
  heartbeatTimeoutMs = 30000
87963
88771
  } = options;
87964
88772
  try {
87965
- renameSync16(socketPath, socketPath + ".bak");
88773
+ renameSync17(socketPath, socketPath + ".bak");
87966
88774
  } catch {}
87967
88775
  try {
87968
88776
  unlinkSync20(socketPath + ".bak");
@@ -88352,7 +89160,7 @@ function createIpcServer(options) {
88352
89160
  clientBySocketId.clear();
88353
89161
  server.stop(true);
88354
89162
  try {
88355
- renameSync16(socketPath, socketPath + ".bak");
89163
+ renameSync17(socketPath, socketPath + ".bak");
88356
89164
  } catch {}
88357
89165
  }
88358
89166
  };
@@ -89256,6 +90064,7 @@ function obligationEscalationText(o) {
89256
90064
  }
89257
90065
 
89258
90066
  // gateway/obligation-store.ts
90067
+ import { dirname as dirname20 } from "node:path";
89259
90068
  function isObligationRow(x) {
89260
90069
  if (x == null || typeof x !== "object")
89261
90070
  return false;
@@ -89311,9 +90120,17 @@ function persistObligations(path3, fs3, snapshot, log = (l) => process.stderr.wr
89311
90120
  const tmp = path3 + ".tmp";
89312
90121
  try {
89313
90122
  fs3.writeFileSync(tmp, JSON.stringify(env));
90123
+ fs3.fsyncFileSync(tmp);
89314
90124
  fs3.renameSync(tmp, path3);
89315
90125
  } catch (err) {
89316
90126
  log(`obligation-store: persist FAILED path=${path3}: ${err.message} \u2014 ` + `durability degraded to in-memory
90127
+ `);
90128
+ return;
90129
+ }
90130
+ try {
90131
+ fs3.fsyncDirSync(dirname20(path3));
90132
+ } catch (err) {
90133
+ log(`obligation-store: directory fsync FAILED path=${path3}: ${err.message} \u2014 ` + `snapshot written but the rename may not survive a power cut
89317
90134
  `);
89318
90135
  }
89319
90136
  }
@@ -89397,12 +90214,12 @@ function createCapturedResumeDispatcher(ports) {
89397
90214
 
89398
90215
  // gateway/status-pin-store.ts
89399
90216
  var BOOT_UNPIN_MAX_ATTEMPTS = 5;
89400
- var SNAPSHOT_VERSIONS2 = new Set([1, 2, 3]);
90217
+ var SNAPSHOT_VERSIONS2 = new Set([1, 2, 3, 4]);
89401
90218
  function isPinRow2(x) {
89402
90219
  if (x == null || typeof x !== "object")
89403
90220
  return false;
89404
90221
  const o = x;
89405
- return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number") && (o.pinnedAt === undefined || typeof o.pinnedAt === "number");
90222
+ return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && (o.threadId === undefined || typeof o.threadId === "number") && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number") && (o.pinnedAt === undefined || typeof o.pinnedAt === "number");
89406
90223
  }
89407
90224
  function loadStatusPins2(path3, fs3) {
89408
90225
  if (!fs3.existsSync(path3))
@@ -89427,7 +90244,7 @@ function loadStatusPins2(path3, fs3) {
89427
90244
  return env.pins.filter(isPinRow2);
89428
90245
  }
89429
90246
  function persistStatusPins2(path3, fs3, snapshot, log = (l) => process.stderr.write(l)) {
89430
- const env = { v: 3, pins: [...snapshot] };
90247
+ const env = { v: 4, pins: [...snapshot] };
89431
90248
  const tmp = path3 + ".tmp";
89432
90249
  try {
89433
90250
  fs3.writeFileSync(tmp, JSON.stringify(env));
@@ -89775,6 +90592,7 @@ function decideWorkerPinReaps(args) {
89775
90592
  }
89776
90593
 
89777
90594
  // gateway/inbound-spool.ts
90595
+ import { dirname as dirname21 } from "node:path";
89778
90596
  function spoolId(msg) {
89779
90597
  if (msg.meta?.source === "subagent_handback" && typeof msg.meta?.subagent_jsonl_id === "string" && msg.meta.subagent_jsonl_id.length > 0) {
89780
90598
  return `s:handback:${msg.meta.subagent_jsonl_id}`;
@@ -89878,6 +90696,7 @@ function createInboundSpool(opts) {
89878
90696
  try {
89879
90697
  fs3.appendFileSync(path3, JSON.stringify(rec) + `
89880
90698
  `);
90699
+ fs3.fsyncFileSync(path3);
89881
90700
  if (consecutiveAppendFailures > 0) {
89882
90701
  log(`inbound-spool: append recovered path=${path3} after ` + `${consecutiveAppendFailures} failure(s) \u2014 durability restored
89883
90702
  `);
@@ -89936,11 +90755,19 @@ function createInboundSpool(opts) {
89936
90755
  fs3.writeFileSync(tmp, lines.length ? lines.join(`
89937
90756
  `) + `
89938
90757
  ` : "");
90758
+ fs3.fsyncFileSync(tmp);
89939
90759
  fs3.renameSync(tmp, path3);
89940
90760
  log(`inbound-spool: compacted path=${path3} live=${live.size}
89941
90761
  `);
89942
90762
  } catch (err) {
89943
90763
  log(`inbound-spool: compact FAILED path=${path3}: ${err.message}
90764
+ `);
90765
+ return;
90766
+ }
90767
+ try {
90768
+ fs3.fsyncDirSync(dirname21(path3));
90769
+ } catch (err) {
90770
+ log(`inbound-spool: compact directory fsync FAILED path=${path3}: ` + `${err.message} \u2014 compacted log written but the rename ` + `may not survive a power cut
89944
90771
  `);
89945
90772
  }
89946
90773
  }
@@ -92163,9 +92990,9 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
92163
92990
  }
92164
92991
  case "Bash":
92165
92992
  case "BashOutput": {
92166
- const description = str("description");
92167
- if (description)
92168
- return truncate4(firstLine(description), MAX_DESCRIPTION_CHARS);
92993
+ const description2 = str("description");
92994
+ if (description2)
92995
+ return truncate4(firstLine(description2), MAX_DESCRIPTION_CHARS);
92169
92996
  const pre = preambleLabel();
92170
92997
  if (pre)
92171
92998
  return pre;
@@ -92227,9 +93054,9 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
92227
93054
  }
92228
93055
  default:
92229
93056
  if (tool.startsWith("mcp__")) {
92230
- const description = str("description");
92231
- if (description)
92232
- return truncate4(firstLine(stripHtml(description)), MAX_DESCRIPTION_CHARS);
93057
+ const description2 = str("description");
93058
+ if (description2)
93059
+ return truncate4(firstLine(stripHtml(description2)), MAX_DESCRIPTION_CHARS);
92233
93060
  const label = mcpBaseLabel(tool);
92234
93061
  const query3 = str("query") ?? str("text") ?? str("name");
92235
93062
  if (label && query3) {
@@ -92596,12 +93423,12 @@ function skillProposalKeyboard(id) {
92596
93423
 
92597
93424
  // ../src/self-improve/skill-proposals.ts
92598
93425
  import {
92599
- closeSync as closeSync8,
93426
+ closeSync as closeSync9,
92600
93427
  existsSync as existsSync41,
92601
93428
  mkdirSync as mkdirSync37,
92602
- openSync as openSync8,
93429
+ openSync as openSync9,
92603
93430
  readFileSync as readFileSync37,
92604
- writeSync as writeSync5
93431
+ writeSync as writeSync6
92605
93432
  } from "node:fs";
92606
93433
  import { join as join50 } from "node:path";
92607
93434
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -92621,12 +93448,12 @@ function ensureDir3(stateDir) {
92621
93448
  }
92622
93449
  }
92623
93450
  function appendLine2(path3, obj) {
92624
- const fd = openSync8(path3, "a");
93451
+ const fd = openSync9(path3, "a");
92625
93452
  try {
92626
- writeSync5(fd, JSON.stringify(obj) + `
93453
+ writeSync6(fd, JSON.stringify(obj) + `
92627
93454
  `);
92628
93455
  } finally {
92629
- closeSync8(fd);
93456
+ closeSync9(fd);
92630
93457
  }
92631
93458
  }
92632
93459
  function readLines2(path3, isValid2) {
@@ -93231,11 +94058,11 @@ function escapeBody2(s) {
93231
94058
  }
93232
94059
 
93233
94060
  // gateway/pid-file.ts
93234
- import { writeFileSync as writeFileSync33, readFileSync as readFileSync38, unlinkSync as unlinkSync21, renameSync as renameSync17 } from "node:fs";
94061
+ import { writeFileSync as writeFileSync33, readFileSync as readFileSync38, unlinkSync as unlinkSync21, renameSync as renameSync18 } from "node:fs";
93235
94062
  function writePidFile(path3, record) {
93236
94063
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
93237
94064
  writeFileSync33(tmp, JSON.stringify(record), "utf-8");
93238
- renameSync17(tmp, path3);
94065
+ renameSync18(tmp, path3);
93239
94066
  }
93240
94067
  function clearPidFile(path3) {
93241
94068
  try {
@@ -93456,11 +94283,11 @@ function safeCount(fn) {
93456
94283
  }
93457
94284
 
93458
94285
  // gateway/session-marker.ts
93459
- import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync18, unlinkSync as unlinkSync22 } from "node:fs";
94286
+ import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync19, unlinkSync as unlinkSync22 } from "node:fs";
93460
94287
  function writeSessionMarker(path3, marker) {
93461
94288
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
93462
94289
  writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
93463
- renameSync18(tmp, path3);
94290
+ renameSync19(tmp, path3);
93464
94291
  }
93465
94292
  function readSessionMarker(path3) {
93466
94293
  try {
@@ -93486,12 +94313,12 @@ function shouldFireRestartBanner(input) {
93486
94313
  }
93487
94314
 
93488
94315
  // gateway/clean-shutdown-marker.ts
93489
- import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync19, unlinkSync as unlinkSync23 } from "node:fs";
94316
+ import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync20, unlinkSync as unlinkSync23 } from "node:fs";
93490
94317
  var DEFAULT_MAX_AGE_MS = 60000;
93491
94318
  function writeCleanShutdownMarker(path3, marker) {
93492
94319
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
93493
94320
  writeFileSync35(tmp, JSON.stringify(marker), "utf-8");
93494
- renameSync19(tmp, path3);
94321
+ renameSync20(tmp, path3);
93495
94322
  }
93496
94323
  function readCleanShutdownMarker(path3) {
93497
94324
  try {
@@ -93868,10 +94695,10 @@ function classifyAdminGate(text4, myAgentName) {
93868
94695
  // subagent-watcher.ts
93869
94696
  import {
93870
94697
  existsSync as existsSync42,
93871
- openSync as openSync9,
94698
+ openSync as openSync10,
93872
94699
  readSync as readSync2,
93873
94700
  statSync as statSync15,
93874
- closeSync as closeSync9,
94701
+ closeSync as closeSync10,
93875
94702
  watch,
93876
94703
  readdirSync as readdirSync10,
93877
94704
  readFileSync as readFileSync42
@@ -94684,8 +95511,8 @@ function startSubagentWatcher(config) {
94684
95511
  existsSync: existsSync42,
94685
95512
  readdirSync: readdirSync10,
94686
95513
  statSync: statSync15,
94687
- openSync: openSync9,
94688
- closeSync: closeSync9,
95514
+ openSync: openSync10,
95515
+ closeSync: closeSync10,
94689
95516
  readSync: readSync2,
94690
95517
  watch
94691
95518
  };
@@ -95060,13 +95887,13 @@ function startSubagentWatcher(config) {
95060
95887
  if (size <= rec.sizeAtSynthesis)
95061
95888
  continue;
95062
95889
  const entry = registry.get(agentId);
95063
- const description = entry?.description ?? "sub-agent";
95890
+ const description2 = entry?.description ?? "sub-agent";
95064
95891
  if (rec.resurrectionCount >= MAX_RESURRECTIONS) {
95065
95892
  rec.lost = true;
95066
95893
  log?.(`subagent-watcher: worker ${agentId} FALSELY finalised again after a prior resurrection (resurrectionCount=${rec.resurrectionCount} >= ${MAX_RESURRECTIONS}) \u2014 NAMED AS LOST, not resurrecting again (bounded resurrection chain, issue #3023)`);
95067
95894
  if (config.onWorkerLost != null) {
95068
95895
  try {
95069
- config.onWorkerLost(agentId, description);
95896
+ config.onWorkerLost(agentId, description2);
95070
95897
  } catch (cbErr) {
95071
95898
  log?.(`subagent-watcher: onWorkerLost callback error ${agentId}: ${cbErr.message}`);
95072
95899
  }
@@ -95078,7 +95905,7 @@ function startSubagentWatcher(config) {
95078
95905
  log?.(`subagent-watcher: RESURRECTING worker ${agentId} \u2014 JSONL resumed growing (${rec.sizeAtSynthesis} \u2192 ${size} bytes) after a false terminal synthesis; the worker is still alive, reviving its card (resurrection #${rec.resurrectionCount}, issue #3023)`);
95079
95906
  if (config.onResurrect != null) {
95080
95907
  try {
95081
- config.onResurrect(agentId, description);
95908
+ config.onResurrect(agentId, description2);
95082
95909
  } catch (cbErr) {
95083
95910
  log?.(`subagent-watcher: onResurrect callback error ${agentId}: ${cbErr.message}`);
95084
95911
  }
@@ -95431,7 +96258,7 @@ import {
95431
96258
  readdirSync as readdirSync11,
95432
96259
  unlinkSync as unlinkSync24,
95433
96260
  existsSync as existsSync43,
95434
- renameSync as renameSync20
96261
+ renameSync as renameSync21
95435
96262
  } from "node:fs";
95436
96263
  import { join as join52, resolve as resolve10 } from "node:path";
95437
96264
  import { homedir as homedir15 } from "node:os";
@@ -95450,7 +96277,7 @@ function writeRecord(record) {
95450
96277
  const tmp = `${target}.tmp${process.pid}`;
95451
96278
  writeFileSync36(tmp, JSON.stringify(record, null, 2) + `
95452
96279
  `, { mode: 384 });
95453
- renameSync20(tmp, target);
96280
+ renameSync21(tmp, target);
95454
96281
  }
95455
96282
  function readRecord(id) {
95456
96283
  const path3 = recordPath(id);
@@ -95613,11 +96440,8 @@ function determineRestartReason(opts) {
95613
96440
  return "fresh";
95614
96441
  }
95615
96442
 
95616
- // gateway/gateway.ts
95617
- init_boot_card();
95618
-
95619
96443
  // gateway/update-announce.ts
95620
- import { existsSync as existsSync48, mkdirSync as mkdirSync42, openSync as openSync10, closeSync as closeSync10, readFileSync as readFileSync50 } from "node:fs";
96444
+ import { existsSync as existsSync48, mkdirSync as mkdirSync42, openSync as openSync11, closeSync as closeSync11, readFileSync as readFileSync50 } from "node:fs";
95621
96445
  import { join as join57 } from "node:path";
95622
96446
  import { homedir as homedir17 } from "node:os";
95623
96447
 
@@ -95710,6 +96534,9 @@ function parseAuditLine(line) {
95710
96534
  entry.failed_step = o.failed_step;
95711
96535
  if (typeof o.failed_agent === "string")
95712
96536
  entry.failed_agent = o.failed_agent;
96537
+ if (Array.isArray(o.drifted) && o.drifted.every((x) => typeof x === "string")) {
96538
+ entry.drifted = o.drifted;
96539
+ }
95713
96540
  if (typeof o.agent === "string")
95714
96541
  entry.agent = o.agent;
95715
96542
  if (typeof o.n === "number")
@@ -95826,8 +96653,8 @@ function claimUpdateAnnouncement(requestId, opts = {}) {
95826
96653
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
95827
96654
  const path3 = join57(dir, safeId);
95828
96655
  try {
95829
- const fd = openSync10(path3, "wx");
95830
- closeSync10(fd);
96656
+ const fd = openSync11(path3, "wx");
96657
+ closeSync11(fd);
95831
96658
  return true;
95832
96659
  } catch {
95833
96660
  return false;
@@ -96050,20 +96877,20 @@ import { join as join59 } from "node:path";
96050
96877
 
96051
96878
  // ../src/issues/store.ts
96052
96879
  import {
96053
- closeSync as closeSync11,
96880
+ closeSync as closeSync12,
96054
96881
  existsSync as existsSync49,
96055
96882
  mkdirSync as mkdirSync43,
96056
- openSync as openSync11,
96883
+ openSync as openSync12,
96057
96884
  readdirSync as readdirSync13,
96058
96885
  readFileSync as readFileSync52,
96059
- renameSync as renameSync23,
96886
+ renameSync as renameSync24,
96060
96887
  statSync as statSync17,
96061
96888
  unlinkSync as unlinkSync25,
96062
96889
  writeFileSync as writeFileSync42,
96063
- writeSync as writeSync6
96890
+ writeSync as writeSync7
96064
96891
  } from "node:fs";
96065
96892
  import { join as join58 } from "node:path";
96066
- import { randomBytes as randomBytes10 } from "node:crypto";
96893
+ import { randomBytes as randomBytes11 } from "node:crypto";
96067
96894
  import { execSync } from "node:child_process";
96068
96895
 
96069
96896
  // ../src/issues/types.ts
@@ -96138,12 +96965,12 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
96138
96965
  function writeAll(stateDir, events) {
96139
96966
  const path3 = join58(stateDir, ISSUES_FILE);
96140
96967
  sweepOrphanTmpFiles(stateDir);
96141
- const tmp = `${path3}.tmp-${process.pid}-${randomBytes10(4).toString("hex")}`;
96968
+ const tmp = `${path3}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
96142
96969
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
96143
96970
  `) + `
96144
96971
  `;
96145
96972
  writeFileSync42(tmp, body, "utf-8");
96146
- renameSync23(tmp, path3);
96973
+ renameSync24(tmp, path3);
96147
96974
  }
96148
96975
  var ORPHAN_TMP_TTL_MS = 60000;
96149
96976
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -96175,9 +97002,9 @@ function withLock(stateDir, fn) {
96175
97002
  let fd = null;
96176
97003
  while (fd === null) {
96177
97004
  try {
96178
- fd = openSync11(lockPath, "wx");
97005
+ fd = openSync12(lockPath, "wx");
96179
97006
  try {
96180
- writeSync6(fd, String(process.pid));
97007
+ writeSync7(fd, String(process.pid));
96181
97008
  } catch {}
96182
97009
  } catch (err) {
96183
97010
  const e = err;
@@ -96195,7 +97022,7 @@ function withLock(stateDir, fn) {
96195
97022
  return fn();
96196
97023
  } finally {
96197
97024
  try {
96198
- closeSync11(fd);
97025
+ closeSync12(fd);
96199
97026
  } catch {}
96200
97027
  try {
96201
97028
  unlinkSync25(lockPath);
@@ -97087,7 +97914,7 @@ function createScopedGrantStore(stateDir, env = process.env, log = (l) => proces
97087
97914
  preserveUnreadableStoreFile(filePath, "scoped-grant-store", log);
97088
97915
  unreadable = false;
97089
97916
  }
97090
- atomicWriteFileSync(filePath, JSON.stringify(serializeScopedGrants(store3)), 384);
97917
+ atomicWriteFileSync2(filePath, JSON.stringify(serializeScopedGrants(store3)), 384);
97091
97918
  } catch (err) {
97092
97919
  log(`telegram gateway: scoped-grant-store write failed: ${err.message}
97093
97920
  `);
@@ -97875,10 +98702,10 @@ init_auth_snapshot_format2();
97875
98702
 
97876
98703
  // gateway/turn-active-marker.ts
97877
98704
  import {
97878
- closeSync as closeSync12,
98705
+ closeSync as closeSync13,
97879
98706
  existsSync as existsSync53,
97880
98707
  mkdirSync as mkdirSync46,
97881
- openSync as openSync12,
98708
+ openSync as openSync13,
97882
98709
  readFileSync as readFileSync55,
97883
98710
  statSync as statSync19,
97884
98711
  unlinkSync as unlinkSync26,
@@ -97963,6 +98790,163 @@ function startGatewayHeartbeat(stateDir, intervalMs = GATEWAY_HEARTBEAT_INTERVAL
97963
98790
  return timer3;
97964
98791
  }
97965
98792
 
98793
+ // gateway/boot-beacon.ts
98794
+ import { randomUUID as randomUUID6 } from "node:crypto";
98795
+ import {
98796
+ closeSync as closeSync14,
98797
+ fsyncSync as fsyncSync5,
98798
+ mkdirSync as mkdirSync48,
98799
+ openSync as openSync14,
98800
+ readFileSync as readFileSync56,
98801
+ renameSync as renameSync25,
98802
+ unlinkSync as unlinkSync27,
98803
+ writeFileSync as writeFileSync47
98804
+ } from "node:fs";
98805
+ import { join as join65 } from "node:path";
98806
+ var BOOT_BEACON_FILE = "gateway-beacon.json";
98807
+ var HOST_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id";
98808
+ var CGROUP_MEMORY_EVENTS_PATH = "/sys/fs/cgroup/memory.events";
98809
+ var CGROUP_MEMORY_CURRENT_PATH = "/sys/fs/cgroup/memory.current";
98810
+ var CGROUP_MEMORY_MAX_PATH = "/sys/fs/cgroup/memory.max";
98811
+ var MAX_FIELD_CHARS = 256;
98812
+ function parseHostBootId(raw) {
98813
+ if (typeof raw !== "string")
98814
+ return;
98815
+ const trimmed = raw.trim();
98816
+ if (trimmed.length === 0 || trimmed.length > MAX_FIELD_CHARS)
98817
+ return;
98818
+ if (/\s/.test(trimmed))
98819
+ return;
98820
+ return trimmed;
98821
+ }
98822
+ function parseOomKillCount(raw) {
98823
+ if (typeof raw !== "string")
98824
+ return;
98825
+ for (const line of raw.split(`
98826
+ `)) {
98827
+ const parts = line.trim().split(/\s+/);
98828
+ if (parts.length !== 2 || parts[0] !== "oom_kill")
98829
+ continue;
98830
+ return parseNonNegativeInt2(parts[1]);
98831
+ }
98832
+ return;
98833
+ }
98834
+ function parseMemoryBytes(raw) {
98835
+ if (typeof raw !== "string")
98836
+ return;
98837
+ return parseNonNegativeInt2(raw.trim());
98838
+ }
98839
+ function parseMemoryMax(raw) {
98840
+ if (typeof raw !== "string")
98841
+ return;
98842
+ const trimmed = raw.trim();
98843
+ if (trimmed === "max")
98844
+ return "max";
98845
+ return parseNonNegativeInt2(trimmed);
98846
+ }
98847
+ function parseNonNegativeInt2(text4) {
98848
+ if (typeof text4 !== "string")
98849
+ return;
98850
+ const trimmed = text4.trim();
98851
+ if (trimmed.length === 0 || trimmed.length > MAX_FIELD_CHARS)
98852
+ return;
98853
+ if (!/^\d+$/.test(trimmed))
98854
+ return;
98855
+ const value = Number(trimmed);
98856
+ return Number.isSafeInteger(value) ? value : undefined;
98857
+ }
98858
+ function buildBootBeacon(input) {
98859
+ const beacon = {
98860
+ bootId: input.bootId,
98861
+ pid: input.pid,
98862
+ wallMs: input.wallMs,
98863
+ monotonicMs: input.monotonicMs
98864
+ };
98865
+ const sample3 = input.sample ?? {};
98866
+ if (sample3.hostBootId !== undefined)
98867
+ beacon.hostBootId = sample3.hostBootId;
98868
+ if (sample3.oomKillCount !== undefined)
98869
+ beacon.oomKillCount = sample3.oomKillCount;
98870
+ if (sample3.memCurrent !== undefined)
98871
+ beacon.memCurrent = sample3.memCurrent;
98872
+ if (sample3.memMax !== undefined)
98873
+ beacon.memMax = sample3.memMax;
98874
+ return beacon;
98875
+ }
98876
+ function serializeBootBeacon(beacon) {
98877
+ return `${JSON.stringify(beacon)}
98878
+ `;
98879
+ }
98880
+ function readTextOrNull(path3) {
98881
+ try {
98882
+ return readFileSync56(path3, "utf-8");
98883
+ } catch {
98884
+ return null;
98885
+ }
98886
+ }
98887
+ function sampleBeaconHost(paths = {}) {
98888
+ const sample3 = {};
98889
+ const hostBootId = parseHostBootId(readTextOrNull(paths.hostBootId ?? HOST_BOOT_ID_PATH));
98890
+ if (hostBootId !== undefined)
98891
+ sample3.hostBootId = hostBootId;
98892
+ const oomKillCount = parseOomKillCount(readTextOrNull(paths.memoryEvents ?? CGROUP_MEMORY_EVENTS_PATH));
98893
+ if (oomKillCount !== undefined)
98894
+ sample3.oomKillCount = oomKillCount;
98895
+ const memCurrent = parseMemoryBytes(readTextOrNull(paths.memoryCurrent ?? CGROUP_MEMORY_CURRENT_PATH));
98896
+ if (memCurrent !== undefined)
98897
+ sample3.memCurrent = memCurrent;
98898
+ const memMax = parseMemoryMax(readTextOrNull(paths.memoryMax ?? CGROUP_MEMORY_MAX_PATH));
98899
+ if (memMax !== undefined)
98900
+ sample3.memMax = memMax;
98901
+ return sample3;
98902
+ }
98903
+ function writeBootBeaconFile(stateDir, beacon) {
98904
+ const path3 = join65(stateDir, BOOT_BEACON_FILE);
98905
+ const tmp = `${path3}.tmp-${process.pid}`;
98906
+ let fd;
98907
+ try {
98908
+ mkdirSync48(stateDir, { recursive: true, mode: 448 });
98909
+ fd = openSync14(tmp, "w", 384);
98910
+ writeFileSync47(fd, serializeBootBeacon(beacon));
98911
+ fsyncSync5(fd);
98912
+ closeSync14(fd);
98913
+ fd = undefined;
98914
+ renameSync25(tmp, path3);
98915
+ return true;
98916
+ } catch {
98917
+ if (fd !== undefined) {
98918
+ try {
98919
+ closeSync14(fd);
98920
+ } catch {}
98921
+ }
98922
+ try {
98923
+ unlinkSync27(tmp);
98924
+ } catch {}
98925
+ return false;
98926
+ }
98927
+ }
98928
+ var GATEWAY_BOOT_ID = randomUUID6();
98929
+ function tickBootBeacon(stateDir, bootId = GATEWAY_BOOT_ID) {
98930
+ try {
98931
+ return writeBootBeaconFile(stateDir, buildBootBeacon({
98932
+ bootId,
98933
+ pid: process.pid,
98934
+ wallMs: Date.now(),
98935
+ monotonicMs: Math.round(performance.now()),
98936
+ sample: sampleBeaconHost()
98937
+ }));
98938
+ } catch {
98939
+ return false;
98940
+ }
98941
+ }
98942
+ function attachBootBeacon(stateDir, next) {
98943
+ tickBootBeacon(stateDir);
98944
+ return () => {
98945
+ tickBootBeacon(stateDir);
98946
+ next();
98947
+ };
98948
+ }
98949
+
97966
98950
  // gateway/outbox-sweep.ts
97967
98951
  init_flood_circuit_breaker();
97968
98952
  async function sweepOutbox(deps) {
@@ -98185,10 +99169,10 @@ function startOutboxSweep(deps) {
98185
99169
  }
98186
99170
 
98187
99171
  // ../src/build-info.ts
98188
- var VERSION2 = "0.19.30";
98189
- var COMMIT_SHA = "efe8e2a7";
98190
- var COMMIT_DATE = "2026-07-28T18:24:56Z";
98191
- var LATEST_PR = 3923;
99172
+ var VERSION2 = "0.19.32";
99173
+ var COMMIT_SHA = "5ccce0a7";
99174
+ var COMMIT_DATE = "2026-07-28T23:48:52Z";
99175
+ var LATEST_PR = 3962;
98192
99176
  var COMMITS_AHEAD_OF_TAG = 0;
98193
99177
 
98194
99178
  // gateway/boot-version.ts
@@ -98266,11 +99250,11 @@ init_peercred();
98266
99250
  import * as net5 from "node:net";
98267
99251
  import * as fs3 from "node:fs";
98268
99252
  import { homedir as homedir18 } from "node:os";
98269
- import { join as join65 } from "node:path";
99253
+ import { join as join66 } from "node:path";
98270
99254
  var DEFAULT_TIMEOUT_MS4 = 2000;
98271
99255
  var UNLOCK_TIMEOUT_MS = 30000;
98272
- var LEGACY_SOCKET_PATH2 = join65(homedir18(), ".switchroom", "vault-broker.sock");
98273
- var OPERATOR_SOCKET_PATH2 = join65(homedir18(), ".switchroom", "broker-operator", "sock");
99256
+ var LEGACY_SOCKET_PATH2 = join66(homedir18(), ".switchroom", "vault-broker.sock");
99257
+ var OPERATOR_SOCKET_PATH2 = join66(homedir18(), ".switchroom", "broker-operator", "sock");
98274
99258
  function defaultBrokerSocketPath2() {
98275
99259
  if (fs3.existsSync(OPERATOR_SOCKET_PATH2))
98276
99260
  return OPERATOR_SOCKET_PATH2;
@@ -98871,8 +99855,8 @@ async function createLinearIssue(args, deps = {}) {
98871
99855
  }
98872
99856
  teamId = nodes[0].id;
98873
99857
  }
98874
- const description = dedupKey ? `${body}${captureDedupMarker(dedupKey)}` : body;
98875
- const input = { teamId, title, description };
99858
+ const description2 = dedupKey ? `${body}${captureDedupMarker(dedupKey)}` : body;
99859
+ const input = { teamId, title, description: description2 };
98876
99860
  if (priority !== undefined)
98877
99861
  input.priority = priority;
98878
99862
  const create = await gql("mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url } } }", { input });
@@ -99152,8 +100136,8 @@ function resolveVaultApprovalPosture(broker) {
99152
100136
  }
99153
100137
 
99154
100138
  // registry/turns-schema.ts
99155
- import { chmodSync as chmodSync12, mkdirSync as mkdirSync48 } from "fs";
99156
- import { join as join66 } from "path";
100139
+ import { chmodSync as chmodSync12, mkdirSync as mkdirSync49 } from "fs";
100140
+ import { join as join67 } from "path";
99157
100141
  var DatabaseClass3 = null;
99158
100142
  function loadDatabaseClass3() {
99159
100143
  if (DatabaseClass3 != null)
@@ -99210,7 +100194,7 @@ var PHASE4_MIGRATIONS = [
99210
100194
  ];
99211
100195
  function applySchema(db3) {
99212
100196
  db3.exec("PRAGMA journal_mode = WAL");
99213
- db3.exec("PRAGMA synchronous = NORMAL");
100197
+ db3.exec("PRAGMA synchronous = FULL");
99214
100198
  db3.exec("PRAGMA busy_timeout = 5000");
99215
100199
  db3.exec(SCHEMA_SQL);
99216
100200
  for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
@@ -99225,9 +100209,9 @@ function applySchema(db3) {
99225
100209
  }
99226
100210
  function openTurnsDb(agentDir) {
99227
100211
  const Database = loadDatabaseClass3();
99228
- const dir = join66(agentDir, "telegram");
99229
- mkdirSync48(dir, { recursive: true, mode: 448 });
99230
- const path3 = join66(dir, "registry.db");
100212
+ const dir = join67(agentDir, "telegram");
100213
+ mkdirSync49(dir, { recursive: true, mode: 448 });
100214
+ const path3 = join67(dir, "registry.db");
99231
100215
  const db3 = new Database(path3, { create: true });
99232
100216
  applySchema(db3);
99233
100217
  try {
@@ -99572,7 +100556,7 @@ function selectResumeBuilder(endedVia, opts) {
99572
100556
  }
99573
100557
 
99574
100558
  // gateway/bridge-dead-watchdog.ts
99575
- import { readFileSync as readFileSync57, writeFileSync as writeFileSync47, renameSync as renameSync24, unlinkSync as unlinkSync27 } from "node:fs";
100559
+ import { readFileSync as readFileSync58, writeFileSync as writeFileSync48, renameSync as renameSync26, unlinkSync as unlinkSync28 } from "node:fs";
99576
100560
 
99577
100561
  // gateway/cron-session.ts
99578
100562
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -99605,7 +100589,7 @@ function readFreshCrashLogTail(path3, opts = {}) {
99605
100589
  const nowMs3 = opts.nowMs ?? Date.now();
99606
100590
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
99607
100591
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
99608
- const readFile = opts.readFile ?? ((p) => readFileSync57(p, "utf8"));
100592
+ const readFile = opts.readFile ?? ((p) => readFileSync58(p, "utf8"));
99609
100593
  let raw;
99610
100594
  try {
99611
100595
  raw = readFile(path3);
@@ -99626,13 +100610,13 @@ function readFreshCrashLogTail(path3, opts = {}) {
99626
100610
  }
99627
100611
  function writeBridgeDeadEscalationMarker(path3, marker) {
99628
100612
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
99629
- writeFileSync47(tmp, JSON.stringify(marker), "utf8");
99630
- renameSync24(tmp, path3);
100613
+ writeFileSync48(tmp, JSON.stringify(marker), "utf8");
100614
+ renameSync26(tmp, path3);
99631
100615
  }
99632
100616
  function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
99633
100617
  let marker = null;
99634
100618
  try {
99635
- const parsed = JSON.parse(readFileSync57(path3, "utf8"));
100619
+ const parsed = JSON.parse(readFileSync58(path3, "utf8"));
99636
100620
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
99637
100621
  const age = nowMs3 - parsed.ts;
99638
100622
  if (age >= 0 && age < maxAgeMs) {
@@ -99644,7 +100628,7 @@ function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs
99644
100628
  }
99645
100629
  } catch {}
99646
100630
  try {
99647
- unlinkSync27(path3);
100631
+ unlinkSync28(path3);
99648
100632
  } catch {}
99649
100633
  return marker;
99650
100634
  }
@@ -99785,7 +100769,7 @@ function createBridgeDeadWatchdog(opts) {
99785
100769
  }
99786
100770
 
99787
100771
  // gateway/boot-probes.ts
99788
- import { readFileSync as readFileSync58, readdirSync as readdirSync14, existsSync as existsSync55 } from "fs";
100772
+ import { readFileSync as readFileSync59, readdirSync as readdirSync14, existsSync as existsSync55 } from "fs";
99789
100773
  init_quota_cache();
99790
100774
  init_generation_stamp();
99791
100775
  init_quota_check();
@@ -99794,7 +100778,7 @@ import { promisify as promisify2 } from "util";
99794
100778
  var execFile2 = promisify2(execFileCb2);
99795
100779
  var realProcFs2 = {
99796
100780
  readdir: (p) => readdirSync14(p),
99797
- readFile: (p) => readFileSync58(p, "utf-8")
100781
+ readFile: (p) => readFileSync59(p, "utf-8")
99798
100782
  };
99799
100783
  function findAgentProcessInContainer2(fs4 = realProcFs2) {
99800
100784
  let entries;
@@ -100044,7 +101028,7 @@ if (isGatewayMain) {
100044
101028
  shutdownAnalytics();
100045
101029
  });
100046
101030
  }
100047
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join67(homedir19(), ".claude", "channels", "telegram");
101031
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join68(homedir19(), ".claude", "channels", "telegram");
100048
101032
  var permCardStore = createPermissionCardStore(STATE_DIR);
100049
101033
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
100050
101034
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -100083,7 +101067,7 @@ function alwaysAllowDrainDeps() {
100083
101067
  return {
100084
101068
  readConfigText: () => {
100085
101069
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
100086
- return readFileSync59(cfgPath, "utf8");
101070
+ return readFileSync60(cfgPath, "utf8");
100087
101071
  },
100088
101072
  resolveAllowList: (_configText, agentName3) => {
100089
101073
  const cfg = loadConfig2();
@@ -100144,11 +101128,11 @@ function scheduleAlwaysAllowPersistDrain() {
100144
101128
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
100145
101129
  timer3.unref?.();
100146
101130
  }
100147
- var ACCESS_FILE = join67(STATE_DIR, "access.json");
100148
- var APPROVED_DIR = join67(STATE_DIR, "approved");
100149
- var ENV_FILE = join67(STATE_DIR, ".env");
100150
- var INBOX_DIR = join67(STATE_DIR, "inbox");
100151
- var PEOPLE_FILE = join67(STATE_DIR, "people.json");
101131
+ var ACCESS_FILE = join68(STATE_DIR, "access.json");
101132
+ var APPROVED_DIR = join68(STATE_DIR, "approved");
101133
+ var ENV_FILE = join68(STATE_DIR, ".env");
101134
+ var INBOX_DIR = join68(STATE_DIR, "inbox");
101135
+ var PEOPLE_FILE = join68(STATE_DIR, "people.json");
100152
101136
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
100153
101137
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
100154
101138
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -100213,7 +101197,7 @@ function formatBootVersion() {
100213
101197
  }
100214
101198
  try {
100215
101199
  chmodSync14(ENV_FILE, 384);
100216
- for (const line of readFileSync59(ENV_FILE, "utf8").split(`
101200
+ for (const line of readFileSync60(ENV_FILE, "utf8").split(`
100217
101201
  `)) {
100218
101202
  const m = line.match(/^(\w+)=(.*)$/);
100219
101203
  if (m && process.env[m[1]] === undefined)
@@ -100234,7 +101218,7 @@ var bot;
100234
101218
  var lastGetUpdatesHeartbeatMs = Date.now();
100235
101219
  var GRAMMY_VERSION = (() => {
100236
101220
  try {
100237
- const raw = readFileSync59(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
101221
+ const raw = readFileSync60(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
100238
101222
  return JSON.parse(raw).version ?? "unknown";
100239
101223
  } catch {
100240
101224
  return "unknown";
@@ -100297,7 +101281,7 @@ function assertSendable(f) {
100297
101281
  } catch {
100298
101282
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
100299
101283
  }
100300
- const inbox = join67(stateReal, "inbox");
101284
+ const inbox = join68(stateReal, "inbox");
100301
101285
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
100302
101286
  throw new Error(`refusing to send channel state: ${f}`);
100303
101287
  }
@@ -100316,7 +101300,7 @@ function assertSendable(f) {
100316
101300
  }
100317
101301
  function readAccessFile() {
100318
101302
  try {
100319
- const raw = readFileSync59(ACCESS_FILE, "utf8");
101303
+ const raw = readFileSync60(ACCESS_FILE, "utf8");
100320
101304
  const parsed = JSON.parse(raw);
100321
101305
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
100322
101306
  const groups = {};
@@ -100356,7 +101340,7 @@ function readAccessFile() {
100356
101340
  if (err.code === "ENOENT")
100357
101341
  return defaultAccess();
100358
101342
  try {
100359
- renameSync25(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
101343
+ renameSync27(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
100360
101344
  } catch {}
100361
101345
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
100362
101346
  `);
@@ -100378,7 +101362,7 @@ function loadAccess() {
100378
101362
  }
100379
101363
  function readPeopleFile() {
100380
101364
  try {
100381
- const raw = readFileSync59(PEOPLE_FILE, "utf8");
101365
+ const raw = readFileSync60(PEOPLE_FILE, "utf8");
100382
101366
  const parsed = JSON.parse(raw);
100383
101367
  if (!Array.isArray(parsed.entries))
100384
101368
  return [];
@@ -100400,11 +101384,11 @@ function assertAllowedChat(chat_id) {
100400
101384
  function saveAccess(a) {
100401
101385
  if (STATIC)
100402
101386
  return;
100403
- mkdirSync50(STATE_DIR, { recursive: true, mode: 448 });
101387
+ mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
100404
101388
  const tmp = ACCESS_FILE + ".tmp";
100405
- writeFileSync49(tmp, JSON.stringify(a, null, 2) + `
101389
+ writeFileSync50(tmp, JSON.stringify(a, null, 2) + `
100406
101390
  `, { mode: 384 });
100407
- renameSync25(tmp, ACCESS_FILE);
101391
+ renameSync27(tmp, ACCESS_FILE);
100408
101392
  }
100409
101393
  function pruneExpired(a) {
100410
101394
  const now = Date.now();
@@ -100422,7 +101406,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
100422
101406
  if (isGatewayMain && HISTORY_ENABLED) {
100423
101407
  try {
100424
101408
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
100425
- process.stderr.write(`telegram gateway: history capture enabled at ${join67(STATE_DIR, "history.db")}
101409
+ process.stderr.write(`telegram gateway: history capture enabled at ${join68(STATE_DIR, "history.db")}
100426
101410
  `);
100427
101411
  } catch (err) {
100428
101412
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -100441,12 +101425,12 @@ if (isGatewayMain)
100441
101425
  let markerTurnKey = null;
100442
101426
  let markerAgeMs = null;
100443
101427
  try {
100444
- const markerPath = join67(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
101428
+ const markerPath = join68(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
100445
101429
  if (existsSync57(markerPath)) {
100446
101430
  const st = statSync22(markerPath);
100447
101431
  markerAgeMs = Date.now() - st.mtimeMs;
100448
101432
  try {
100449
- const payload = JSON.parse(readFileSync59(markerPath, "utf8"));
101433
+ const payload = JSON.parse(readFileSync60(markerPath, "utf8"));
100450
101434
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
100451
101435
  markerTurnKey = payload.turnKey;
100452
101436
  }
@@ -100466,10 +101450,10 @@ if (isGatewayMain)
100466
101450
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
100467
101451
  `);
100468
101452
  } else {
100469
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join67(agentDir, "telegram", "registry.db")}
101453
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join68(agentDir, "telegram", "registry.db")}
100470
101454
  `);
100471
101455
  }
100472
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join67(STATE_DIR, "bridge-dead-escalation.json"));
101456
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join68(STATE_DIR, "bridge-dead-escalation.json"));
100473
101457
  if (bridgeDeadMarker != null) {
100474
101458
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
100475
101459
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -100482,7 +101466,7 @@ if (isGatewayMain)
100482
101466
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
100483
101467
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
100484
101468
  if (pending2 != null && selfAgent) {
100485
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join67(STATE_DIR, "clean-shutdown.json");
101469
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
100486
101470
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
100487
101471
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
100488
101472
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -100595,7 +101579,7 @@ if (isGatewayMain)
100595
101579
  `);
100596
101580
  }
100597
101581
  }
100598
- const pendingEnvPath = join67(agentDir, ".pending-turn.env");
101582
+ const pendingEnvPath = join68(agentDir, ".pending-turn.env");
100599
101583
  try {
100600
101584
  if (pending2 != null) {
100601
101585
  const lines = [
@@ -100609,14 +101593,14 @@ if (isGatewayMain)
100609
101593
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
100610
101594
  ];
100611
101595
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
100612
- writeFileSync49(pendingEnvTmp, lines.join(`
101596
+ writeFileSync50(pendingEnvTmp, lines.join(`
100613
101597
  `) + `
100614
101598
  `, { mode: 384 });
100615
- renameSync25(pendingEnvTmp, pendingEnvPath);
101599
+ renameSync27(pendingEnvTmp, pendingEnvPath);
100616
101600
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
100617
101601
  `);
100618
101602
  } else if (existsSync57(pendingEnvPath)) {
100619
- rmSync7(pendingEnvPath, { force: true });
101603
+ rmSync8(pendingEnvPath, { force: true });
100620
101604
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
100621
101605
  `);
100622
101606
  }
@@ -100723,16 +101707,16 @@ function checkApprovals() {
100723
101707
  return;
100724
101708
  }
100725
101709
  for (const senderId of files) {
100726
- const file = join67(APPROVED_DIR, senderId);
100727
- bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync7(file, { force: true }), (err) => {
101710
+ const file = join68(APPROVED_DIR, senderId);
101711
+ bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync8(file, { force: true }), (err) => {
100728
101712
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
100729
101713
  `);
100730
- rmSync7(file, { force: true });
101714
+ rmSync8(file, { force: true });
100731
101715
  });
100732
101716
  }
100733
101717
  }
100734
101718
  if (isGatewayMain && !STATIC)
100735
- setInterval(checkApprovals, 5000).unref();
101719
+ setInterval(attachBootBeacon(STATE_DIR, checkApprovals), 5000).unref();
100736
101720
  if (isGatewayMain && !STATIC)
100737
101721
  startGatewayHeartbeat(STATE_DIR);
100738
101722
  var IDLE_CLEAR_CHECK_MS = Number(process.env.SWITCHROOM_IDLE_CLEAR_CHECK_MS ?? 60000);
@@ -100897,12 +101881,14 @@ function noteAgentOutputAt(key, ts) {
100897
101881
  lastAgentOutputAt.delete(oldest);
100898
101882
  }
100899
101883
  }
100900
- var OBLIGATION_STORE_PATH = join67(STATE_DIR, "obligations.json");
101884
+ var OBLIGATION_STORE_PATH = join68(STATE_DIR, "obligations.json");
100901
101885
  var obligationStoreFs = {
100902
- readFileSync: (p) => readFileSync59(p, "utf8"),
100903
- writeFileSync: (p, d) => writeFileSync49(p, d),
100904
- renameSync: (a, b) => renameSync25(a, b),
100905
- existsSync: (p) => existsSync57(p)
101886
+ readFileSync: (p) => readFileSync60(p, "utf8"),
101887
+ writeFileSync: (p, d) => writeFileSync50(p, d),
101888
+ renameSync: (a, b) => renameSync27(a, b),
101889
+ existsSync: (p) => existsSync57(p),
101890
+ fsyncFileSync: fsyncPathSync,
101891
+ fsyncDirSync: fsyncPathSync
100906
101892
  };
100907
101893
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
100908
101894
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -101677,7 +102663,7 @@ function emitTurnRecord(turn, endedAt) {
101677
102663
  return;
101678
102664
  }
101679
102665
  },
101680
- rename: (from, to) => renameSync25(from, to)
102666
+ rename: (from, to) => renameSync27(from, to)
101681
102667
  });
101682
102668
  appendFileSync9(turnsPath, rec);
101683
102669
  } catch {}
@@ -102225,7 +103211,7 @@ function maybePostMissedApprovalDigest(reason) {
102225
103211
  const first = entries[0];
102226
103212
  const chatId = first.chatId;
102227
103213
  const threadId = first.threadId ?? undefined;
102228
- const digestId = randomBytes12(4).toString("hex");
103214
+ const digestId = randomBytes13(4).toString("hex");
102229
103215
  const text5 = renderMissedApprovalsDigest(entries, {
102230
103216
  agentName: process.env.SWITCHROOM_AGENT_NAME ?? null
102231
103217
  });
@@ -103240,27 +104226,27 @@ var PIN_STATUS_WHILE_WORKING = (() => {
103240
104226
  })();
103241
104227
  var statusPinClaims = new Map;
103242
104228
  var statusPinRightsCache = new PinRightsCache2;
103243
- var STATUS_PIN_STORE_PATH = join67(STATE_DIR, "status-pins.json");
104229
+ var STATUS_PIN_STORE_PATH = join68(STATE_DIR, "status-pins.json");
103244
104230
  var statusPinStoreFs = {
103245
- readFileSync: (p) => readFileSync59(p, "utf8"),
103246
- writeFileSync: (p, d) => writeFileSync49(p, d),
103247
- renameSync: (a, b) => renameSync25(a, b),
104231
+ readFileSync: (p) => readFileSync60(p, "utf8"),
104232
+ writeFileSync: (p, d) => writeFileSync50(p, d),
104233
+ renameSync: (a, b) => renameSync27(a, b),
103248
104234
  existsSync: (p) => existsSync57(p)
103249
104235
  };
103250
104236
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
103251
- var ACTIVITY_CARD_STORE_PATH = join67(STATE_DIR, "activity-cards-pending.json");
104237
+ var ACTIVITY_CARD_STORE_PATH = join68(STATE_DIR, "activity-cards-pending.json");
103252
104238
  var activityCardStoreFs = {
103253
- readFileSync: (p) => readFileSync59(p, "utf8"),
103254
- writeFileSync: (p, d) => writeFileSync49(p, d),
103255
- renameSync: (a, b) => renameSync25(a, b),
104239
+ readFileSync: (p) => readFileSync60(p, "utf8"),
104240
+ writeFileSync: (p, d) => writeFileSync50(p, d),
104241
+ renameSync: (a, b) => renameSync27(a, b),
103256
104242
  existsSync: (p) => existsSync57(p)
103257
104243
  };
103258
104244
  var activityCardPersistEnabled = !STATIC;
103259
- var QUEUED_CARD_STORE_PATH = join67(STATE_DIR, "queued-cards-pending.json");
104245
+ var QUEUED_CARD_STORE_PATH = join68(STATE_DIR, "queued-cards-pending.json");
103260
104246
  var queuedCardStoreFs = {
103261
- readFileSync: (p) => readFileSync59(p, "utf8"),
103262
- writeFileSync: (p, d) => writeFileSync49(p, d),
103263
- renameSync: (a, b) => renameSync25(a, b),
104247
+ readFileSync: (p) => readFileSync60(p, "utf8"),
104248
+ writeFileSync: (p, d) => writeFileSync50(p, d),
104249
+ renameSync: (a, b) => renameSync27(a, b),
103264
104250
  existsSync: (p) => existsSync57(p)
103265
104251
  };
103266
104252
  var queuedCardPersistEnabled = !STATIC;
@@ -103499,16 +104485,16 @@ var midSessionCardReaper = isGatewayMain ? setInterval(() => {
103499
104485
  midSessionReaperGuard.tick();
103500
104486
  }, MID_SESSION_CARD_REAPER_INTERVAL_MS) : undefined;
103501
104487
  midSessionCardReaper?.unref();
103502
- async function reconcileStatusPin(pinKey, chatId, desired) {
104488
+ async function reconcileStatusPin(pinKey, chatId, desired, threadId) {
103503
104489
  try {
103504
- await withPinReconcileLock(pinKey, () => reconcileStatusPinInner(pinKey, chatId, desired));
104490
+ await withPinReconcileLock(pinKey, () => reconcileStatusPinInner(pinKey, chatId, desired, threadId));
103505
104491
  } catch (err) {
103506
104492
  const msg = err instanceof Error ? err.message : String(err);
103507
104493
  process.stderr.write(`telegram gateway: status-pin reconcile absorbed error (key=${pinKey} chat=${chatId}): ${msg}
103508
104494
  `);
103509
104495
  }
103510
104496
  }
103511
- async function reconcileStatusPinInner(pinKey, chatId, desired) {
104497
+ async function reconcileStatusPinInner(pinKey, chatId, desired, threadId) {
103512
104498
  if (!PIN_STATUS_WHILE_WORKING)
103513
104499
  return;
103514
104500
  if (chatId.length === 0)
@@ -103534,6 +104520,7 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
103534
104520
  await runStatusPinReconcile({
103535
104521
  pinKey,
103536
104522
  chatId,
104523
+ threadId,
103537
104524
  prev,
103538
104525
  desired,
103539
104526
  persist: statusPinPersistEnabled ? { path: STATUS_PIN_STORE_PATH, fs: statusPinStoreFs } : null,
@@ -103546,37 +104533,26 @@ async function unpinAllStatusPins() {
103546
104533
  await reconcileStatusPin(key, claim.chatId, { pinned: false });
103547
104534
  }
103548
104535
  }
103549
- var dmPinSweepEligible = false;
103550
- var dmPinSweeper = createDmPinSweeper({
103551
- unpinAll: (chatId) => robustApiCall(() => lockedBot.api.unpinAllChatMessages(chatId), {
103552
- chat_id: chatId,
103553
- verb: "dm-pin-sweep.unpin-all"
103554
- }),
103555
- pinSilent: (chatId, messageId) => robustApiCall(() => lockedBot.api.pinChatMessage(chatId, messageId, {
103556
- disable_notification: true
103557
- }), { chat_id: chatId, verb: "dm-pin-sweep.repin" }),
103558
- liveTrackedMessageIds: (chatId) => {
103559
- const ids = [];
103560
- for (const claim of statusPinClaims.values()) {
103561
- if (claim.chatId === chatId)
103562
- ids.push(claim.messageId);
103563
- }
103564
- if (statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled) {
103565
- try {
103566
- ids.push(...unexpiredStoreRepinIds(loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs), chatId, Date.now()));
103567
- } catch (err) {
103568
- process.stderr.write(`telegram gateway: dm-pin-sweep: store repin scan failed (chat=${chatId}): ${err.message}
103569
- `);
103570
- }
104536
+ var stalePinSweepEligible = false;
104537
+ var STALE_PIN_SWEEP_STORE_PATH = join68(STATE_DIR, "stale-pin-sweep.json");
104538
+ var stalePinSweeper = createGatewayStalePinSweeper({
104539
+ telegram: { handle: () => lockedBot, call: robustApiCall },
104540
+ claims: () => statusPinClaims.values(),
104541
+ loadPinRows: () => statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled ? loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs) : [],
104542
+ eligible: () => stalePinSweepEligible,
104543
+ store: {
104544
+ path: STALE_PIN_SWEEP_STORE_PATH,
104545
+ fs: {
104546
+ readFileSync: (p) => readFileSync60(p, "utf-8"),
104547
+ writeFileSync: (p, data) => atomicWriteFileSync(p, data, 384),
104548
+ existsSync: (p) => existsSync57(p)
103571
104549
  }
103572
- return ids;
103573
104550
  },
103574
- eligible: () => dmPinSweepEligible,
103575
- log: (line) => process.stderr.write(line)
104551
+ allowUnpinAllForumTopic: process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC == null ? undefined : process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC === "1"
103576
104552
  });
103577
- function runBootPinCleanupAndDmSweep() {
104553
+ function runBootPinCleanupAndStalePinSweep() {
103578
104554
  return runBootPinSweepSteps({
103579
- scanDmChatIds: () => collectDmChatIdsFromStores({
104555
+ scanSweepTargets: () => collectSweepTargets({
103580
104556
  statusPins: statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled ? loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs) : [],
103581
104557
  activityCards: activityCardPersistEnabled ? loadActivityCards2(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs) : [],
103582
104558
  queuedCards: queuedCardPersistEnabled ? loadQueuedCards(QUEUED_CARD_STORE_PATH, queuedCardStoreFs) : []
@@ -103584,14 +104560,14 @@ function runBootPinCleanupAndDmSweep() {
103584
104560
  statusPinCleanup: statusPinBootCleanup,
103585
104561
  activityCardReaper: activityCardBootReaper,
103586
104562
  queuedCardReaper: queuedCardBootReaper,
103587
- enableDmSweep: () => {
103588
- dmPinSweepEligible = true;
104563
+ enableSweep: () => {
104564
+ stalePinSweepEligible = true;
103589
104565
  },
103590
- sweepDm: (id) => dmPinSweeper.sweep(id),
104566
+ sweepTarget: (t) => stalePinSweeper.sweepTarget(t),
103591
104567
  log: (line) => process.stderr.write(line)
103592
104568
  });
103593
104569
  }
103594
- var bootPinSweepGate = createBootSweepGate({ run: runBootPinCleanupAndDmSweep, onError: (err) => process.stderr.write(`telegram gateway: boot pin cleanup / DM sweep failed: ${err.message}
104570
+ var bootPinSweepGate = createBootSweepGate({ run: runBootPinCleanupAndStalePinSweep, onError: (err) => process.stderr.write(`telegram gateway: boot pin cleanup / stale-pin sweep failed: ${err.message}
103595
104571
  `) });
103596
104572
  var progressDriver = null;
103597
104573
  var unpinProgressCardForChat = null;
@@ -103599,12 +104575,12 @@ var getPinnedProgressCardMessageId = null;
103599
104575
  var completeProgressCardTurn = null;
103600
104576
  var subagentWatcher = null;
103601
104577
  var workerActivityFeed = null;
103602
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join67(STATE_DIR, "gateway.sock");
104578
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join68(STATE_DIR, "gateway.sock");
103603
104579
  if (isGatewayMain)
103604
- mkdirSync50(STATE_DIR, { recursive: true, mode: 448 });
103605
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join67(STATE_DIR, "gateway.pid.json");
103606
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join67(STATE_DIR, "gateway-session.json");
103607
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join67(STATE_DIR, "clean-shutdown.json");
104580
+ mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
104581
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join68(STATE_DIR, "gateway.pid.json");
104582
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join68(STATE_DIR, "gateway-session.json");
104583
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
103608
104584
  var GATEWAY_STARTED_AT_MS = Date.now();
103609
104585
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
103610
104586
  var activeBootCard = null;
@@ -103633,7 +104609,7 @@ function ensureIssuesCard(chatId, threadId) {
103633
104609
  bot: botApi,
103634
104610
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
103635
104611
  `),
103636
- persistPath: join67(stateDir, "issues-card.json")
104612
+ persistPath: join68(stateDir, "issues-card.json")
103637
104613
  });
103638
104614
  activeIssuesWatcher = startIssuesWatcher({
103639
104615
  stateDir,
@@ -103833,14 +104809,16 @@ if (isGatewayMain)
103833
104809
  var inboundSpool;
103834
104810
  if (isGatewayMain)
103835
104811
  inboundSpool = STATIC ? undefined : createInboundSpool({
103836
- path: join67(STATE_DIR, "inbound-spool.jsonl"),
104812
+ path: join68(STATE_DIR, "inbound-spool.jsonl"),
103837
104813
  fs: {
103838
104814
  appendFileSync: (p, d) => appendFileSync9(p, d),
103839
- readFileSync: (p) => readFileSync59(p, "utf8"),
103840
- writeFileSync: (p, d) => writeFileSync49(p, d),
103841
- renameSync: (a, b) => renameSync25(a, b),
104815
+ readFileSync: (p) => readFileSync60(p, "utf8"),
104816
+ writeFileSync: (p, d) => writeFileSync50(p, d),
104817
+ renameSync: (a, b) => renameSync27(a, b),
103842
104818
  existsSync: (p) => existsSync57(p),
103843
- statSizeSync: (p) => statSync22(p).size
104819
+ statSizeSync: (p) => statSync22(p).size,
104820
+ fsyncFileSync: fsyncPathSync,
104821
+ fsyncDirSync: fsyncPathSync
103844
104822
  },
103845
104823
  onDegraded: (info) => {
103846
104824
  process.stderr.write(`telegram gateway: inbound-spool durability ${info.degraded ? "DEGRADED to in-memory-only" : "RECOVERED"} path=${info.path} consecutiveFailures=${info.consecutiveFailures}${info.error != null ? ` error=${info.error}` : ""}
@@ -103901,13 +104879,13 @@ async function maybeRedeliverUndeliveredAnswer() {
103901
104879
  let transcriptText;
103902
104880
  try {
103903
104881
  const projectsDir = getProjectsDirForCwd();
103904
- const path3 = join67(projectsDir, `${sessionId}.jsonl`);
104882
+ const path3 = join68(projectsDir, `${sessionId}.jsonl`);
103905
104883
  if (!existsSync57(path3)) {
103906
104884
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
103907
104885
  `);
103908
104886
  return;
103909
104887
  }
103910
- transcriptText = readFileSync59(path3, "utf8");
104888
+ transcriptText = readFileSync60(path3, "utf8");
103911
104889
  } catch (err) {
103912
104890
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
103913
104891
  `);
@@ -104024,13 +105002,13 @@ function dispatchPermissionVerdict(ev) {
104024
105002
  }
104025
105003
  }
104026
105004
  function rearmPermissionFromStore(msg, persisted) {
104027
- const { requestId, toolName, description, inputPreview } = msg;
105005
+ const { requestId, toolName, description: description2, inputPreview } = msg;
104028
105006
  const startedAt = persisted.reduce((min, c) => c.startedAt < min ? c.startedAt : min, persisted[0].startedAt);
104029
105007
  const cardText = persisted[0].cardText;
104030
105008
  const cards = persisted.map((c) => ({ chatId: c.chatId, messageId: c.messageId }));
104031
105009
  pendingPermissions.set(requestId, {
104032
105010
  tool_name: toolName,
104033
- description,
105011
+ description: description2,
104034
105012
  input_preview: inputPreview,
104035
105013
  startedAt,
104036
105014
  card_text: cardText,
@@ -104066,8 +105044,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
104066
105044
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
104067
105045
  isShuttingDown: () => shuttingDown,
104068
105046
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
104069
- crashLogPath: join67(STATE_DIR, "bridge-crash.log"),
104070
- markerPath: join67(STATE_DIR, "bridge-dead-escalation.json"),
105047
+ crashLogPath: join68(STATE_DIR, "bridge-crash.log"),
105048
+ markerPath: join68(STATE_DIR, "bridge-dead-escalation.json"),
104071
105049
  log: (line) => process.stderr.write(`${line}
104072
105050
  `),
104073
105051
  priorStreak: bridgeDeadPriorStreak,
@@ -104173,8 +105151,8 @@ if (isGatewayMain)
104173
105151
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
104174
105152
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
104175
105153
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
104176
- configSnapshotPath: join67(resolvedAgentDirForCard, ".config-snapshot.json"),
104177
- bootCardStatePath: join67(resolvedAgentDirForCard, ".boot-card-msgid.json"),
105154
+ configSnapshotPath: join68(resolvedAgentDirForCard, ".config-snapshot.json"),
105155
+ bootCardStatePath: join68(resolvedAgentDirForCard, ".boot-card-msgid.json"),
104178
105156
  floodStatePath: FLOOD_STATE_PATH,
104179
105157
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
104180
105158
  }, ackMsgId).then((handle) => {
@@ -104289,7 +105267,7 @@ if (isGatewayMain)
104289
105267
  }
104290
105268
  },
104291
105269
  onPermissionRequest(_client, msg) {
104292
- const { requestId, toolName, description, inputPreview } = msg;
105270
+ const { requestId, toolName, description: description2, inputPreview } = msg;
104293
105271
  if (isPermissionRearmEnabled()) {
104294
105272
  const persisted = pendingPermissions.has(requestId) ? [] : permCardStore.loadAll().filter((e) => e.requestId === requestId);
104295
105273
  const disposition = classifyPermissionRequest({
@@ -104333,10 +105311,10 @@ if (isGatewayMain)
104333
105311
  const text5 = formatPermissionCardBody({
104334
105312
  toolName,
104335
105313
  inputPreview,
104336
- description,
105314
+ description: description2,
104337
105315
  agentName: _client.agentName
104338
105316
  });
104339
- const pendEntry = { tool_name: toolName, description, input_preview: inputPreview, startedAt: Date.now(), card_text: text5, cards: [] };
105317
+ const pendEntry = { tool_name: toolName, description: description2, input_preview: inputPreview, startedAt: Date.now(), card_text: text5, cards: [] };
104340
105318
  pendingPermissions.set(requestId, pendEntry);
104341
105319
  const activeTurn = currentTurn;
104342
105320
  postPermissionCard(requestId, pendEntry);
@@ -104842,7 +105820,7 @@ if (isGatewayMain)
104842
105820
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
104843
105821
  if (Number.isInteger(receiverUid))
104844
105822
  allowedUids.push(receiverUid);
104845
- const socketPath = join67(STATE_DIR, "webhook.sock");
105823
+ const socketPath = join68(STATE_DIR, "webhook.sock");
104846
105824
  const webhookInject = (agentName3, inbound) => {
104847
105825
  const msg = inbound;
104848
105826
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -105070,9 +106048,9 @@ function redactOutboundText(text5, site) {
105070
106048
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
105071
106049
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
105072
106050
  var voiceOnDemandCache = new VoiceOnDemandCache({
105073
- persistPath: join67(STATE_DIR, "voice-ondemand.json")
106051
+ persistPath: join68(STATE_DIR, "voice-ondemand.json")
105074
106052
  });
105075
- var VOICE_CACHE_DIR = join67(STATE_DIR, "voice-cache");
106053
+ var VOICE_CACHE_DIR = join68(STATE_DIR, "voice-cache");
105076
106054
  var voicePreSynthQueue = new PreSynthQueue({
105077
106055
  runJob: async (job) => {
105078
106056
  const sidecarToken = await materializeSidecarToken2();
@@ -105474,11 +106452,11 @@ async function executeSendGif(rawArgs) {
105474
106452
  };
105475
106453
  }
105476
106454
  async function publishToTelegraph(text5, shortName, authorName) {
105477
- const accountPath = join67(STATE_DIR, "telegraph-account.json");
106455
+ const accountPath = join68(STATE_DIR, "telegraph-account.json");
105478
106456
  let account = null;
105479
106457
  try {
105480
106458
  if (existsSync57(accountPath)) {
105481
- const raw = readFileSync59(accountPath, "utf-8");
106459
+ const raw = readFileSync60(accountPath, "utf-8");
105482
106460
  const parsed = JSON.parse(raw);
105483
106461
  if (parsed.shortName && parsed.accessToken) {
105484
106462
  account = parsed;
@@ -105497,8 +106475,8 @@ async function publishToTelegraph(text5, shortName, authorName) {
105497
106475
  }
105498
106476
  account = created.value;
105499
106477
  try {
105500
- mkdirSync50(STATE_DIR, { recursive: true, mode: 448 });
105501
- writeFileSync49(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
106478
+ mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
106479
+ writeFileSync50(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
105502
106480
  } catch (err) {
105503
106481
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
105504
106482
  `);
@@ -105614,7 +106592,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
105614
106592
  }
105615
106593
  function readLiveSwitchroomConfigText() {
105616
106594
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
105617
- return readFileSync59(cfgPath, "utf8");
106595
+ return readFileSync60(cfgPath, "utf8");
105618
106596
  }
105619
106597
  async function executeReact(args) {
105620
106598
  if (!args.chat_id)
@@ -105653,9 +106631,9 @@ async function executeDownloadAttachment(args) {
105653
106631
  fileUniqueId: file.file_unique_id,
105654
106632
  now: Date.now()
105655
106633
  });
105656
- mkdirSync50(INBOX_DIR, { recursive: true, mode: 448 });
106634
+ mkdirSync51(INBOX_DIR, { recursive: true, mode: 448 });
105657
106635
  assertInsideInbox2(INBOX_DIR, dlPath);
105658
- writeFileSync49(dlPath, buf, { mode: 384 });
106636
+ writeFileSync50(dlPath, buf, { mode: 384 });
105659
106637
  return { content: [{ type: "text", text: dlPath }] };
105660
106638
  }
105661
106639
  async function executeEditMessage(args) {
@@ -106299,7 +107277,7 @@ function gate(ctx) {
106299
107277
  }
106300
107278
  if (Object.keys(access.pending).length >= 3)
106301
107279
  return { action: "drop", reason: "pairing_capacity" };
106302
- const code2 = randomBytes12(3).toString("hex");
107280
+ const code2 = randomBytes13(3).toString("hex");
106303
107281
  const now = Date.now();
106304
107282
  access.pending[code2] = {
106305
107283
  senderId,
@@ -106539,8 +107517,13 @@ async function handleInbound(ctx, text5, downloadImage, attachment, extraAttachm
106539
107517
  maybePostMissedApprovalDigest("operator inbound");
106540
107518
  {
106541
107519
  const inboundChatId = ctx.chat?.id;
106542
- if (inboundChatId != null)
106543
- dmPinSweeper.sweep(String(inboundChatId));
107520
+ if (inboundChatId != null) {
107521
+ stalePinSweeper.sweepTarget({
107522
+ chatId: String(inboundChatId),
107523
+ threadId: ctx.message?.message_thread_id,
107524
+ isForum: ctx.chat?.is_forum === true
107525
+ });
107526
+ }
106544
107527
  }
106545
107528
  const inboundReceivedAt = Date.now();
106546
107529
  const machineInTurnAtReceipt = isMachineInTurn();
@@ -107023,14 +108006,14 @@ function restartMarkerPath() {
107023
108006
  const agentDir = resolveAgentDirFromEnv();
107024
108007
  if (!agentDir)
107025
108008
  return null;
107026
- return join67(agentDir, "restart-pending.json");
108009
+ return join68(agentDir, "restart-pending.json");
107027
108010
  }
107028
108011
  function writeRestartMarker(marker) {
107029
108012
  const p = restartMarkerPath();
107030
108013
  if (!p)
107031
108014
  return;
107032
108015
  try {
107033
- writeFileSync49(p, JSON.stringify(marker));
108016
+ writeFileSync50(p, JSON.stringify(marker));
107034
108017
  lastPlannedRestartAt = Date.now();
107035
108018
  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}
107036
108019
  `);
@@ -107049,7 +108032,7 @@ function readRestartMarker() {
107049
108032
  if (!p)
107050
108033
  return null;
107051
108034
  try {
107052
- return JSON.parse(readFileSync59(p, "utf8"));
108035
+ return JSON.parse(readFileSync60(p, "utf8"));
107053
108036
  } catch {
107054
108037
  return null;
107055
108038
  }
@@ -107059,7 +108042,7 @@ function clearRestartMarker() {
107059
108042
  if (!p)
107060
108043
  return;
107061
108044
  try {
107062
- rmSync7(p, { force: true });
108045
+ rmSync8(p, { force: true });
107063
108046
  process.stderr.write(`telegram gateway: restart-marker: cleared path=${p}
107064
108047
  `);
107065
108048
  } catch {}
@@ -107215,12 +108198,12 @@ function _resetDockerReachableCache() {
107215
108198
  }
107216
108199
  function spawnSwitchroomDetached(args, onFailure) {
107217
108200
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
107218
- const logPath = join67(STATE_DIR, "detached-spawn.log");
108201
+ const logPath = join68(STATE_DIR, "detached-spawn.log");
107219
108202
  let outFd = null;
107220
108203
  try {
107221
- mkdirSync50(STATE_DIR, { recursive: true });
107222
- outFd = openSync13(logPath, "a");
107223
- writeFileSync49(logPath, `
108204
+ mkdirSync51(STATE_DIR, { recursive: true });
108205
+ outFd = openSync15(logPath, "a");
108206
+ writeFileSync50(logPath, `
107224
108207
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
107225
108208
  `, { flag: "a" });
107226
108209
  } catch {}
@@ -107234,7 +108217,7 @@ function spawnSwitchroomDetached(args, onFailure) {
107234
108217
  });
107235
108218
  if (outFd != null) {
107236
108219
  try {
107237
- closeSync13(outFd);
108220
+ closeSync15(outFd);
107238
108221
  } catch {}
107239
108222
  }
107240
108223
  if (onFailure) {
@@ -107246,7 +108229,7 @@ function spawnSwitchroomDetached(args, onFailure) {
107246
108229
  return;
107247
108230
  let tail = "";
107248
108231
  try {
107249
- const full = readFileSync59(logPath, "utf8");
108232
+ const full = readFileSync60(logPath, "utf8");
107250
108233
  tail = full.split(`
107251
108234
  `).slice(-30).join(`
107252
108235
  `).trim();
@@ -107508,10 +108491,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
107508
108491
  }
107509
108492
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
107510
108493
  try {
107511
- const auditPath = join67(homedir19(), ".switchroom", "vault-audit.log");
108494
+ const auditPath = join68(homedir19(), ".switchroom", "vault-audit.log");
107512
108495
  if (!existsSync57(auditPath))
107513
108496
  return [];
107514
- const raw = readFileSync59(auditPath, "utf8");
108497
+ const raw = readFileSync60(auditPath, "utf8");
107515
108498
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
107516
108499
  } catch {
107517
108500
  return [];
@@ -107562,7 +108545,7 @@ async function buildAgentMetadata(agentName3) {
107562
108545
  try {
107563
108546
  const agentDir = resolveAgentDirFromEnv();
107564
108547
  if (agentDir) {
107565
- const raw = readFileSync59(join67(agentDir, ".claude", ".claude.json"), "utf8");
108548
+ const raw = readFileSync60(join68(agentDir, ".claude", ".claude.json"), "utf8");
107566
108549
  claudeJson = JSON.parse(raw);
107567
108550
  }
107568
108551
  } catch {}
@@ -107691,7 +108674,7 @@ function buildModelDeps(restartCtx) {
107691
108674
  try {
107692
108675
  const agentDir = resolveAgentDirFromEnv();
107693
108676
  if (agentDir) {
107694
- const local = await fetchQuota2({ claudeConfigDir: join67(agentDir, ".claude") });
108677
+ const local = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
107695
108678
  if (local.ok)
107696
108679
  return formatQuotaLine2(local.data);
107697
108680
  }
@@ -107937,10 +108920,10 @@ function effortMenuReplyMarkup(reply) {
107937
108920
  function flushAgentHandoff(agentDir) {
107938
108921
  let removed = 0;
107939
108922
  for (const fname of [".handoff.md", ".handoff-topic"]) {
107940
- const p = join67(agentDir, fname);
108923
+ const p = join68(agentDir, fname);
107941
108924
  try {
107942
108925
  if (existsSync57(p)) {
107943
- unlinkSync29(p);
108926
+ unlinkSync30(p);
107944
108927
  removed++;
107945
108928
  }
107946
108929
  } catch (err) {
@@ -107995,7 +108978,7 @@ async function handleNewCommand(ctx) {
107995
108978
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
107996
108979
  if (agentDir != null) {
107997
108980
  try {
107998
- writeFileSync49(join67(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
108981
+ writeFileSync50(join68(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
107999
108982
  `, "utf8");
108000
108983
  } catch (err) {
108001
108984
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -108108,11 +109091,11 @@ function buildFolderPickerDeps() {
108108
109091
  };
108109
109092
  }
108110
109093
  var lockoutOps = {
108111
- readFileSync: (p, enc) => readFileSync59(p, enc),
108112
- writeFileSync: (p, data, opts) => writeFileSync49(p, data, opts),
109094
+ readFileSync: (p, enc) => readFileSync60(p, enc),
109095
+ writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
108113
109096
  existsSync: (p) => existsSync57(p),
108114
- mkdirSync: (p, opts) => mkdirSync50(p, opts),
108115
- joinPath: (...parts) => join67(...parts)
109097
+ mkdirSync: (p, opts) => mkdirSync51(p, opts),
109098
+ joinPath: (...parts) => join68(...parts)
108116
109099
  };
108117
109100
  var FLEET_FALLBACK_DEDUP_MS = 30000;
108118
109101
  function isAuthBrokerSocketReachable() {
@@ -108372,7 +109355,7 @@ async function runCreditWatch() {
108372
109355
  if (!agentDir)
108373
109356
  return;
108374
109357
  const agentName3 = getMyAgentName();
108375
- const claudeConfigDir = join67(agentDir, ".claude");
109358
+ const claudeConfigDir = join68(agentDir, ".claude");
108376
109359
  const stateDir = STATE_DIR;
108377
109360
  const reason = readClaudeJsonOverage(claudeConfigDir);
108378
109361
  const prev = loadCreditState(stateDir);
@@ -110128,7 +111111,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
110128
111111
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
110129
111112
  return;
110130
111113
  }
110131
- const result = await fetchQuota2({ claudeConfigDir: join67(agentDir, ".claude") });
111114
+ const result = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
110132
111115
  if (!result.ok) {
110133
111116
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
110134
111117
  return;
@@ -110515,7 +111498,7 @@ ${interimLabel}` : interimLabel
110515
111498
  const unifiedDiff = (() => {
110516
111499
  try {
110517
111500
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
110518
- const raw = readFileSync59(cfgPath, "utf8");
111501
+ const raw = readFileSync60(cfgPath, "utf8");
110519
111502
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
110520
111503
  } catch (err) {
110521
111504
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -111384,7 +112367,7 @@ async function startGateway() {
111384
112367
  return;
111385
112368
  }
111386
112369
  })();
111387
- const resolvedAgentDirForBootCard = agentDir ?? join67(homedir19(), ".switchroom", "agents", agentSlug);
112370
+ const resolvedAgentDirForBootCard = agentDir ?? join68(homedir19(), ".switchroom", "agents", agentSlug);
111388
112371
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
111389
112372
  agentName: agentDisplayName,
111390
112373
  agentSlug,
@@ -111398,8 +112381,8 @@ async function startGateway() {
111398
112381
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
111399
112382
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
111400
112383
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
111401
- configSnapshotPath: join67(resolvedAgentDirForBootCard, ".config-snapshot.json"),
111402
- bootCardStatePath: join67(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
112384
+ configSnapshotPath: join68(resolvedAgentDirForBootCard, ".config-snapshot.json"),
112385
+ bootCardStatePath: join68(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
111403
112386
  floodStatePath: FLOOD_STATE_PATH,
111404
112387
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
111405
112388
  }, ackMsgId);
@@ -111430,10 +112413,10 @@ async function startGateway() {
111430
112413
  try {
111431
112414
  const smAgentDir = resolveAgentDirFromEnv();
111432
112415
  if (smAgentDir) {
111433
- const activePath = join67(smAgentDir, ".active-session-model");
112416
+ const activePath = join68(smAgentDir, ".active-session-model");
111434
112417
  if (existsSync57(activePath)) {
111435
112418
  try {
111436
- const launched = readFileSync59(activePath, "utf8").trim();
112419
+ const launched = readFileSync60(activePath, "utf8").trim();
111437
112420
  const configured = (() => {
111438
112421
  const d = switchroomExecJson(["agent", "list"]);
111439
112422
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -111466,29 +112449,29 @@ async function startGateway() {
111466
112449
  deliverModelSwitchBootNotice({
111467
112450
  ...modelBootCardDeps,
111468
112451
  confirmation,
111469
- hasSessionModelAlert: existsSync57(join67(smAgentDir, ".session-model-alert"))
112452
+ hasSessionModelAlert: existsSync57(join68(smAgentDir, ".session-model-alert"))
111470
112453
  });
111471
112454
  }
111472
112455
  } catch {}
111473
112456
  }
111474
- const activeEffortPath = join67(smAgentDir, ".active-session-effort");
112457
+ const activeEffortPath = join68(smAgentDir, ".active-session-effort");
111475
112458
  if (existsSync57(activeEffortPath)) {
111476
112459
  try {
111477
- const launchedEffort = readFileSync59(activeEffortPath, "utf8").trim();
112460
+ const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
111478
112461
  const configuredEffort = getConfiguredEffortForPersist();
111479
112462
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
111480
112463
  } catch {}
111481
112464
  }
111482
- const alertPath = join67(smAgentDir, ".session-model-alert");
112465
+ const alertPath = join68(smAgentDir, ".session-model-alert");
111483
112466
  if (existsSync57(alertPath)) {
111484
112467
  let alertText = null;
111485
112468
  try {
111486
- alertText = readFileSync59(alertPath, "utf8").trim();
112469
+ alertText = readFileSync60(alertPath, "utf8").trim();
111487
112470
  } catch {
111488
112471
  alertText = null;
111489
112472
  }
111490
112473
  try {
111491
- unlinkSync29(alertPath);
112474
+ unlinkSync30(alertPath);
111492
112475
  } catch {}
111493
112476
  if (alertText && alertText.length > 0) {
111494
112477
  const operators = loadAccess().allowFrom;
@@ -111609,12 +112592,12 @@ async function startGateway() {
111609
112592
  staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
111610
112593
  absoluteRowLifetimeCapMs: resolveInflightTerminalCapMs() * WORKER_FEED_ABSOLUTE_ROW_LIFETIME_CAP_MULTIPLE,
111611
112594
  groupMessageLifetimeCapMs: WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS,
111612
- reconcilePin: ({ feedKey, chatId, messageId }) => {
112595
+ reconcilePin: ({ feedKey, chatId, threadId, messageId }) => {
111613
112596
  if (!PIN_STATUS_WHILE_WORKING)
111614
112597
  return;
111615
112598
  const key = `wk:group:${feedKey}`;
111616
112599
  if (messageId != null) {
111617
- reconcileStatusPin(key, chatId, { pinned: true, messageId });
112600
+ reconcileStatusPin(key, chatId, { pinned: true, messageId }, threadId);
111618
112601
  } else {
111619
112602
  const unpinChat = chatId || statusPinClaims.get(key)?.chatId;
111620
112603
  if (unpinChat != null && unpinChat.length > 0) {
@@ -111667,7 +112650,7 @@ async function startGateway() {
111667
112650
  `);
111668
112651
  }
111669
112652
  },
111670
- onFinish: ({ agentId, outcome, description, resultText, toolCount, totalTokens, durationMs, background: entryBackground }) => {
112653
+ onFinish: ({ agentId, outcome, description: description2, resultText, toolCount, totalTokens, durationMs, background: entryBackground }) => {
111671
112654
  workerFeedOriginDeferrals.delete(agentId);
111672
112655
  deferredDoneReactions.promote();
111673
112656
  let fleetChatId = "";
@@ -111680,10 +112663,10 @@ async function startGateway() {
111680
112663
  }
111681
112664
  }
111682
112665
  } catch {}
111683
- let dispatch = resolveWorkerFeedDispatch(null, description, entryBackground);
112666
+ let dispatch = resolveWorkerFeedDispatch(null, description2, entryBackground);
111684
112667
  if (turnsDb != null) {
111685
112668
  try {
111686
- dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description, entryBackground);
112669
+ dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description2, entryBackground);
111687
112670
  } catch {}
111688
112671
  }
111689
112672
  let isBackground = dispatch.isBackground;
@@ -111773,7 +112756,7 @@ async function startGateway() {
111773
112756
  fleetChatId: handbackOrigin?.chatId || fleetChatId,
111774
112757
  ...handbackOrigin?.threadId != null ? { originThreadId: handbackOrigin.threadId } : {},
111775
112758
  ownerChatId: loadAccess().allowFrom[0] ?? "",
111776
- taskDescription: description,
112759
+ taskDescription: description2,
111777
112760
  resultText,
111778
112761
  jsonlAgentId: agentId
111779
112762
  });
@@ -111799,7 +112782,7 @@ async function startGateway() {
111799
112782
  process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
111800
112783
  `);
111801
112784
  },
111802
- onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, totalTokens, progressLine, model, skeleton }) => {
112785
+ onProgress: ({ agentId, description: description2, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, totalTokens, progressLine, model, skeleton }) => {
111803
112786
  let fleetChatId = "";
111804
112787
  try {
111805
112788
  const fleets = progressDriver?.peekAllFleets() ?? [];
@@ -111810,10 +112793,10 @@ async function startGateway() {
111810
112793
  }
111811
112794
  }
111812
112795
  } catch {}
111813
- let dispatch = resolveWorkerFeedDispatch(null, description);
112796
+ let dispatch = resolveWorkerFeedDispatch(null, description2);
111814
112797
  if (turnsDb != null) {
111815
112798
  try {
111816
- dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description);
112799
+ dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description2);
111817
112800
  } catch {}
111818
112801
  }
111819
112802
  const isBackground = dispatch.isBackground || dispatch.isNested;
@@ -111931,7 +112914,7 @@ async function startGateway() {
111931
112914
  ...progressOrigin?.threadId != null ? { originThreadId: progressOrigin.threadId } : {},
111932
112915
  ownerChatId: loadAccess().allowFrom[0] ?? "",
111933
112916
  subagentJsonlId: agentId,
111934
- taskDescription: description,
112917
+ taskDescription: description2,
111935
112918
  latestSummary,
111936
112919
  elapsedMs,
111937
112920
  progressIntervalMs: DEFAULT_PROGRESS_INTERVAL_MS,