switchroom 0.19.14 → 0.19.16

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 (49) hide show
  1. package/dist/cli/switchroom.js +1 -1
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +1 -1
  4. package/telegram-plugin/bridge/bridge.ts +1 -1
  5. package/telegram-plugin/dist/bridge/bridge.js +31 -2
  6. package/telegram-plugin/dist/gateway/gateway.js +1690 -932
  7. package/telegram-plugin/dist/server.js +31 -2
  8. package/telegram-plugin/gateway/background-shell-liveness.ts +65 -0
  9. package/telegram-plugin/gateway/forward-origin.ts +6 -1
  10. package/telegram-plugin/gateway/gateway.ts +10 -57
  11. package/telegram-plugin/gateway/narrative-lane.ts +11 -0
  12. package/telegram-plugin/gateway/outbound-send-path.ts +25 -23
  13. package/telegram-plugin/gateway/outbox-listen-markup.ts +67 -0
  14. package/telegram-plugin/gateway/outbox-sweep.ts +124 -20
  15. package/telegram-plugin/gateway/rich-message-handler.ts +241 -0
  16. package/telegram-plugin/gateway/silence-poke-session-event.ts +89 -0
  17. package/telegram-plugin/gateway/stream-render.ts +107 -15
  18. package/telegram-plugin/gateway/unhandled-message.ts +14 -0
  19. package/telegram-plugin/hooks/narration-classify.d.mts +23 -0
  20. package/telegram-plugin/hooks/narration-classify.mjs +210 -0
  21. package/telegram-plugin/hooks/silent-end-scan.mjs +136 -82
  22. package/telegram-plugin/narrative-flush.ts +35 -0
  23. package/telegram-plugin/outbox.ts +73 -3
  24. package/telegram-plugin/session-tail.ts +88 -1
  25. package/telegram-plugin/shown-ledger.ts +145 -0
  26. package/telegram-plugin/silence-poke.ts +118 -1
  27. package/telegram-plugin/silent-end.ts +42 -0
  28. package/telegram-plugin/tests/background-shell-liveness.test.ts +72 -0
  29. package/telegram-plugin/tests/backstop-exactly-once.test.ts +335 -0
  30. package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +14 -0
  31. package/telegram-plugin/tests/feed-survival.test.ts +7 -1
  32. package/telegram-plugin/tests/fixtures/bg-shell-liveness-3519.jsonl +3 -0
  33. package/telegram-plugin/tests/forward-origin.test.ts +20 -0
  34. package/telegram-plugin/tests/forwarded-rich-message-coalesce.test.ts +290 -0
  35. package/telegram-plugin/tests/forwarded-rich-message.test.ts +305 -0
  36. package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +1 -0
  37. package/telegram-plugin/tests/narration-leak-3513.test.ts +352 -0
  38. package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +253 -0
  39. package/telegram-plugin/tests/session-tail.test.ts +91 -1
  40. package/telegram-plugin/tests/silence-poke.test.ts +280 -0
  41. package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +42 -13
  42. package/telegram-plugin/tests/silent-end.test.ts +7 -1
  43. package/telegram-plugin/tests/tts-normalize.test.ts +66 -0
  44. package/telegram-plugin/tests/turn-flush-safety.test.ts +35 -3
  45. package/telegram-plugin/tests/voice-normalize-text.test.ts +82 -1
  46. package/telegram-plugin/tts-normalize.ts +12 -0
  47. package/telegram-plugin/turn-flush-safety.ts +66 -53
  48. package/telegram-plugin/voice-normalize-text.ts +100 -0
  49. package/telegram-plugin/voice-ondemand.ts +71 -0
@@ -8869,12 +8869,12 @@ var require_mod4 = __commonJS((exports) => {
8869
8869
  // flood-circuit-breaker.ts
8870
8870
  import {
8871
8871
  existsSync as existsSync2,
8872
- readFileSync as readFileSync2,
8873
- writeFileSync as writeFileSync3,
8874
- mkdirSync as mkdirSync4,
8872
+ readFileSync as readFileSync3,
8873
+ writeFileSync as writeFileSync4,
8874
+ mkdirSync as mkdirSync5,
8875
8875
  chmodSync,
8876
8876
  unlinkSync as unlinkSync2,
8877
- renameSync as renameSync4
8877
+ renameSync as renameSync5
8878
8878
  } from "node:fs";
8879
8879
  function floodWaitRemainingMs(state, now) {
8880
8880
  if (!state)
@@ -8884,7 +8884,7 @@ function floodWaitRemainingMs(state, now) {
8884
8884
  function readFloodStateResult(path) {
8885
8885
  let text;
8886
8886
  try {
8887
- text = readFileSync2(path, "utf-8");
8887
+ text = readFileSync3(path, "utf-8");
8888
8888
  } catch (err) {
8889
8889
  const code = err?.code;
8890
8890
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -13554,7 +13554,7 @@ __export(exports_tmux, {
13554
13554
  captureAgentPane: () => captureAgentPane
13555
13555
  });
13556
13556
  import { execFileSync as execFileSync2 } from "node:child_process";
13557
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
13557
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync6, readdirSync as readdirSync2, statSync as statSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
13558
13558
  import { resolve } from "node:path";
13559
13559
  function captureAgentPane(opts) {
13560
13560
  const { agentName, agentDir, reason } = opts;
@@ -13566,7 +13566,7 @@ function captureAgentPane(opts) {
13566
13566
  const reasonSlug = sanitizeReason(reason);
13567
13567
  const outPath = resolve(outDir, `${ts}-${reasonSlug}.txt`);
13568
13568
  try {
13569
- mkdirSync5(outDir, { recursive: true, mode: 448 });
13569
+ mkdirSync6(outDir, { recursive: true, mode: 448 });
13570
13570
  } catch (err) {
13571
13571
  const msg = `mkdir crash-reports failed: ${err.message}`;
13572
13572
  console.error(`[tmux-capture] ${agentName}: ${msg}`);
@@ -13605,7 +13605,7 @@ function captureAgentPane(opts) {
13605
13605
  ` + `
13606
13606
  `;
13607
13607
  try {
13608
- writeFileSync4(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
13608
+ writeFileSync5(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
13609
13609
  mode: 384
13610
13610
  });
13611
13611
  } catch (err) {
@@ -29091,8 +29091,8 @@ __export(exports_history, {
29091
29091
  _resetForTests: () => _resetForTests,
29092
29092
  MIN_PREFIX_MATCH_CHARS: () => MIN_PREFIX_MATCH_CHARS2
29093
29093
  });
29094
- import { chmodSync as chmodSync9, existsSync as existsSync26, mkdirSync as mkdirSync27 } from "fs";
29095
- import { join as join30 } from "path";
29094
+ import { chmodSync as chmodSync9, existsSync as existsSync27, mkdirSync as mkdirSync28 } from "fs";
29095
+ import { join as join31 } from "path";
29096
29096
  function loadDatabaseClass2() {
29097
29097
  if (DatabaseClass2 != null)
29098
29098
  return DatabaseClass2;
@@ -29123,8 +29123,8 @@ function initHistory2(stateDir, retentionDays = 30) {
29123
29123
  if (db2 != null)
29124
29124
  return;
29125
29125
  const Database = loadDatabaseClass2();
29126
- mkdirSync27(stateDir, { recursive: true, mode: 448 });
29127
- const path2 = join30(stateDir, "history.db");
29126
+ mkdirSync28(stateDir, { recursive: true, mode: 448 });
29127
+ const path2 = join31(stateDir, "history.db");
29128
29128
  dbPath2 = path2;
29129
29129
  db2 = new Database(path2, { create: true });
29130
29130
  db2.exec("PRAGMA journal_mode = WAL");
@@ -29190,7 +29190,7 @@ function initHistory2(stateDir, retentionDays = 30) {
29190
29190
  }
29191
29191
  for (const suffix of ["", "-shm", "-wal"]) {
29192
29192
  const f = path2 + suffix;
29193
- if (existsSync26(f)) {
29193
+ if (existsSync27(f)) {
29194
29194
  try {
29195
29195
  chmodSync9(f, 420);
29196
29196
  } catch {}
@@ -29242,7 +29242,7 @@ function checkpointWal2() {
29242
29242
  if (dbPath2) {
29243
29243
  for (const suffix of ["-shm", "-wal"]) {
29244
29244
  const f = dbPath2 + suffix;
29245
- if (existsSync26(f)) {
29245
+ if (existsSync27(f)) {
29246
29246
  try {
29247
29247
  chmodSync9(f, 420);
29248
29248
  } catch {}
@@ -29489,7 +29489,7 @@ var FLUSH_SUPPRESSION_WINDOW_MS = 2000;
29489
29489
  var init_turn_flush_suppression = () => {};
29490
29490
 
29491
29491
  // ../src/util/atomic.ts
29492
- import { closeSync as closeSync6, constants as constants2, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync12, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29492
+ import { closeSync as closeSync6, constants as constants2, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync13, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29493
29493
  var TMP_OPEN_FLAGS;
29494
29494
  var init_atomic = __esm(() => {
29495
29495
  TMP_OPEN_FLAGS = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
@@ -32451,7 +32451,7 @@ var require_util = __commonJS((exports2) => {
32451
32451
  return path2;
32452
32452
  }
32453
32453
  exports2.normalize = normalize;
32454
- function join38(aRoot, aPath) {
32454
+ function join39(aRoot, aPath) {
32455
32455
  if (aRoot === "") {
32456
32456
  aRoot = ".";
32457
32457
  }
@@ -32483,7 +32483,7 @@ var require_util = __commonJS((exports2) => {
32483
32483
  }
32484
32484
  return joined;
32485
32485
  }
32486
- exports2.join = join38;
32486
+ exports2.join = join39;
32487
32487
  exports2.isAbsolute = function(aPath) {
32488
32488
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
32489
32489
  };
@@ -32656,7 +32656,7 @@ var require_util = __commonJS((exports2) => {
32656
32656
  parsed.path = parsed.path.substring(0, index2 + 1);
32657
32657
  }
32658
32658
  }
32659
- sourceURL = join38(urlGenerate(parsed), sourceURL);
32659
+ sourceURL = join39(urlGenerate(parsed), sourceURL);
32660
32660
  }
32661
32661
  return normalize(sourceURL);
32662
32662
  }
@@ -35261,19 +35261,19 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35261
35261
  }
35262
35262
 
35263
35263
  // gateway/quota-cache.ts
35264
- import { existsSync as existsSync42, readFileSync as readFileSync43, writeFileSync as writeFileSync36, mkdirSync as mkdirSync37 } from "fs";
35265
- import { join as join47, dirname as dirname15 } from "path";
35264
+ import { existsSync as existsSync43, readFileSync as readFileSync44, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38 } from "fs";
35265
+ import { join as join48, dirname as dirname15 } from "path";
35266
35266
  function defaultCachePath() {
35267
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join47(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35267
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join48(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35268
35268
  }
35269
35269
  function readQuotaCache(opts = {}) {
35270
35270
  const path2 = opts.path ?? defaultCachePath();
35271
35271
  const now = opts.now ?? Date.now();
35272
- if (!existsSync42(path2))
35272
+ if (!existsSync43(path2))
35273
35273
  return null;
35274
35274
  let entry;
35275
35275
  try {
35276
- entry = JSON.parse(readFileSync43(path2, "utf8"));
35276
+ entry = JSON.parse(readFileSync44(path2, "utf8"));
35277
35277
  } catch {
35278
35278
  return null;
35279
35279
  }
@@ -35299,8 +35299,8 @@ function writeQuotaCache(result, opts = {}) {
35299
35299
  result
35300
35300
  };
35301
35301
  try {
35302
- mkdirSync37(dirname15(path2), { recursive: true });
35303
- writeFileSync36(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35302
+ mkdirSync38(dirname15(path2), { recursive: true });
35303
+ writeFileSync37(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35304
35304
  } catch {}
35305
35305
  }
35306
35306
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -35310,8 +35310,8 @@ var init_quota_cache = __esm(() => {
35310
35310
  });
35311
35311
 
35312
35312
  // gateway/boot-probes.ts
35313
- import { readFileSync as readFileSync44, readdirSync as readdirSync10, existsSync as existsSync43 } from "fs";
35314
- import { join as join48 } from "path";
35313
+ import { readFileSync as readFileSync45, readdirSync as readdirSync10, existsSync as existsSync44 } from "fs";
35314
+ import { join as join49 } from "path";
35315
35315
  import { execFile as execFileCb } from "child_process";
35316
35316
  import { promisify } from "util";
35317
35317
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -35353,11 +35353,11 @@ function mapPlan(billingType, hasExtra) {
35353
35353
  }
35354
35354
  async function probeAccount(agentDir) {
35355
35355
  return withTimeout("Account", (async () => {
35356
- const claudeDir = join48(agentDir, ".claude");
35357
- const claudeJsonPath = join48(claudeDir, ".claude.json");
35356
+ const claudeDir = join49(agentDir, ".claude");
35357
+ const claudeJsonPath = join49(claudeDir, ".claude.json");
35358
35358
  let cfg = {};
35359
35359
  try {
35360
- const raw = readFileSync44(claudeJsonPath, "utf8");
35360
+ const raw = readFileSync45(claudeJsonPath, "utf8");
35361
35361
  cfg = JSON.parse(raw);
35362
35362
  } catch {
35363
35363
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -35375,12 +35375,12 @@ async function probeAccount(agentDir) {
35375
35375
  let tokenStr = "";
35376
35376
  let status = "ok";
35377
35377
  for (const candidate of [
35378
- join48(claudeDir, ".oauth-token.meta.json"),
35379
- join48(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35378
+ join49(claudeDir, ".oauth-token.meta.json"),
35379
+ join49(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35380
35380
  ]) {
35381
- if (existsSync43(candidate)) {
35381
+ if (existsSync44(candidate)) {
35382
35382
  try {
35383
- const meta = JSON.parse(readFileSync44(candidate, "utf8"));
35383
+ const meta = JSON.parse(readFileSync45(candidate, "utf8"));
35384
35384
  if (meta.expiresAt) {
35385
35385
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
35386
35386
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -35555,9 +35555,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35555
35555
  if (!cgroup)
35556
35556
  return null;
35557
35557
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
35558
- if (!existsSync43(procsPath))
35558
+ if (!existsSync44(procsPath))
35559
35559
  return null;
35560
- const pidsRaw = readFileSync44(procsPath, "utf-8");
35560
+ const pidsRaw = readFileSync45(procsPath, "utf-8");
35561
35561
  const pids = pidsRaw.split(`
35562
35562
  `).map((s) => s.trim()).filter(Boolean);
35563
35563
  if (pids.length === 0)
@@ -35570,7 +35570,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35570
35570
  let rss = 0;
35571
35571
  let comm = "";
35572
35572
  try {
35573
- const status = readFileSync44(`/proc/${pid}/status`, "utf-8");
35573
+ const status = readFileSync45(`/proc/${pid}/status`, "utf-8");
35574
35574
  const rssLine = status.split(`
35575
35575
  `).find((l) => l.startsWith("VmRSS:"));
35576
35576
  if (rssLine) {
@@ -35582,7 +35582,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35582
35582
  continue;
35583
35583
  }
35584
35584
  try {
35585
- comm = readFileSync44(`/proc/${pid}/comm`, "utf-8").trim();
35585
+ comm = readFileSync45(`/proc/${pid}/comm`, "utf-8").trim();
35586
35586
  } catch {}
35587
35587
  candidates.push({ pid, rss, comm });
35588
35588
  }
@@ -35762,9 +35762,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
35762
35762
  let claudeDirForProbe = null;
35763
35763
  for (const candidate of [
35764
35764
  claudeConfigDir,
35765
- join48(claudeConfigDir, "accounts", "default")
35765
+ join49(claudeConfigDir, "accounts", "default")
35766
35766
  ]) {
35767
- if (existsSync43(join48(candidate, ".oauth-token"))) {
35767
+ if (existsSync44(join49(candidate, ".oauth-token"))) {
35768
35768
  claudeDirForProbe = candidate;
35769
35769
  break;
35770
35770
  }
@@ -35829,7 +35829,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
35829
35829
  }
35830
35830
  function readContainerBootTimeMsForProbe() {
35831
35831
  try {
35832
- const stat1 = readFileSync44("/proc/1/stat", "utf8");
35832
+ const stat1 = readFileSync45("/proc/1/stat", "utf8");
35833
35833
  const lastParen = stat1.lastIndexOf(")");
35834
35834
  if (lastParen < 0)
35835
35835
  return null;
@@ -35837,7 +35837,7 @@ function readContainerBootTimeMsForProbe() {
35837
35837
  const starttimeTicks = Number(after[19]);
35838
35838
  if (!Number.isFinite(starttimeTicks))
35839
35839
  return null;
35840
- const procStat = readFileSync44("/proc/stat", "utf8");
35840
+ const procStat = readFileSync45("/proc/stat", "utf8");
35841
35841
  const btimeLine = procStat.split(`
35842
35842
  `).find((l) => l.startsWith("btime "));
35843
35843
  if (!btimeLine)
@@ -35935,7 +35935,7 @@ async function probeUds(label, socketPath, opts = {}) {
35935
35935
  }
35936
35936
  return withTimeout(label, (async () => {
35937
35937
  if (!opts.connectImpl) {
35938
- if (!existsSync43(socketPath)) {
35938
+ if (!existsSync44(socketPath)) {
35939
35939
  return {
35940
35940
  status: "fail",
35941
35941
  label,
@@ -35999,7 +35999,7 @@ async function probeSkills(agentDir, opts = {}) {
35999
35999
  return withTimeout("Skills", (async () => {
36000
36000
  const fs2 = opts.fs ?? realSkillsFs;
36001
36001
  const max = opts.maxNamesShown ?? 3;
36002
- const skillsDir = join48(agentDir, ".claude", "skills");
36002
+ const skillsDir = join49(agentDir, ".claude", "skills");
36003
36003
  if (!fs2.exists(skillsDir)) {
36004
36004
  return { status: "ok", label: "Skills", detail: "no skills dir" };
36005
36005
  }
@@ -36014,17 +36014,17 @@ async function probeSkills(agentDir, opts = {}) {
36014
36014
  }
36015
36015
  const dangling = [];
36016
36016
  for (const name of entries) {
36017
- const skillPath = join48(skillsDir, name);
36017
+ const skillPath = join49(skillsDir, name);
36018
36018
  if (!fs2.exists(skillPath)) {
36019
36019
  dangling.push(name);
36020
36020
  continue;
36021
36021
  }
36022
- const skillMd = join48(skillPath, "SKILL.md");
36022
+ const skillMd = join49(skillPath, "SKILL.md");
36023
36023
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
36024
36024
  continue;
36025
36025
  }
36026
36026
  }
36027
- const overlayDir = opts.overlaySkillsDir ?? join48(agentDir, "skills.d");
36027
+ const overlayDir = opts.overlaySkillsDir ?? join49(agentDir, "skills.d");
36028
36028
  const overlaySlugs = new Set;
36029
36029
  if (fs2.exists(overlayDir)) {
36030
36030
  let overlayEntries = [];
@@ -36066,8 +36066,8 @@ function renderBucketedSkills(switchroom, agent) {
36066
36066
  }
36067
36067
  async function probeConnections(agentDir, opts = {}) {
36068
36068
  return withTimeout("Connections", (async () => {
36069
- const path2 = join48(agentDir, ".claude", "connection-health.json");
36070
- const read = opts.readFileImpl ?? ((p) => readFileSync44(p, "utf8"));
36069
+ const path2 = join49(agentDir, ".claude", "connection-health.json");
36070
+ const read = opts.readFileImpl ?? ((p) => readFileSync45(p, "utf8"));
36071
36071
  let issues = [];
36072
36072
  try {
36073
36073
  const parsed = JSON.parse(read(path2));
@@ -36098,24 +36098,24 @@ var init_boot_probes = __esm(() => {
36098
36098
  execFile = promisify(execFileCb);
36099
36099
  realProcFs = {
36100
36100
  readdir: (p) => readdirSync10(p),
36101
- readFile: (p) => readFileSync44(p, "utf-8")
36101
+ readFile: (p) => readFileSync45(p, "utf-8")
36102
36102
  };
36103
36103
  realSchedulerFs = {
36104
- readFile: (p) => readFileSync44(p, "utf-8"),
36104
+ readFile: (p) => readFileSync45(p, "utf-8"),
36105
36105
  mtimeMs: (p) => {
36106
36106
  const { statSync: statSync14 } = __require("fs");
36107
36107
  return statSync14(p).mtimeMs;
36108
36108
  },
36109
- exists: (p) => existsSync43(p)
36109
+ exists: (p) => existsSync44(p)
36110
36110
  };
36111
36111
  realSkillsFs = {
36112
36112
  readdir: (p) => readdirSync10(p),
36113
- exists: (p) => existsSync43(p)
36113
+ exists: (p) => existsSync44(p)
36114
36114
  };
36115
36115
  });
36116
36116
 
36117
36117
  // gateway/boot-issue-cache.ts
36118
- import { existsSync as existsSync44, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38, renameSync as renameSync18 } from "fs";
36118
+ import { existsSync as existsSync45, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync39, renameSync as renameSync19 } from "fs";
36119
36119
  import { dirname as dirname16 } from "path";
36120
36120
  function fingerprintProbe(key, r) {
36121
36121
  if (r.status === "ok")
@@ -36195,11 +36195,11 @@ function diffProbes(probes, cache, opts = {}) {
36195
36195
  return out;
36196
36196
  }
36197
36197
  function loadCache(path2, now = Date.now) {
36198
- if (!existsSync44(path2))
36198
+ if (!existsSync45(path2))
36199
36199
  return { ...EMPTY_CACHE, probes: {} };
36200
36200
  let raw;
36201
36201
  try {
36202
- raw = readFileSync45(path2, "utf-8");
36202
+ raw = readFileSync46(path2, "utf-8");
36203
36203
  } catch {
36204
36204
  return { ...EMPTY_CACHE, probes: {} };
36205
36205
  }
@@ -36208,7 +36208,7 @@ function loadCache(path2, now = Date.now) {
36208
36208
  parsed = JSON.parse(raw);
36209
36209
  } catch {
36210
36210
  try {
36211
- renameSync18(path2, `${path2}.corrupt-${now()}`);
36211
+ renameSync19(path2, `${path2}.corrupt-${now()}`);
36212
36212
  } catch {}
36213
36213
  return { ...EMPTY_CACHE, probes: {} };
36214
36214
  }
@@ -36242,10 +36242,10 @@ function applyAndSave(path2, cache, diff) {
36242
36242
  }
36243
36243
  }
36244
36244
  try {
36245
- mkdirSync38(dirname16(path2), { recursive: true });
36245
+ mkdirSync39(dirname16(path2), { recursive: true });
36246
36246
  const tmp = `${path2}.tmp`;
36247
- writeFileSync37(tmp, JSON.stringify(next), { mode: 384 });
36248
- renameSync18(tmp, path2);
36247
+ writeFileSync38(tmp, JSON.stringify(next), { mode: 384 });
36248
+ renameSync19(tmp, path2);
36249
36249
  } catch {}
36250
36250
  return next;
36251
36251
  }
@@ -36257,14 +36257,14 @@ var init_boot_issue_cache = __esm(() => {
36257
36257
  });
36258
36258
 
36259
36259
  // gateway/config-snapshot.ts
36260
- import { createHash as createHash4 } from "crypto";
36261
- import { existsSync as existsSync45, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync39, renameSync as renameSync19 } from "fs";
36260
+ import { createHash as createHash5 } from "crypto";
36261
+ import { existsSync as existsSync46, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync40, renameSync as renameSync20 } from "fs";
36262
36262
  import { dirname as dirname17 } from "path";
36263
36263
  function hashStringArray(items) {
36264
36264
  if (!items || items.length === 0)
36265
36265
  return null;
36266
36266
  const sorted = [...items].sort();
36267
- const raw = createHash4("sha256").update(sorted.join("\x00")).digest("hex");
36267
+ const raw = createHash5("sha256").update(sorted.join("\x00")).digest("hex");
36268
36268
  return raw.slice(0, 12);
36269
36269
  }
36270
36270
  function normalizeModel(model) {
@@ -36323,11 +36323,11 @@ function renderConfigChangeDim(dim) {
36323
36323
  }
36324
36324
  }
36325
36325
  function loadSnapshot(path2, now = Date.now) {
36326
- if (!existsSync45(path2))
36326
+ if (!existsSync46(path2))
36327
36327
  return null;
36328
36328
  let raw;
36329
36329
  try {
36330
- raw = readFileSync46(path2, "utf-8");
36330
+ raw = readFileSync47(path2, "utf-8");
36331
36331
  } catch {
36332
36332
  return null;
36333
36333
  }
@@ -36336,7 +36336,7 @@ function loadSnapshot(path2, now = Date.now) {
36336
36336
  parsed = JSON.parse(raw);
36337
36337
  } catch {
36338
36338
  try {
36339
- renameSync19(path2, `${path2}.corrupt-${now()}`);
36339
+ renameSync20(path2, `${path2}.corrupt-${now()}`);
36340
36340
  } catch {}
36341
36341
  return null;
36342
36342
  }
@@ -36357,10 +36357,10 @@ function loadSnapshot(path2, now = Date.now) {
36357
36357
  }
36358
36358
  function persistSnapshot(path2, snapshot) {
36359
36359
  try {
36360
- mkdirSync39(dirname17(path2), { recursive: true });
36360
+ mkdirSync40(dirname17(path2), { recursive: true });
36361
36361
  const tmp = `${path2}.tmp`;
36362
- writeFileSync38(tmp, JSON.stringify(snapshot), { mode: 384 });
36363
- renameSync19(tmp, path2);
36362
+ writeFileSync39(tmp, JSON.stringify(snapshot), { mode: 384 });
36363
+ renameSync20(tmp, path2);
36364
36364
  } catch {}
36365
36365
  }
36366
36366
  var init_config_snapshot = __esm(() => {
@@ -36368,13 +36368,13 @@ var init_config_snapshot = __esm(() => {
36368
36368
  });
36369
36369
 
36370
36370
  // gateway/boot-card-msgid.ts
36371
- import { readFileSync as readFileSync47, writeFileSync as writeFileSync39 } from "node:fs";
36371
+ import { readFileSync as readFileSync48, writeFileSync as writeFileSync40 } from "node:fs";
36372
36372
  function bootCardChatKey(chatId, threadId) {
36373
36373
  return `${chatId}:${threadId ?? ""}`;
36374
36374
  }
36375
36375
  function readStore(path2) {
36376
36376
  try {
36377
- const parsed = JSON.parse(readFileSync47(path2, "utf8"));
36377
+ const parsed = JSON.parse(readFileSync48(path2, "utf8"));
36378
36378
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
36379
36379
  return parsed;
36380
36380
  }
@@ -36393,7 +36393,7 @@ function saveBootCardMsgId(path2, chatKey3, messageId) {
36393
36393
  if (store2[chatKey3] === messageId)
36394
36394
  return;
36395
36395
  store2[chatKey3] = messageId;
36396
- writeFileSync39(path2, JSON.stringify(store2), "utf8");
36396
+ writeFileSync40(path2, JSON.stringify(store2), "utf8");
36397
36397
  } catch {}
36398
36398
  }
36399
36399
  var init_boot_card_msgid = () => {};
@@ -36411,8 +36411,8 @@ __export(exports_boot_card, {
36411
36411
  readRoutingMode: () => readRoutingMode,
36412
36412
  containerBootStartMs: () => containerBootStartMs
36413
36413
  });
36414
- import { join as join49 } from "path";
36415
- import { readFileSync as readFileSync48, statSync as statSync14 } from "fs";
36414
+ import { join as join50 } from "path";
36415
+ import { readFileSync as readFileSync49, statSync as statSync14 } from "fs";
36416
36416
  function resolvePersonaName(slug, loadConfig3) {
36417
36417
  try {
36418
36418
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -36434,7 +36434,7 @@ function shouldSkipDuplicateBootCard(gate, site) {
36434
36434
  }
36435
36435
  return { skip: false };
36436
36436
  }
36437
- function containerBootStartMs(fsImpl = { readFileSync: readFileSync48 }) {
36437
+ function containerBootStartMs(fsImpl = { readFileSync: readFileSync49 }) {
36438
36438
  try {
36439
36439
  const btimeLine = fsImpl.readFileSync("/proc/stat", "utf-8").split(`
36440
36440
  `).find((l) => l.startsWith("btime "));
@@ -36455,8 +36455,8 @@ function containerBootStartMs(fsImpl = { readFileSync: readFileSync48 }) {
36455
36455
  return null;
36456
36456
  }
36457
36457
  }
36458
- function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync48, statSync: statSync14 }) {
36459
- const path2 = join49(agentDir, ".routing-mode");
36458
+ function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync49, statSync: statSync14 }) {
36459
+ const path2 = join50(agentDir, ".routing-mode");
36460
36460
  try {
36461
36461
  const raw = fsImpl.readFileSync(path2, "utf-8");
36462
36462
  const line = raw.split(`
@@ -36563,7 +36563,7 @@ function renderBootCard(opts) {
36563
36563
  return stackCardLines(flatLines);
36564
36564
  }
36565
36565
  async function runAllProbes(opts) {
36566
- const claudeDir = join49(opts.agentDir, ".claude");
36566
+ const claudeDir = join50(opts.agentDir, ".claude");
36567
36567
  const probes = {};
36568
36568
  const slug = opts.agentSlug ?? opts.agentName;
36569
36569
  await Promise.allSettled([
@@ -37417,7 +37417,7 @@ __export(exports_tmux2, {
37417
37417
  captureAgentPane: () => captureAgentPane2
37418
37418
  });
37419
37419
  import { execFileSync as execFileSync8 } from "node:child_process";
37420
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync47, readdirSync as readdirSync13, statSync as statSync19, unlinkSync as unlinkSync26, writeFileSync as writeFileSync48 } from "node:fs";
37420
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync48, readdirSync as readdirSync13, statSync as statSync19, unlinkSync as unlinkSync26, writeFileSync as writeFileSync49 } from "node:fs";
37421
37421
  import { resolve as resolve12 } from "node:path";
37422
37422
  function captureAgentPane2(opts) {
37423
37423
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -37429,7 +37429,7 @@ function captureAgentPane2(opts) {
37429
37429
  const reasonSlug = sanitizeReason2(reason);
37430
37430
  const outPath = resolve12(outDir, `${ts}-${reasonSlug}.txt`);
37431
37431
  try {
37432
- mkdirSync47(outDir, { recursive: true, mode: 448 });
37432
+ mkdirSync48(outDir, { recursive: true, mode: 448 });
37433
37433
  } catch (err) {
37434
37434
  const msg = `mkdir crash-reports failed: ${err.message}`;
37435
37435
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -37468,7 +37468,7 @@ function captureAgentPane2(opts) {
37468
37468
  ` + `
37469
37469
  `;
37470
37470
  try {
37471
- writeFileSync48(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37471
+ writeFileSync49(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37472
37472
  mode: 384
37473
37473
  });
37474
37474
  } catch (err) {
@@ -38348,7 +38348,7 @@ __export(exports_materialize_bot_token, {
38348
38348
  materializeBotToken: () => materializeBotToken,
38349
38349
  BotTokenMaterializeError: () => BotTokenMaterializeError
38350
38350
  });
38351
- import { existsSync as existsSync54 } from "node:fs";
38351
+ import { existsSync as existsSync55 } from "node:fs";
38352
38352
  function pickConfiguredToken(config, agentName3) {
38353
38353
  if (agentName3) {
38354
38354
  const agent = config.agents?.[agentName3];
@@ -38362,7 +38362,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
38362
38362
  if (!passphrase)
38363
38363
  return null;
38364
38364
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
38365
- if (!existsSync54(vaultPath))
38365
+ if (!existsSync55(vaultPath))
38366
38366
  return null;
38367
38367
  try {
38368
38368
  const secrets = openVault(passphrase, vaultPath);
@@ -38514,26 +38514,26 @@ var init_approvals_commands = __esm(() => {
38514
38514
  // gateway/gateway.ts
38515
38515
  var import_grammy16 = __toESM(require_mod(), 1);
38516
38516
  var import_runner3 = __toESM(require_mod3(), 1);
38517
- import { randomBytes as randomBytes10, createHash as createHash5 } from "crypto";
38517
+ import { randomBytes as randomBytes10, createHash as createHash6 } from "crypto";
38518
38518
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
38519
38519
  import {
38520
- readFileSync as readFileSync59,
38521
- writeFileSync as writeFileSync49,
38522
- mkdirSync as mkdirSync48,
38520
+ readFileSync as readFileSync60,
38521
+ writeFileSync as writeFileSync50,
38522
+ mkdirSync as mkdirSync49,
38523
38523
  readdirSync as readdirSync14,
38524
38524
  rmSync as rmSync6,
38525
38525
  statSync as statSync20,
38526
- renameSync as renameSync22,
38526
+ renameSync as renameSync23,
38527
38527
  realpathSync as realpathSync4,
38528
38528
  chmodSync as chmodSync14,
38529
38529
  openSync as openSync12,
38530
38530
  closeSync as closeSync12,
38531
- existsSync as existsSync55,
38531
+ existsSync as existsSync56,
38532
38532
  unlinkSync as unlinkSync27,
38533
- appendFileSync as appendFileSync8
38533
+ appendFileSync as appendFileSync9
38534
38534
  } from "fs";
38535
38535
  import { homedir as homedir20 } from "os";
38536
- import { join as join61, sep as sep4, basename as basename15 } from "path";
38536
+ import { join as join62, sep as sep4, basename as basename15 } from "path";
38537
38537
 
38538
38538
  // plugin-logger.ts
38539
38539
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -39416,13 +39416,218 @@ class VoiceOnDemandCache {
39416
39416
  }
39417
39417
  }
39418
39418
 
39419
+ // voice-ondemand.ts
39420
+ import { randomBytes as randomBytes2 } from "crypto";
39421
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync3, mkdirSync as mkdirSync3 } from "fs";
39422
+ import { dirname as dirname3 } from "path";
39423
+ var VOICE_ONDEMAND_CALLBACK_PREFIX = "voice:";
39424
+ var VOICE_ONDEMAND_TTL_MS2 = 7 * 24 * 60 * 60 * 1000;
39425
+ var VOICE_ONDEMAND_MAX_ENTRIES2 = 500;
39426
+
39427
+ class VoiceOnDemandCache2 {
39428
+ store = new Map;
39429
+ ttlMs;
39430
+ maxEntries;
39431
+ now;
39432
+ persistPath;
39433
+ constructor(options = {}) {
39434
+ this.ttlMs = options.ttlMs ?? VOICE_ONDEMAND_TTL_MS2;
39435
+ this.maxEntries = options.maxEntries ?? VOICE_ONDEMAND_MAX_ENTRIES2;
39436
+ this.now = options.now ?? Date.now;
39437
+ this.persistPath = options.persistPath;
39438
+ if (this.persistPath !== undefined)
39439
+ this.load();
39440
+ }
39441
+ put(token, payload) {
39442
+ this.store.delete(token);
39443
+ this.store.set(token, {
39444
+ createdAt: this.now(),
39445
+ ...payload,
39446
+ expiresAt: this.now() + this.ttlMs
39447
+ });
39448
+ while (this.store.size > this.maxEntries) {
39449
+ const oldest = this.store.keys().next().value;
39450
+ if (oldest === undefined)
39451
+ break;
39452
+ this.store.delete(oldest);
39453
+ }
39454
+ this.flush();
39455
+ }
39456
+ get(token) {
39457
+ const entry = this.store.get(token);
39458
+ if (entry == null)
39459
+ return null;
39460
+ if (entry.expiresAt <= this.now()) {
39461
+ this.store.delete(token);
39462
+ this.flush();
39463
+ return null;
39464
+ }
39465
+ const { text, voice, speed, filePath, telegramFileId } = entry;
39466
+ return {
39467
+ text,
39468
+ speed,
39469
+ ...voice !== undefined ? { voice } : {},
39470
+ ...filePath !== undefined ? { filePath } : {},
39471
+ ...telegramFileId !== undefined ? { telegramFileId } : {}
39472
+ };
39473
+ }
39474
+ setFilePath(token, filePath) {
39475
+ const entry = this.store.get(token);
39476
+ if (entry == null || entry.expiresAt <= this.now())
39477
+ return;
39478
+ entry.filePath = filePath;
39479
+ this.flush();
39480
+ }
39481
+ setTelegramFileId(token, fileId) {
39482
+ if (fileId.length === 0)
39483
+ return;
39484
+ const entry = this.store.get(token);
39485
+ if (entry == null || entry.expiresAt <= this.now())
39486
+ return;
39487
+ entry.telegramFileId = fileId;
39488
+ this.flush();
39489
+ }
39490
+ prune(tokens) {
39491
+ let changed = false;
39492
+ for (const token of tokens) {
39493
+ if (this.store.delete(token))
39494
+ changed = true;
39495
+ }
39496
+ if (changed)
39497
+ this.flush();
39498
+ }
39499
+ get size() {
39500
+ return this.store.size;
39501
+ }
39502
+ load() {
39503
+ if (this.persistPath === undefined)
39504
+ return;
39505
+ let raw;
39506
+ try {
39507
+ raw = readFileSync2(this.persistPath, "utf8");
39508
+ } catch {
39509
+ return;
39510
+ }
39511
+ try {
39512
+ const parsed = JSON.parse(raw);
39513
+ if (parsed == null || parsed.version !== 1 || typeof parsed.entries !== "object")
39514
+ return;
39515
+ const nowMs = this.now();
39516
+ for (const [token, entry] of Object.entries(parsed.entries)) {
39517
+ if (entry == null || typeof entry.expiresAt !== "number" || typeof entry.text !== "string" || typeof entry.speed !== "number") {
39518
+ continue;
39519
+ }
39520
+ if (entry.expiresAt <= nowMs)
39521
+ continue;
39522
+ this.store.set(token, entry);
39523
+ }
39524
+ while (this.store.size > this.maxEntries) {
39525
+ const oldest = this.store.keys().next().value;
39526
+ if (oldest === undefined)
39527
+ break;
39528
+ this.store.delete(oldest);
39529
+ }
39530
+ } catch (err) {
39531
+ process.stderr.write(`voice-ondemand: failed to parse persisted cache ${this.persistPath}: ${String(err)}
39532
+ `);
39533
+ }
39534
+ }
39535
+ flush() {
39536
+ if (this.persistPath === undefined)
39537
+ return;
39538
+ const doc = {
39539
+ version: 1,
39540
+ entries: Object.fromEntries(this.store)
39541
+ };
39542
+ const tmp = `${this.persistPath}.tmp`;
39543
+ try {
39544
+ mkdirSync3(dirname3(this.persistPath), { recursive: true });
39545
+ writeFileSync2(tmp, JSON.stringify(doc), "utf8");
39546
+ renameSync3(tmp, this.persistPath);
39547
+ } catch (err) {
39548
+ process.stderr.write(`voice-ondemand: failed to persist cache ${this.persistPath}: ${String(err)}
39549
+ `);
39550
+ }
39551
+ }
39552
+ }
39553
+ function mintVoiceOnDemandToken() {
39554
+ return randomBytes2(4).toString("hex");
39555
+ }
39556
+ function isVoiceOnDemandCallback(data) {
39557
+ return data.startsWith(VOICE_ONDEMAND_CALLBACK_PREFIX);
39558
+ }
39559
+ function parseVoiceOnDemandToken(data) {
39560
+ if (!isVoiceOnDemandCallback(data))
39561
+ return null;
39562
+ const token = data.slice(VOICE_ONDEMAND_CALLBACK_PREFIX.length);
39563
+ return token.length > 0 ? token : null;
39564
+ }
39565
+ function buildListenKeyboard(token) {
39566
+ return {
39567
+ inline_keyboard: [
39568
+ [{ text: "\uD83D\uDD0A Listen", callback_data: `${VOICE_ONDEMAND_CALLBACK_PREFIX}${token}` }]
39569
+ ]
39570
+ };
39571
+ }
39572
+ function mayInjectListenButton(rawKeyboard) {
39573
+ if (rawKeyboard == null)
39574
+ return true;
39575
+ return !rawKeyboard.some((row) => Array.isArray(row) && row.length > 0);
39576
+ }
39577
+ function planListenButton(params) {
39578
+ const plan = params.voiceOutPlan;
39579
+ const useOnDemandButton = plan != null && plan.replyMode === "on-demand" && plan.engine === "kokoro";
39580
+ if (!useOnDemandButton)
39581
+ return null;
39582
+ if (!(plan.ttsChunks.length > 0 && plan.ttsChunks[0].length > 0))
39583
+ return null;
39584
+ if (!mayInjectListenButton(params.rawKeyboard))
39585
+ return null;
39586
+ const token = mintVoiceOnDemandToken();
39587
+ return {
39588
+ replyMarkup: buildListenKeyboard(token),
39589
+ token,
39590
+ payload: {
39591
+ text: plan.ttsChunks[0],
39592
+ ...plan.voice != null ? { voice: plan.voice } : {},
39593
+ speed: plan.speed
39594
+ }
39595
+ };
39596
+ }
39597
+
39598
+ // gateway/outbox-listen-markup.ts
39599
+ function makeOutboxListenMarkupResolver(deps) {
39600
+ return (_chatId, _threadId, text) => {
39601
+ const voiceOutPlan = deps.resolveVoiceOutPlan(text);
39602
+ const listenPlan = planListenButton({ voiceOutPlan, rawKeyboard: undefined });
39603
+ if (listenPlan == null)
39604
+ return;
39605
+ const payload = listenPlan.payload;
39606
+ deps.cachePut(listenPlan.token, payload);
39607
+ if (deps.eagerVoiceEnabled()) {
39608
+ deps.enqueuePreSynth({
39609
+ token: listenPlan.token,
39610
+ text: payload.text,
39611
+ ...payload.voice != null ? { voice: payload.voice } : {},
39612
+ speed: payload.speed
39613
+ });
39614
+ }
39615
+ return listenPlan.replyMarkup;
39616
+ };
39617
+ }
39618
+
39419
39619
  // voice-presynth.ts
39420
- import { readdirSync, statSync as statSync2, unlinkSync, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, renameSync as renameSync3 } from "fs";
39620
+ import { readdirSync, statSync as statSync2, unlinkSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, renameSync as renameSync4 } from "fs";
39421
39621
  import { join as join2 } from "path";
39422
39622
  var VOICE_FILE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
39423
39623
  var VOICE_CACHE_MAX_BYTES = 500 * 1024 * 1024;
39424
39624
  var PRESYNTH_MAX_PENDING = 50;
39425
39625
  var VOICE_SWEEP_INTERVAL_MS = 60 * 60 * 1000;
39626
+ function eagerVoiceEnabled() {
39627
+ const kill = process.env.SWITCHROOM_DISABLE_EAGER_VOICE;
39628
+ return !(kill === "1" || kill === "true");
39629
+ }
39630
+
39426
39631
  class PreSynthQueue {
39427
39632
  pending = [];
39428
39633
  running = false;
@@ -39474,11 +39679,11 @@ function voiceCacheFilePath(dir, token) {
39474
39679
  return join2(dir, `${token}.ogg`);
39475
39680
  }
39476
39681
  function writeVoiceCacheFile(dir, token, audio) {
39477
- mkdirSync3(dir, { recursive: true, mode: 448 });
39682
+ mkdirSync4(dir, { recursive: true, mode: 448 });
39478
39683
  const final = voiceCacheFilePath(dir, token);
39479
39684
  const tmp = `${final}.tmp`;
39480
- writeFileSync2(tmp, audio);
39481
- renameSync3(tmp, final);
39685
+ writeFileSync3(tmp, audio);
39686
+ renameSync4(tmp, final);
39482
39687
  return final;
39483
39688
  }
39484
39689
  function sweepVoiceCacheDir(options) {
@@ -39535,6 +39740,49 @@ function sweepVoiceCacheDir(options) {
39535
39740
 
39536
39741
  // voice-normalize-text.ts
39537
39742
  var CODE_BLOCK_PLACEHOLDER = "code block omitted";
39743
+ var HTML_ENTITIES = {
39744
+ amp: "&",
39745
+ lt: "<",
39746
+ gt: ">",
39747
+ quot: '"',
39748
+ apos: "'",
39749
+ nbsp: " "
39750
+ };
39751
+ var METACHAR_SPOKEN = {
39752
+ "#": " hash ",
39753
+ "*": " asterisk ",
39754
+ _: " underscore ",
39755
+ "~": " tilde ",
39756
+ "`": " backtick ",
39757
+ "|": " bar "
39758
+ };
39759
+ function decodeHtmlEntitiesOnce(input) {
39760
+ const toChar = (cp, raw) => {
39761
+ if (!(cp > 0 && cp <= 1114111))
39762
+ return raw;
39763
+ const ch = String.fromCodePoint(cp);
39764
+ return METACHAR_SPOKEN[ch] ?? ch;
39765
+ };
39766
+ return input.replace(/&#x([0-9a-f]+);/gi, (m, hex) => toChar(parseInt(hex, 16), m)).replace(/&#(\d+);/g, (m, dec) => toChar(Number(dec), m)).replace(/&([a-z][a-z0-9]*);/gi, (m, name) => {
39767
+ const ch = HTML_ENTITIES[name.toLowerCase()];
39768
+ if (ch === undefined)
39769
+ return m;
39770
+ return METACHAR_SPOKEN[ch] ?? ch;
39771
+ });
39772
+ }
39773
+ function decodeHtmlEntities(input) {
39774
+ let s = input;
39775
+ for (let i = 0;i < 10; i++) {
39776
+ const next = decodeHtmlEntitiesOnce(s);
39777
+ if (next === s)
39778
+ break;
39779
+ s = next;
39780
+ }
39781
+ return s;
39782
+ }
39783
+ function stripBackslashEscapes(input) {
39784
+ return input.replace(/\\([\s\S])/g, "$1").replace(/\\/g, "");
39785
+ }
39538
39786
  var ONES = [
39539
39787
  "zero",
39540
39788
  "one",
@@ -39701,6 +39949,8 @@ function normalizeForSpeech(input) {
39701
39949
  return "";
39702
39950
  let s = input.replace(/\r\n?/g, `
39703
39951
  `);
39952
+ s = decodeHtmlEntities(s);
39953
+ s = stripBackslashEscapes(s);
39704
39954
  s = s.replace(/[\u{1F000}-\u{1FAFF}\u{1F1E6}-\u{1F1FF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE00}-\u{FE0F}\u{200D}\u{2B50}\u{3030}\u{303D}\u{3297}\u{3299}\u{24C2}]/gu, "");
39705
39955
  s = s.replace(/:([a-z0-9][a-z0-9_+-]*):/gi, " ");
39706
39956
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, `$1${CODE_BLOCK_PLACEHOLDER}.`);
@@ -39800,6 +40050,65 @@ function normalizeForSpeech(input) {
39800
40050
  return s.trim();
39801
40051
  }
39802
40052
 
40053
+ // voice-normalize-text.ts
40054
+ var HTML_ENTITIES2 = {
40055
+ amp: "&",
40056
+ lt: "<",
40057
+ gt: ">",
40058
+ quot: '"',
40059
+ apos: "'",
40060
+ nbsp: " "
40061
+ };
40062
+ var METACHAR_SPOKEN2 = {
40063
+ "#": " hash ",
40064
+ "*": " asterisk ",
40065
+ _: " underscore ",
40066
+ "~": " tilde ",
40067
+ "`": " backtick ",
40068
+ "|": " bar "
40069
+ };
40070
+ function decodeHtmlEntitiesOnce2(input) {
40071
+ const toChar = (cp, raw) => {
40072
+ if (!(cp > 0 && cp <= 1114111))
40073
+ return raw;
40074
+ const ch = String.fromCodePoint(cp);
40075
+ return METACHAR_SPOKEN2[ch] ?? ch;
40076
+ };
40077
+ return input.replace(/&#x([0-9a-f]+);/gi, (m, hex) => toChar(parseInt(hex, 16), m)).replace(/&#(\d+);/g, (m, dec) => toChar(Number(dec), m)).replace(/&([a-z][a-z0-9]*);/gi, (m, name) => {
40078
+ const ch = HTML_ENTITIES2[name.toLowerCase()];
40079
+ if (ch === undefined)
40080
+ return m;
40081
+ return METACHAR_SPOKEN2[ch] ?? ch;
40082
+ });
40083
+ }
40084
+ function decodeHtmlEntities2(input) {
40085
+ let s = input;
40086
+ for (let i = 0;i < 10; i++) {
40087
+ const next = decodeHtmlEntitiesOnce2(s);
40088
+ if (next === s)
40089
+ break;
40090
+ s = next;
40091
+ }
40092
+ return s;
40093
+ }
40094
+ function stripBackslashEscapes2(input) {
40095
+ return input.replace(/\\([\s\S])/g, "$1").replace(/\\/g, "");
40096
+ }
40097
+ var ACRONYMS2 = new Set([
40098
+ "CI",
40099
+ "PR",
40100
+ "API",
40101
+ "URL",
40102
+ "GPU",
40103
+ "CPU",
40104
+ "TTS",
40105
+ "STT",
40106
+ "HTTP",
40107
+ "JSON",
40108
+ "SQL",
40109
+ "UI"
40110
+ ]);
40111
+
39803
40112
  // tts-normalize.ts
39804
40113
  var NULL = "\x00";
39805
40114
  var INLINE_PH = `${NULL}TN_INLINE`;
@@ -39980,6 +40289,8 @@ function normalizeForTts(text) {
39980
40289
  return text;
39981
40290
  let s = text.replace(/\r\n?/g, `
39982
40291
  `);
40292
+ s = decodeHtmlEntities2(s);
40293
+ s = stripBackslashEscapes2(s);
39983
40294
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, "$1code block omitted.");
39984
40295
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*$/g, "$1code block omitted.");
39985
40296
  const { parked, parts } = parkInline(s);
@@ -40819,6 +41130,141 @@ function forwardOriginDateIso(o) {
40819
41130
  return new Date(o.date * 1000).toISOString();
40820
41131
  }
40821
41132
 
41133
+ // gateway/rich-message-handler.ts
41134
+ var MAX_DEPTH = 32;
41135
+ function renderRichText(rt, depth = 0) {
41136
+ if (depth > MAX_DEPTH || rt == null)
41137
+ return "";
41138
+ if (typeof rt === "string")
41139
+ return rt;
41140
+ if (Array.isArray(rt))
41141
+ return rt.map((t) => renderRichText(t, depth + 1)).join("");
41142
+ if (typeof rt !== "object")
41143
+ return "";
41144
+ const node = rt;
41145
+ if (node.type === "custom_emoji")
41146
+ return node.alternative_text ?? "";
41147
+ if (node.type === "mathematical_expression") {
41148
+ return typeof node.expression === "string" ? node.expression : "";
41149
+ }
41150
+ if (node.type === "anchor")
41151
+ return "";
41152
+ if ("text" in node)
41153
+ return renderRichText(node.text, depth + 1);
41154
+ return "";
41155
+ }
41156
+ function renderCaption(caption, depth) {
41157
+ if (caption == null || typeof caption !== "object")
41158
+ return renderRichText(caption, depth);
41159
+ const c = caption;
41160
+ const text = renderRichText(c.text, depth);
41161
+ const credit = renderRichText(c.credit, depth);
41162
+ return [text, credit ? `\u2014 ${credit}` : ""].filter(Boolean).join(" ");
41163
+ }
41164
+ function renderBlocks(blocks, depth) {
41165
+ if (depth > MAX_DEPTH || !Array.isArray(blocks))
41166
+ return "";
41167
+ return blocks.map((b) => renderRichBlock(b, depth + 1)).filter((s) => s.length > 0).join(`
41168
+ `);
41169
+ }
41170
+ function renderRichBlock(block, depth = 0) {
41171
+ if (depth > MAX_DEPTH || block == null || typeof block !== "object")
41172
+ return "";
41173
+ const b = block;
41174
+ switch (b.type) {
41175
+ case "paragraph":
41176
+ case "heading":
41177
+ case "footer":
41178
+ return renderRichText(b.text, depth);
41179
+ case "pre": {
41180
+ const body = renderRichText(b.text, depth);
41181
+ return body.length > 0 ? `\`\`\`${b.language ?? ""}
41182
+ ${body}
41183
+ \`\`\`` : "";
41184
+ }
41185
+ case "divider":
41186
+ return "---";
41187
+ case "mathematical_expression":
41188
+ return typeof b.expression === "string" ? b.expression : "";
41189
+ case "anchor":
41190
+ return "";
41191
+ case "list": {
41192
+ if (!Array.isArray(b.items))
41193
+ return "";
41194
+ return b.items.map((item) => {
41195
+ const body = renderBlocks(item?.blocks, depth);
41196
+ const check = item?.has_checkbox ? item.is_checked ? "[x] " : "[ ] " : "";
41197
+ const label = typeof item?.label === "string" && item.label.length > 0 ? item.label : "-";
41198
+ return body.length > 0 ? `${label} ${check}${body}` : "";
41199
+ }).filter((s) => s.length > 0).join(`
41200
+ `);
41201
+ }
41202
+ case "blockquote": {
41203
+ const body = renderBlocks(b.blocks, depth);
41204
+ const credit = renderRichText(b.credit, depth);
41205
+ const quoted = body.split(`
41206
+ `).map((line) => `> ${line}`).join(`
41207
+ `);
41208
+ return [quoted, credit ? `> \u2014 ${credit}` : ""].filter((s) => s.length > 0).join(`
41209
+ `);
41210
+ }
41211
+ case "pullquote": {
41212
+ const body = renderRichText(b.text, depth);
41213
+ const credit = renderRichText(b.credit, depth);
41214
+ return [body, credit ? `\u2014 ${credit}` : ""].filter(Boolean).join(" ");
41215
+ }
41216
+ case "details": {
41217
+ const summary = renderRichText(b.summary, depth);
41218
+ const body = renderBlocks(b.blocks, depth);
41219
+ return [summary, body].filter((s) => s.length > 0).join(`
41220
+ `);
41221
+ }
41222
+ case "collage":
41223
+ case "slideshow": {
41224
+ const body = renderBlocks(b.blocks, depth);
41225
+ const caption = renderCaption(b.caption, depth);
41226
+ return [body, caption].filter((s) => s.length > 0).join(`
41227
+ `);
41228
+ }
41229
+ case "table": {
41230
+ if (!Array.isArray(b.cells))
41231
+ return "";
41232
+ const rows = b.cells.map((row) => Array.isArray(row) ? row.map((cell) => renderRichText(cell?.text, depth)).join(" | ") : "").filter((s) => s.length > 0).join(`
41233
+ `);
41234
+ const caption = renderRichText(b.caption, depth);
41235
+ return [caption, rows].filter((s) => s.length > 0).join(`
41236
+ `);
41237
+ }
41238
+ case "map":
41239
+ case "animation":
41240
+ case "audio":
41241
+ case "photo":
41242
+ case "video":
41243
+ case "voice_note": {
41244
+ const caption = renderCaption(b.caption, depth);
41245
+ const tag = `[${b.type}]`;
41246
+ return caption.length > 0 ? `${tag} ${caption}` : tag;
41247
+ }
41248
+ case "thinking":
41249
+ return "";
41250
+ default: {
41251
+ const text = renderRichText(b.text, depth);
41252
+ if (text.length > 0)
41253
+ return text;
41254
+ return renderBlocks(b.blocks, depth);
41255
+ }
41256
+ }
41257
+ }
41258
+ function extractRichMessageText(rich) {
41259
+ if (rich == null || typeof rich !== "object")
41260
+ return;
41261
+ const blocks = rich.blocks;
41262
+ const rendered = renderBlocks(blocks, 0).replace(/\n{3,}/g, `
41263
+
41264
+ `).trim();
41265
+ return rendered.length > 0 ? rendered : undefined;
41266
+ }
41267
+
40822
41268
  // gateway/unhandled-message.ts
40823
41269
  var MESSAGE_ENVELOPE_KEYS = new Set([
40824
41270
  "message_id",
@@ -40847,7 +41293,13 @@ var MESSAGE_ENVELOPE_KEYS = new Set([
40847
41293
  "show_caption_above_media",
40848
41294
  "entities",
40849
41295
  "caption_entities",
40850
- "paid_star_count"
41296
+ "paid_star_count",
41297
+ "forward_from",
41298
+ "forward_from_chat",
41299
+ "forward_from_message_id",
41300
+ "forward_signature",
41301
+ "forward_sender_name",
41302
+ "forward_date"
40851
41303
  ]);
40852
41304
  var SERVICE_NOISE_KEYS = new Set([
40853
41305
  "new_chat_members",
@@ -40892,7 +41344,7 @@ function planUnhandledMessage(msg) {
40892
41344
  return { action: "log-only", contentKeys };
40893
41345
  }
40894
41346
  const contentType = contentKeys[0] ?? "unknown";
40895
- const text = (typeof msg.text === "string" ? msg.text : undefined) ?? (typeof msg.caption === "string" ? msg.caption : undefined) ?? `(unhandled message content: ${contentType})`;
41347
+ const text = (typeof msg.text === "string" ? msg.text : undefined) ?? (typeof msg.caption === "string" ? msg.caption : undefined) ?? extractRichMessageText(msg.rich_message) ?? `(unhandled message content: ${contentType})`;
40896
41348
  return { action: "turn", text, contentKeys };
40897
41349
  }
40898
41350
  var TAP_MAX_LINES_PER_MINUTE = 300;
@@ -41091,6 +41543,153 @@ async function handlePassportDataMessage(ctx, deps) {
41091
41543
  await deps.handleRefusal(ctx, "passport_data", PASSPORT_REFUSAL_TEXT);
41092
41544
  }
41093
41545
 
41546
+ // gateway/rich-message-handler.ts
41547
+ var RICH_MESSAGE_EMPTY_TEXT = "(rich message with no extractable text)";
41548
+ var MAX_DEPTH2 = 32;
41549
+ function renderRichText2(rt, depth = 0) {
41550
+ if (depth > MAX_DEPTH2 || rt == null)
41551
+ return "";
41552
+ if (typeof rt === "string")
41553
+ return rt;
41554
+ if (Array.isArray(rt))
41555
+ return rt.map((t) => renderRichText2(t, depth + 1)).join("");
41556
+ if (typeof rt !== "object")
41557
+ return "";
41558
+ const node = rt;
41559
+ if (node.type === "custom_emoji")
41560
+ return node.alternative_text ?? "";
41561
+ if (node.type === "mathematical_expression") {
41562
+ return typeof node.expression === "string" ? node.expression : "";
41563
+ }
41564
+ if (node.type === "anchor")
41565
+ return "";
41566
+ if ("text" in node)
41567
+ return renderRichText2(node.text, depth + 1);
41568
+ return "";
41569
+ }
41570
+ function renderCaption2(caption, depth) {
41571
+ if (caption == null || typeof caption !== "object")
41572
+ return renderRichText2(caption, depth);
41573
+ const c = caption;
41574
+ const text = renderRichText2(c.text, depth);
41575
+ const credit = renderRichText2(c.credit, depth);
41576
+ return [text, credit ? `\u2014 ${credit}` : ""].filter(Boolean).join(" ");
41577
+ }
41578
+ function renderBlocks2(blocks, depth) {
41579
+ if (depth > MAX_DEPTH2 || !Array.isArray(blocks))
41580
+ return "";
41581
+ return blocks.map((b) => renderRichBlock2(b, depth + 1)).filter((s) => s.length > 0).join(`
41582
+ `);
41583
+ }
41584
+ function renderRichBlock2(block, depth = 0) {
41585
+ if (depth > MAX_DEPTH2 || block == null || typeof block !== "object")
41586
+ return "";
41587
+ const b = block;
41588
+ switch (b.type) {
41589
+ case "paragraph":
41590
+ case "heading":
41591
+ case "footer":
41592
+ return renderRichText2(b.text, depth);
41593
+ case "pre": {
41594
+ const body = renderRichText2(b.text, depth);
41595
+ return body.length > 0 ? `\`\`\`${b.language ?? ""}
41596
+ ${body}
41597
+ \`\`\`` : "";
41598
+ }
41599
+ case "divider":
41600
+ return "---";
41601
+ case "mathematical_expression":
41602
+ return typeof b.expression === "string" ? b.expression : "";
41603
+ case "anchor":
41604
+ return "";
41605
+ case "list": {
41606
+ if (!Array.isArray(b.items))
41607
+ return "";
41608
+ return b.items.map((item) => {
41609
+ const body = renderBlocks2(item?.blocks, depth);
41610
+ const check = item?.has_checkbox ? item.is_checked ? "[x] " : "[ ] " : "";
41611
+ const label = typeof item?.label === "string" && item.label.length > 0 ? item.label : "-";
41612
+ return body.length > 0 ? `${label} ${check}${body}` : "";
41613
+ }).filter((s) => s.length > 0).join(`
41614
+ `);
41615
+ }
41616
+ case "blockquote": {
41617
+ const body = renderBlocks2(b.blocks, depth);
41618
+ const credit = renderRichText2(b.credit, depth);
41619
+ const quoted = body.split(`
41620
+ `).map((line) => `> ${line}`).join(`
41621
+ `);
41622
+ return [quoted, credit ? `> \u2014 ${credit}` : ""].filter((s) => s.length > 0).join(`
41623
+ `);
41624
+ }
41625
+ case "pullquote": {
41626
+ const body = renderRichText2(b.text, depth);
41627
+ const credit = renderRichText2(b.credit, depth);
41628
+ return [body, credit ? `\u2014 ${credit}` : ""].filter(Boolean).join(" ");
41629
+ }
41630
+ case "details": {
41631
+ const summary = renderRichText2(b.summary, depth);
41632
+ const body = renderBlocks2(b.blocks, depth);
41633
+ return [summary, body].filter((s) => s.length > 0).join(`
41634
+ `);
41635
+ }
41636
+ case "collage":
41637
+ case "slideshow": {
41638
+ const body = renderBlocks2(b.blocks, depth);
41639
+ const caption = renderCaption2(b.caption, depth);
41640
+ return [body, caption].filter((s) => s.length > 0).join(`
41641
+ `);
41642
+ }
41643
+ case "table": {
41644
+ if (!Array.isArray(b.cells))
41645
+ return "";
41646
+ const rows = b.cells.map((row) => Array.isArray(row) ? row.map((cell) => renderRichText2(cell?.text, depth)).join(" | ") : "").filter((s) => s.length > 0).join(`
41647
+ `);
41648
+ const caption = renderRichText2(b.caption, depth);
41649
+ return [caption, rows].filter((s) => s.length > 0).join(`
41650
+ `);
41651
+ }
41652
+ case "map":
41653
+ case "animation":
41654
+ case "audio":
41655
+ case "photo":
41656
+ case "video":
41657
+ case "voice_note": {
41658
+ const caption = renderCaption2(b.caption, depth);
41659
+ const tag = `[${b.type}]`;
41660
+ return caption.length > 0 ? `${tag} ${caption}` : tag;
41661
+ }
41662
+ case "thinking":
41663
+ return "";
41664
+ default: {
41665
+ const text = renderRichText2(b.text, depth);
41666
+ if (text.length > 0)
41667
+ return text;
41668
+ return renderBlocks2(b.blocks, depth);
41669
+ }
41670
+ }
41671
+ }
41672
+ function extractRichMessageText2(rich) {
41673
+ if (rich == null || typeof rich !== "object")
41674
+ return;
41675
+ const blocks = rich.blocks;
41676
+ const rendered = renderBlocks2(blocks, 0).replace(/\n{3,}/g, `
41677
+
41678
+ `).trim();
41679
+ return rendered.length > 0 ? rendered : undefined;
41680
+ }
41681
+ async function handleRichMessageMessage(ctx, deps) {
41682
+ try {
41683
+ const text = extractRichMessageText2(ctx.message.rich_message) ?? RICH_MESSAGE_EMPTY_TEXT;
41684
+ deps.log(`telegram gateway: inbound rich_message from chat=${ctx.chat?.id ?? "?"} chars=${text.length}
41685
+ `);
41686
+ await deps.handleInbound(ctx, text, undefined);
41687
+ } catch (err) {
41688
+ deps.log(`telegram gateway: rich_message handler error: ${err.message}
41689
+ `);
41690
+ }
41691
+ }
41692
+
41094
41693
  // gateway/stop-command.ts
41095
41694
  function parseStopKeyword2(text) {
41096
41695
  return /^stop[.!]?$/i.test(text.trim());
@@ -42712,6 +43311,8 @@ function buildForwardOriginMeta(origins) {
42712
43311
  if (o.date != null) {
42713
43312
  out[`forwarded_date${suffix}`] = fmtLocalStamp(o.date * 1000, resolveEnvTimezone());
42714
43313
  }
43314
+ if (o.messageId != null)
43315
+ out[`forwarded_message_id${suffix}`] = String(o.messageId);
42715
43316
  });
42716
43317
  return out;
42717
43318
  }
@@ -43339,7 +43940,7 @@ async function handleAnimationMessage(ctx, deps) {
43339
43940
  }
43340
43941
 
43341
43942
  // gateway/photo-message-handler.ts
43342
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
43943
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
43343
43944
 
43344
43945
  // attachment-path.ts
43345
43946
  import { join as join4, basename as basename3, resolve as resolve2, sep } from "node:path";
@@ -43400,9 +44001,9 @@ async function handlePhotoMessage(ctx, deps) {
43400
44001
  fileUniqueId: best.file_unique_id,
43401
44002
  now: Date.now()
43402
44003
  });
43403
- mkdirSync6(deps.inboxDir, { recursive: true, mode: 448 });
44004
+ mkdirSync7(deps.inboxDir, { recursive: true, mode: 448 });
43404
44005
  assertInsideInbox(deps.inboxDir, dlPath);
43405
- writeFileSync5(dlPath, buf, { mode: 384 });
44006
+ writeFileSync6(dlPath, buf, { mode: 384 });
43406
44007
  return dlPath;
43407
44008
  } catch (err) {
43408
44009
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -44470,165 +45071,6 @@ async function handleAskCallback(ctx, data, deps) {
44470
45071
  // gateway/voice-ondemand-callback-handler.ts
44471
45072
  import { readFileSync as readFileSync8 } from "node:fs";
44472
45073
 
44473
- // voice-ondemand.ts
44474
- import { randomBytes as randomBytes2 } from "crypto";
44475
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync6, renameSync as renameSync5, mkdirSync as mkdirSync7 } from "fs";
44476
- import { dirname as dirname3 } from "path";
44477
- var VOICE_ONDEMAND_CALLBACK_PREFIX = "voice:";
44478
- var VOICE_ONDEMAND_TTL_MS2 = 7 * 24 * 60 * 60 * 1000;
44479
- var VOICE_ONDEMAND_MAX_ENTRIES2 = 500;
44480
-
44481
- class VoiceOnDemandCache2 {
44482
- store = new Map;
44483
- ttlMs;
44484
- maxEntries;
44485
- now;
44486
- persistPath;
44487
- constructor(options = {}) {
44488
- this.ttlMs = options.ttlMs ?? VOICE_ONDEMAND_TTL_MS2;
44489
- this.maxEntries = options.maxEntries ?? VOICE_ONDEMAND_MAX_ENTRIES2;
44490
- this.now = options.now ?? Date.now;
44491
- this.persistPath = options.persistPath;
44492
- if (this.persistPath !== undefined)
44493
- this.load();
44494
- }
44495
- put(token, payload) {
44496
- this.store.delete(token);
44497
- this.store.set(token, {
44498
- createdAt: this.now(),
44499
- ...payload,
44500
- expiresAt: this.now() + this.ttlMs
44501
- });
44502
- while (this.store.size > this.maxEntries) {
44503
- const oldest = this.store.keys().next().value;
44504
- if (oldest === undefined)
44505
- break;
44506
- this.store.delete(oldest);
44507
- }
44508
- this.flush();
44509
- }
44510
- get(token) {
44511
- const entry = this.store.get(token);
44512
- if (entry == null)
44513
- return null;
44514
- if (entry.expiresAt <= this.now()) {
44515
- this.store.delete(token);
44516
- this.flush();
44517
- return null;
44518
- }
44519
- const { text, voice, speed, filePath, telegramFileId } = entry;
44520
- return {
44521
- text,
44522
- speed,
44523
- ...voice !== undefined ? { voice } : {},
44524
- ...filePath !== undefined ? { filePath } : {},
44525
- ...telegramFileId !== undefined ? { telegramFileId } : {}
44526
- };
44527
- }
44528
- setFilePath(token, filePath) {
44529
- const entry = this.store.get(token);
44530
- if (entry == null || entry.expiresAt <= this.now())
44531
- return;
44532
- entry.filePath = filePath;
44533
- this.flush();
44534
- }
44535
- setTelegramFileId(token, fileId) {
44536
- if (fileId.length === 0)
44537
- return;
44538
- const entry = this.store.get(token);
44539
- if (entry == null || entry.expiresAt <= this.now())
44540
- return;
44541
- entry.telegramFileId = fileId;
44542
- this.flush();
44543
- }
44544
- prune(tokens) {
44545
- let changed = false;
44546
- for (const token of tokens) {
44547
- if (this.store.delete(token))
44548
- changed = true;
44549
- }
44550
- if (changed)
44551
- this.flush();
44552
- }
44553
- get size() {
44554
- return this.store.size;
44555
- }
44556
- load() {
44557
- if (this.persistPath === undefined)
44558
- return;
44559
- let raw;
44560
- try {
44561
- raw = readFileSync4(this.persistPath, "utf8");
44562
- } catch {
44563
- return;
44564
- }
44565
- try {
44566
- const parsed = JSON.parse(raw);
44567
- if (parsed == null || parsed.version !== 1 || typeof parsed.entries !== "object")
44568
- return;
44569
- const nowMs = this.now();
44570
- for (const [token, entry] of Object.entries(parsed.entries)) {
44571
- if (entry == null || typeof entry.expiresAt !== "number" || typeof entry.text !== "string" || typeof entry.speed !== "number") {
44572
- continue;
44573
- }
44574
- if (entry.expiresAt <= nowMs)
44575
- continue;
44576
- this.store.set(token, entry);
44577
- }
44578
- while (this.store.size > this.maxEntries) {
44579
- const oldest = this.store.keys().next().value;
44580
- if (oldest === undefined)
44581
- break;
44582
- this.store.delete(oldest);
44583
- }
44584
- } catch (err) {
44585
- process.stderr.write(`voice-ondemand: failed to parse persisted cache ${this.persistPath}: ${String(err)}
44586
- `);
44587
- }
44588
- }
44589
- flush() {
44590
- if (this.persistPath === undefined)
44591
- return;
44592
- const doc = {
44593
- version: 1,
44594
- entries: Object.fromEntries(this.store)
44595
- };
44596
- const tmp = `${this.persistPath}.tmp`;
44597
- try {
44598
- mkdirSync7(dirname3(this.persistPath), { recursive: true });
44599
- writeFileSync6(tmp, JSON.stringify(doc), "utf8");
44600
- renameSync5(tmp, this.persistPath);
44601
- } catch (err) {
44602
- process.stderr.write(`voice-ondemand: failed to persist cache ${this.persistPath}: ${String(err)}
44603
- `);
44604
- }
44605
- }
44606
- }
44607
- function mintVoiceOnDemandToken() {
44608
- return randomBytes2(4).toString("hex");
44609
- }
44610
- function isVoiceOnDemandCallback(data) {
44611
- return data.startsWith(VOICE_ONDEMAND_CALLBACK_PREFIX);
44612
- }
44613
- function parseVoiceOnDemandToken(data) {
44614
- if (!isVoiceOnDemandCallback(data))
44615
- return null;
44616
- const token = data.slice(VOICE_ONDEMAND_CALLBACK_PREFIX.length);
44617
- return token.length > 0 ? token : null;
44618
- }
44619
- function buildListenKeyboard(token) {
44620
- return {
44621
- inline_keyboard: [
44622
- [{ text: "\uD83D\uDD0A Listen", callback_data: `${VOICE_ONDEMAND_CALLBACK_PREFIX}${token}` }]
44623
- ]
44624
- };
44625
- }
44626
- function mayInjectListenButton(rawKeyboard) {
44627
- if (rawKeyboard == null)
44628
- return true;
44629
- return !rawKeyboard.some((row) => Array.isArray(row) && row.length > 0);
44630
- }
44631
-
44632
45074
  // voice-send.ts
44633
45075
  function extractVoiceFileId(sent) {
44634
45076
  const id = sent?.voice?.file_id;
@@ -44941,6 +45383,8 @@ function normalizeForTts2(text) {
44941
45383
  return text;
44942
45384
  let s = text.replace(/\r\n?/g, `
44943
45385
  `);
45386
+ s = decodeHtmlEntities2(s);
45387
+ s = stripBackslashEscapes2(s);
44944
45388
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, "$1code block omitted.");
44945
45389
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*$/g, "$1code block omitted.");
44946
45390
  const { parked, parts } = parkInline2(s);
@@ -51276,202 +51720,6 @@ function createSessionModelSource(options = {}) {
51276
51720
  };
51277
51721
  }
51278
51722
 
51279
- // tool-labels.ts
51280
- var MAX_LABEL_CHARS = 60;
51281
- var MAX_BASH_CHARS = 40;
51282
- var MAX_DESCRIPTION_CHARS = 160;
51283
- function basename6(p) {
51284
- if (!p)
51285
- return "";
51286
- const parts = p.split("/").filter(Boolean);
51287
- return parts.length > 0 ? parts[parts.length - 1] : p;
51288
- }
51289
- function shortenGrepPath(p) {
51290
- if (!p)
51291
- return "repo";
51292
- const hadTrailingSlash = /\/+$/.test(p);
51293
- const trimmed = p.replace(/\/+$/, "");
51294
- const parts = trimmed.split("/").filter(Boolean);
51295
- if (parts.length === 0)
51296
- return "repo";
51297
- const last = parts[parts.length - 1];
51298
- if (hadTrailingSlash)
51299
- return `${last}/`;
51300
- if (last.startsWith(".") && !last.slice(1).includes("."))
51301
- return last;
51302
- if (!last.includes("."))
51303
- return `${last}/`;
51304
- return last;
51305
- }
51306
- function hostFromUrl(u) {
51307
- if (!u)
51308
- return "";
51309
- try {
51310
- return new URL(u).host;
51311
- } catch {
51312
- return truncate3(u);
51313
- }
51314
- }
51315
- function truncate3(s, n = MAX_LABEL_CHARS) {
51316
- if (s.length <= n)
51317
- return s;
51318
- return s.slice(0, n - 1) + "\u2026";
51319
- }
51320
- function stripHtml(s) {
51321
- return s.replace(/<\/?[a-zA-Z][^>]*>/g, "");
51322
- }
51323
- function firstLine(s) {
51324
- const idx = s.indexOf(`
51325
- `);
51326
- return idx === -1 ? s : s.slice(0, idx);
51327
- }
51328
- function toolLabel(tool, input, preamble, precomputedLabel) {
51329
- if (precomputedLabel && precomputedLabel.trim().length > 0) {
51330
- return truncate3(firstLine(precomputedLabel.trim()), MAX_DESCRIPTION_CHARS);
51331
- }
51332
- if (!input || typeof input !== "object")
51333
- return "";
51334
- const str = (k) => typeof input[k] === "string" ? input[k] : undefined;
51335
- const preambleLabel = () => {
51336
- if (!preamble)
51337
- return null;
51338
- if (preamble.includes(`
51339
- `))
51340
- return null;
51341
- const trimmed = preamble.trim();
51342
- if (!trimmed)
51343
- return null;
51344
- if (trimmed.length > MAX_DESCRIPTION_CHARS)
51345
- return null;
51346
- return trimmed;
51347
- };
51348
- switch (tool) {
51349
- case "Read":
51350
- case "Write":
51351
- case "NotebookEdit":
51352
- case "Edit": {
51353
- const pre = preambleLabel();
51354
- if (pre)
51355
- return pre;
51356
- return truncate3(basename6(str("file_path") ?? ""));
51357
- }
51358
- case "Bash":
51359
- case "BashOutput": {
51360
- const description = str("description");
51361
- if (description)
51362
- return truncate3(firstLine(description), MAX_DESCRIPTION_CHARS);
51363
- const pre = preambleLabel();
51364
- if (pre)
51365
- return pre;
51366
- const cmd = str("command") ?? str("bash_id") ?? "";
51367
- return truncate3(firstLine(cmd), MAX_BASH_CHARS);
51368
- }
51369
- case "KillShell":
51370
- return truncate3(str("shell_id") ?? "");
51371
- case "Glob": {
51372
- const pre = preambleLabel();
51373
- if (pre)
51374
- return pre;
51375
- return truncate3(str("pattern") ?? "");
51376
- }
51377
- case "Grep": {
51378
- const pre = preambleLabel();
51379
- if (pre)
51380
- return pre;
51381
- const pat = str("pattern") ?? "";
51382
- if (!pat)
51383
- return "";
51384
- const path = str("path");
51385
- const where = shortenGrepPath(path ?? "");
51386
- return truncate3(`"${pat}" (in ${where})`);
51387
- }
51388
- case "WebFetch":
51389
- return truncate3(hostFromUrl(str("url") ?? ""));
51390
- case "WebSearch": {
51391
- const q = str("query") ?? "";
51392
- return q ? truncate3(`"${q}"`) : "";
51393
- }
51394
- case "Task":
51395
- case "Agent": {
51396
- const desc = str("description") ?? str("subagent_type") ?? "";
51397
- return truncate3(desc);
51398
- }
51399
- case "TodoWrite":
51400
- case "TaskCreate":
51401
- case "TaskUpdate":
51402
- case "TaskList":
51403
- case "TaskGet":
51404
- case "TaskStop":
51405
- case "TaskOutput":
51406
- return "";
51407
- case "Skill":
51408
- return truncate3(str("skill") ?? "");
51409
- case "SlashCommand":
51410
- return truncate3(str("command") ?? "");
51411
- case "ToolSearch": {
51412
- const q = str("query") ?? "";
51413
- if (!q)
51414
- return "";
51415
- const selectMatch = q.match(/^\s*select\s*:\s*(.+)$/i);
51416
- if (selectMatch) {
51417
- const names = selectMatch[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0).join(", ");
51418
- return truncate3(`Loading schema: ${names}`);
51419
- }
51420
- return truncate3(`Searching tools: ${q}`);
51421
- }
51422
- default:
51423
- if (tool.startsWith("mcp__")) {
51424
- const description = str("description");
51425
- if (description)
51426
- return truncate3(firstLine(stripHtml(description)), MAX_DESCRIPTION_CHARS);
51427
- const label = mcpBaseLabel(tool);
51428
- const query = str("query") ?? str("text") ?? str("name");
51429
- if (label && query) {
51430
- const budget = Math.max(8, MAX_LABEL_CHARS - label.length - 4);
51431
- const preview = truncate3(firstLine(stripHtml(query)), budget);
51432
- return `${label} (${preview})`;
51433
- }
51434
- if (label)
51435
- return truncate3(label);
51436
- }
51437
- for (const k of ["description", "file_path", "path", "url", "query", "pattern", "command"]) {
51438
- const v = str(k);
51439
- if (v != null && v.length > 0) {
51440
- if (k === "file_path" || k === "path")
51441
- return truncate3(basename6(v));
51442
- if (k === "url")
51443
- return truncate3(hostFromUrl(v));
51444
- if (k === "description")
51445
- return truncate3(firstLine(v), MAX_DESCRIPTION_CHARS);
51446
- return truncate3(firstLine(v));
51447
- }
51448
- }
51449
- return "";
51450
- }
51451
- }
51452
- function mcpBaseLabel(tool) {
51453
- if (!tool.startsWith("mcp__"))
51454
- return "";
51455
- const parts = tool.slice("mcp__".length).split("__");
51456
- if (parts.length < 2)
51457
- return "";
51458
- const rawServer = parts[0];
51459
- const action = parts.slice(1).join("__");
51460
- if (!rawServer || !action)
51461
- return "";
51462
- return `${prettifyServer(rawServer)}: ${action}`;
51463
- }
51464
- function prettifyServer(name) {
51465
- const LABELS = {
51466
- "switchroom-telegram": "Telegram"
51467
- };
51468
- if (LABELS[name])
51469
- return LABELS[name];
51470
- if (!name)
51471
- return name;
51472
- return name.charAt(0).toUpperCase() + name.slice(1);
51473
- }
51474
-
51475
51723
  // typing-wrap.ts
51476
51724
  function createTypingWrapper(deps) {
51477
51725
  const debounceMs = deps.debounceMs ?? 500;
@@ -61243,7 +61491,7 @@ function prefixLines(text4, prefix) {
61243
61491
  `);
61244
61492
  }
61245
61493
  function renderBlockquote(node2) {
61246
- const inner = renderBlocks(node2.children);
61494
+ const inner = renderBlocks3(node2.children);
61247
61495
  if (node2.expandable) {
61248
61496
  const lines = inner.split(`
61249
61497
  `);
@@ -61339,7 +61587,7 @@ function renderBlock(node2) {
61339
61587
  function renderBlocksJoined(blocks, sep2) {
61340
61588
  return blocks.map(renderBlock).join(sep2);
61341
61589
  }
61342
- function renderBlocks(blocks) {
61590
+ function renderBlocks3(blocks) {
61343
61591
  return renderBlocksJoined(blocks, `
61344
61592
 
61345
61593
  `);
@@ -62963,7 +63211,7 @@ function loadInitialFloodWindows(floodStateFilePath, floodWindowsFilePath, now)
62963
63211
  }
62964
63212
 
62965
63213
  // attachment-path.ts
62966
- import { join as join17, basename as basename7, resolve as resolve6, sep as sep2 } from "node:path";
63214
+ import { join as join17, basename as basename6, resolve as resolve6, sep as sep2 } from "node:path";
62967
63215
  function sanitizeExtension2(ext) {
62968
63216
  if (ext == null)
62969
63217
  return "bin";
@@ -62992,7 +63240,7 @@ function assertInsideInbox2(inboxDir, candidatePath) {
62992
63240
  const inboxReal = resolve6(inboxDir);
62993
63241
  const candidateReal = resolve6(candidatePath);
62994
63242
  if (candidateReal !== inboxReal && !candidateReal.startsWith(inboxReal + sep2)) {
62995
- throw new Error(`attachment path escape: ${basename7(candidatePath)} resolved outside ${inboxDir}`);
63243
+ throw new Error(`attachment path escape: ${basename6(candidatePath)} resolved outside ${inboxDir}`);
62996
63244
  }
62997
63245
  }
62998
63246
 
@@ -67805,6 +68053,8 @@ __export(exports_silence_poke, {
67805
68053
  noteThinking: () => noteThinking,
67806
68054
  noteProduction: () => noteProduction,
67807
68055
  noteOutbound: () => noteOutbound2,
68056
+ noteBackgroundShellDead: () => noteBackgroundShellDead,
68057
+ noteBackgroundShellAlive: () => noteBackgroundShellAlive,
67808
68058
  longestInFlightTool: () => longestInFlightTool,
67809
68059
  formatFrameworkFallbackText: () => formatFrameworkFallbackText,
67810
68060
  endTurn: () => endTurn,
@@ -67812,6 +68062,7 @@ __export(exports_silence_poke, {
67812
68062
  __setDepsForTests: () => __setDepsForTests,
67813
68063
  __resetAllForTests: () => __resetAllForTests2,
67814
68064
  __getStateForTests: () => __getStateForTests,
68065
+ __bgMarkerParserConfirmedForTests: () => __bgMarkerParserConfirmedForTests,
67815
68066
  DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS,
67816
68067
  DEFAULT_POLL_INTERVAL_MS: () => DEFAULT_POLL_INTERVAL_MS
67817
68068
  });
@@ -67882,6 +68133,7 @@ var DEFAULT_POLL_INTERVAL_MS = 5000;
67882
68133
  var state2 = new Map;
67883
68134
  var timer = null;
67884
68135
  var activeDeps = null;
68136
+ var bgMarkerParserConfirmed = false;
67885
68137
  function silencePokeEnabled() {
67886
68138
  const v = process.env.SWITCHROOM_DISABLE_SILENCE_POKE;
67887
68139
  return !(v === "1" || v === "true");
@@ -67895,9 +68147,24 @@ function startTurn(key, now) {
67895
68147
  lastThinkingAt: null,
67896
68148
  fallbackFired: false,
67897
68149
  floorFired: false,
67898
- inFlightTools: new Map
68150
+ inFlightTools: new Map,
68151
+ sawBashThisTurn: false,
68152
+ aliveShells: new Set
67899
68153
  });
67900
68154
  }
68155
+ function noteBackgroundShellAlive(key, shellId) {
68156
+ bgMarkerParserConfirmed = true;
68157
+ const s = state2.get(key);
68158
+ if (s == null)
68159
+ return;
68160
+ s.aliveShells.add(shellId);
68161
+ }
68162
+ function noteBackgroundShellDead(key, shellId) {
68163
+ const s = state2.get(key);
68164
+ if (s == null)
68165
+ return;
68166
+ s.aliveShells.delete(shellId);
68167
+ }
67901
68168
  function noteOutbound2(key, now) {
67902
68169
  const s = state2.get(key);
67903
68170
  if (s == null)
@@ -67923,6 +68190,8 @@ function noteToolStart(key, toolUseId, name, label, now) {
67923
68190
  if (s == null)
67924
68191
  return;
67925
68192
  s.inFlightTools.set(toolUseId, { name, startedAt: now, label });
68193
+ if (name === "Bash")
68194
+ s.sawBashThisTurn = true;
67926
68195
  }
67927
68196
  function noteToolEnd(key, toolUseId, _now) {
67928
68197
  const s = state2.get(key);
@@ -68037,6 +68306,10 @@ function tick(now) {
68037
68306
  if (!forceDisable && activeDeps.isLegitimatelyWorking != null) {
68038
68307
  if (activeDeps.isLegitimatelyWorking(key))
68039
68308
  continue;
68309
+ if (s.aliveShells.size > 0)
68310
+ continue;
68311
+ if (!bgMarkerParserConfirmed && s.sawBashThisTurn)
68312
+ continue;
68040
68313
  } else if (!forceDisable && activeDeps.deferFallbackWhileToolInFlight === true && s.inFlightTools.size > 0) {
68041
68314
  continue;
68042
68315
  }
@@ -68119,6 +68392,10 @@ function __getStateForTests(key) {
68119
68392
  function __resetAllForTests2() {
68120
68393
  state2.clear();
68121
68394
  stopTimer();
68395
+ bgMarkerParserConfirmed = false;
68396
+ }
68397
+ function __bgMarkerParserConfirmedForTests() {
68398
+ return bgMarkerParserConfirmed;
68122
68399
  }
68123
68400
 
68124
68401
  // pending-work-progress.ts
@@ -68491,7 +68768,7 @@ function resolveAnswerLaneConfig(input) {
68491
68768
 
68492
68769
  // session-tail.ts
68493
68770
  import { homedir as homedir7 } from "os";
68494
- import { basename as basename8, join as join23 } from "path";
68771
+ import { basename as basename7, join as join23 } from "path";
68495
68772
 
68496
68773
  // operator-events.ts
68497
68774
  init_format();
@@ -68928,6 +69205,29 @@ function extractToolResultErrorText(content3) {
68928
69205
  }
68929
69206
  return "";
68930
69207
  }
69208
+ function parseBackgroundTaskId(obj) {
69209
+ const tur = obj.toolUseResult;
69210
+ if (typeof tur === "object" && tur != null) {
69211
+ const id = tur.backgroundTaskId;
69212
+ if (typeof id === "string" && id.length > 0)
69213
+ return id;
69214
+ }
69215
+ return null;
69216
+ }
69217
+ function parseBackgroundLaunchString(content3) {
69218
+ const text4 = typeof content3 === "string" ? content3 : extractToolResultErrorText(content3);
69219
+ const m = text4.match(/Command running in background with ID: (\w+)/);
69220
+ return m != null ? m[1] : null;
69221
+ }
69222
+ function parseTaskNotification(content3) {
69223
+ if (!content3.includes("<task-notification>"))
69224
+ return null;
69225
+ const idM = content3.match(/<task-id>([^<]+)<\/task-id>/);
69226
+ const stM = content3.match(/<status>([^<]+)<\/status>/);
69227
+ if (idM == null || stM == null)
69228
+ return null;
69229
+ return { taskId: idM[1].trim(), status: stM[1].trim() };
69230
+ }
68931
69231
  function projectAssistantTextBlocks(content3, make) {
68932
69232
  const out = new Map;
68933
69233
  let lastToolUseIdx = -1;
@@ -69024,6 +69324,10 @@ function projectTranscriptLine(line) {
69024
69324
  const op = obj.operation;
69025
69325
  if (op === "enqueue") {
69026
69326
  const content3 = obj.content ?? "";
69327
+ const notif = parseTaskNotification(content3);
69328
+ if (notif != null) {
69329
+ return [{ kind: "task_notification", taskId: notif.taskId, status: notif.status }];
69330
+ }
69027
69331
  const { chatId, messageId, threadId } = parseChannelMeta(content3);
69028
69332
  return [{ kind: "enqueue", chatId, messageId, threadId, rawContent: content3 }];
69029
69333
  }
@@ -69081,6 +69385,7 @@ function projectTranscriptLine(line) {
69081
69385
  const content3 = message?.content;
69082
69386
  if (!Array.isArray(content3))
69083
69387
  return [];
69388
+ const backgroundTaskId = parseBackgroundTaskId(obj);
69084
69389
  const events = [];
69085
69390
  for (const c of content3) {
69086
69391
  if (c.type === "tool_result") {
@@ -69090,7 +69395,8 @@ function projectTranscriptLine(line) {
69090
69395
  toolUseId: c.tool_use_id ?? "",
69091
69396
  toolName: null,
69092
69397
  isError: isError2,
69093
- errorText: isError2 ? extractToolResultErrorText(c.content) : undefined
69398
+ errorText: isError2 ? extractToolResultErrorText(c.content) : undefined,
69399
+ backgroundTaskId: backgroundTaskId ?? parseBackgroundLaunchString(c.content) ?? undefined
69094
69400
  });
69095
69401
  }
69096
69402
  }
@@ -74010,6 +74316,33 @@ function readDeliveredNonces(stateDir) {
74010
74316
  } catch {}
74011
74317
  return set;
74012
74318
  }
74319
+ function isBackstopDeliveredEntry(e) {
74320
+ if (e.deliverySource === "sweep" || e.deliverySource === "flush")
74321
+ return true;
74322
+ if (e.deliverySource === "reply-tool" && e.replyAlreadyDeliveredThisTurn === false)
74323
+ return true;
74324
+ return false;
74325
+ }
74326
+ function backstopAlreadyDelivered(nonce, stateDir) {
74327
+ if (nonce == null || nonce === "")
74328
+ return false;
74329
+ const path2 = join28(resolveOutboxDir(stateDir), JOURNAL_FILE);
74330
+ if (!existsSync24(path2))
74331
+ return false;
74332
+ try {
74333
+ for (const line of readFileSync24(path2, "utf8").split(`
74334
+ `)) {
74335
+ if (!line)
74336
+ continue;
74337
+ try {
74338
+ const e = JSON.parse(line);
74339
+ if (e.turnNonce === nonce && isBackstopDeliveredEntry(e))
74340
+ return true;
74341
+ } catch {}
74342
+ }
74343
+ } catch {}
74344
+ return false;
74345
+ }
74013
74346
  var JOURNAL_KEEP = 2000;
74014
74347
  var JOURNAL_ROTATE_AT = 4000;
74015
74348
  function appendDelivered(entry, stateDir) {
@@ -74053,10 +74386,13 @@ function decideOutboxSweep(input) {
74053
74386
  routable,
74054
74387
  routePrefix = "",
74055
74388
  quietMs = OUTBOX_QUIET_MS,
74056
- maxAgeMs = OUTBOX_MAX_AGE_MS
74389
+ maxAgeMs = OUTBOX_MAX_AGE_MS,
74390
+ shownLedgerHit = false
74057
74391
  } = input;
74058
74392
  if (deliveredNonces.has(record.turnNonce))
74059
74393
  return { action: "skip-journaled" };
74394
+ if (shownLedgerHit)
74395
+ return { action: "skip-ephemeral-shown" };
74060
74396
  const age = now - record.createdAt;
74061
74397
  if (age < quietMs)
74062
74398
  return { action: "skip-quiet" };
@@ -74089,6 +74425,149 @@ function extractTaskId(anchorContent) {
74089
74425
  return m ? m[1] : null;
74090
74426
  }
74091
74427
 
74428
+ // shown-ledger.ts
74429
+ import {
74430
+ existsSync as existsSync25,
74431
+ mkdirSync as mkdirSync26,
74432
+ readFileSync as readFileSync25,
74433
+ renameSync as renameSync9,
74434
+ writeFileSync as writeFileSync23,
74435
+ appendFileSync as appendFileSync5
74436
+ } from "node:fs";
74437
+ import { join as join29 } from "node:path";
74438
+
74439
+ // hooks/narration-classify.mjs
74440
+ import { createHash as createHash4 } from "node:crypto";
74441
+ var SUBSTANTIVE_MIN_CHARS = 200;
74442
+ var NARRATION_OPENER = /^(let me\b|lemme\b|i'?ll\b|i will\b|i am going to\b|i'?m going to\b|i'?m about to\b|going to\b|first,?\s+(?:let me|i'?ll|i will)\b|now,?\s+(?:let me|i'?ll|i will)\b|next,?\s+(?:let me|i'?ll|i will)\b|let'?s\b)/i;
74443
+ var NARRATION_TRAILER = /(?:\.{3}|\u2026|:)\s*$/;
74444
+ function isTrailingNarrationLine(block) {
74445
+ const t = typeof block === "string" ? block.trim() : "";
74446
+ if (t.length === 0 || t.length >= SUBSTANTIVE_MIN_CHARS)
74447
+ return false;
74448
+ if (t.includes(`
74449
+ `))
74450
+ return false;
74451
+ return NARRATION_TRAILER.test(t);
74452
+ }
74453
+ function isNarrationBlock(block) {
74454
+ const s = typeof block === "string" ? block : "";
74455
+ return NARRATION_OPENER.test(s.trimStart()) || isTrailingNarrationLine(s);
74456
+ }
74457
+ function isStructuralNarration(text4, followedByToolUse) {
74458
+ if (followedByToolUse !== true)
74459
+ return false;
74460
+ const t = typeof text4 === "string" ? text4.trim() : "";
74461
+ if (t.length === 0)
74462
+ return true;
74463
+ return isNarrationBlock(t) || t.length < SUBSTANTIVE_MIN_CHARS;
74464
+ }
74465
+ var EPHEMERAL_TOOLS = new Set([
74466
+ "react",
74467
+ "send_typing",
74468
+ "pin_message",
74469
+ "delete_message",
74470
+ "edit_message"
74471
+ ]);
74472
+ function isEphemeralTool(name) {
74473
+ if (typeof name !== "string")
74474
+ return false;
74475
+ const suffix = name.replace(/^mcp__[^_].*?telegram__/, "");
74476
+ return EPHEMERAL_TOOLS.has(suffix);
74477
+ }
74478
+ function selectBackstopDelivery(blocks) {
74479
+ const nonEmpty = (Array.isArray(blocks) ? blocks : []).map((b) => ({
74480
+ text: typeof b?.text === "string" ? b.text.trim() : "",
74481
+ followedByToolUse: b?.followedByToolUse
74482
+ })).filter((b) => b.text.length > 0);
74483
+ if (nonEmpty.length === 0)
74484
+ return null;
74485
+ const run3 = [];
74486
+ for (let i = nonEmpty.length - 1;i >= 0; i--) {
74487
+ if (nonEmpty[i].followedByToolUse === true)
74488
+ break;
74489
+ run3.unshift(nonEmpty[i].text);
74490
+ }
74491
+ if (run3.length > 0)
74492
+ return { text: run3.join(`
74493
+
74494
+ `) };
74495
+ const last = nonEmpty[nonEmpty.length - 1];
74496
+ if (last.text.length >= SUBSTANTIVE_MIN_CHARS)
74497
+ return { text: last.text };
74498
+ return null;
74499
+ }
74500
+ function ledgerHashHex(text4) {
74501
+ return createHash4("sha256").update(String(text4 ?? "").trim(), "utf8").digest("hex");
74502
+ }
74503
+
74504
+ // shown-ledger.ts
74505
+ var SHOWN_LEDGER_FILE = "shown-ledger.jsonl";
74506
+ var SHOWN_LEDGER_KEEP = 2000;
74507
+ var SHOWN_LEDGER_ROTATE_AT = 4000;
74508
+ function shownLedgerPath(stateDir) {
74509
+ return join29(resolveOutboxDir(stateDir), SHOWN_LEDGER_FILE);
74510
+ }
74511
+ function appendShownBlock(turnNonce, text4, stateDir, now = Date.now()) {
74512
+ if (turnNonce == null || turnNonce === "")
74513
+ return;
74514
+ const trimmed = typeof text4 === "string" ? text4.trim() : "";
74515
+ if (trimmed.length === 0)
74516
+ return;
74517
+ const dir = resolveOutboxDir(stateDir);
74518
+ const path2 = shownLedgerPath(stateDir);
74519
+ try {
74520
+ mkdirSync26(dir, { recursive: true });
74521
+ const entry = { turnNonce, hash: ledgerHashHex(trimmed), ts: now };
74522
+ appendFileSync5(path2, JSON.stringify(entry) + `
74523
+ `, { mode: 384 });
74524
+ compactIfLarge(path2);
74525
+ } catch {}
74526
+ }
74527
+ function readShownHashes(turnNonce, stateDir) {
74528
+ const set = new Set;
74529
+ if (turnNonce == null || turnNonce === "")
74530
+ return set;
74531
+ const path2 = shownLedgerPath(stateDir);
74532
+ if (!existsSync25(path2))
74533
+ return set;
74534
+ try {
74535
+ for (const line of readFileSync25(path2, "utf8").split(`
74536
+ `)) {
74537
+ if (!line)
74538
+ continue;
74539
+ try {
74540
+ const e = JSON.parse(line);
74541
+ if (e.turnNonce === turnNonce && typeof e.hash === "string")
74542
+ set.add(e.hash);
74543
+ } catch {}
74544
+ }
74545
+ } catch {}
74546
+ return set;
74547
+ }
74548
+ function isShownBlock(turnNonce, text4, stateDir) {
74549
+ if (turnNonce == null || turnNonce === "")
74550
+ return false;
74551
+ const trimmed = typeof text4 === "string" ? text4.trim() : "";
74552
+ if (trimmed.length === 0)
74553
+ return false;
74554
+ return readShownHashes(turnNonce, stateDir).has(ledgerHashHex(trimmed));
74555
+ }
74556
+ function compactIfLarge(path2) {
74557
+ try {
74558
+ const lines = readFileSync25(path2, "utf8").split(`
74559
+ `).filter((l) => l.length > 0);
74560
+ if (lines.length <= SHOWN_LEDGER_ROTATE_AT)
74561
+ return;
74562
+ const kept = lines.slice(lines.length - SHOWN_LEDGER_KEEP);
74563
+ const tmp = `${path2}.${process.pid}.compact`;
74564
+ writeFileSync23(tmp, kept.join(`
74565
+ `) + `
74566
+ `, { mode: 384 });
74567
+ renameSync9(tmp, path2);
74568
+ } catch {}
74569
+ }
74570
+
74092
74571
  // registry/subagents-schema.ts
74093
74572
  function countRunningBackgroundSubagents(db2) {
74094
74573
  const row = db2.prepare("SELECT count(*) AS n FROM subagents WHERE background = 1 AND status = 'running'").get();
@@ -74211,7 +74690,7 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
74211
74690
  textSha256: sha256Hex(args.text),
74212
74691
  tgMessageId: args.tgMessageId,
74213
74692
  ts: now,
74214
- deliverySource: "reply-tool",
74693
+ deliverySource: args.deliverySource ?? "reply-tool",
74215
74694
  ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn }
74216
74695
  }, stateDir);
74217
74696
  clearOutboxRecord(nonce, stateDir);
@@ -74445,7 +74924,7 @@ function deriveTelegraphTitle2(text4) {
74445
74924
  var VOICE_FILE_TTL_MS2 = 7 * 24 * 60 * 60 * 1000;
74446
74925
  var VOICE_CACHE_MAX_BYTES2 = 500 * 1024 * 1024;
74447
74926
  var VOICE_SWEEP_INTERVAL_MS2 = 60 * 60 * 1000;
74448
- function eagerVoiceEnabled() {
74927
+ function eagerVoiceEnabled2() {
74449
74928
  const kill = process.env.SWITCHROOM_DISABLE_EAGER_VOICE;
74450
74929
  return !(kill === "1" || kill === "true");
74451
74930
  }
@@ -74606,8 +75085,8 @@ function monotonicNowMs2() {
74606
75085
  }
74607
75086
 
74608
75087
  // silent-end.ts
74609
- import { existsSync as existsSync25, readFileSync as readFileSync25, writeFileSync as writeFileSync23, unlinkSync as unlinkSync13, mkdirSync as mkdirSync26 } from "node:fs";
74610
- import { dirname as dirname11, join as join29 } from "node:path";
75088
+ import { existsSync as existsSync26, readFileSync as readFileSync26, writeFileSync as writeFileSync24, unlinkSync as unlinkSync13, mkdirSync as mkdirSync27 } from "node:fs";
75089
+ import { dirname as dirname11, join as join30 } from "node:path";
74611
75090
  import { homedir as homedir11 } from "node:os";
74612
75091
  var SILENT_END_MAX_RETRIES2 = 2;
74613
75092
  var SILENT_END_STALE_RECORD_MAX_AGE_MS2 = 30 * 60000;
@@ -74622,10 +75101,10 @@ function resolveStateDir3(deps) {
74622
75101
  if (env != null && env !== "")
74623
75102
  return env;
74624
75103
  const home2 = process.env.HOME ?? homedir11();
74625
- return join29(home2, ".claude", "channels", "telegram");
75104
+ return join30(home2, ".claude", "channels", "telegram");
74626
75105
  }
74627
75106
  function resolveStatePath3(deps) {
74628
- return join29(resolveStateDir3(deps), "silent-end-pending.json");
75107
+ return join30(resolveStateDir3(deps), "silent-end-pending.json");
74629
75108
  }
74630
75109
  function emitLog2(deps, line) {
74631
75110
  if (deps?.log != null)
@@ -74637,8 +75116,8 @@ function writeSilentEndState2(args, deps) {
74637
75116
  const statePath = resolveStatePath3(deps);
74638
75117
  let retryCount = 0;
74639
75118
  try {
74640
- if (existsSync25(statePath)) {
74641
- const prev = JSON.parse(readFileSync25(statePath, "utf8"));
75119
+ if (existsSync26(statePath)) {
75120
+ const prev = JSON.parse(readFileSync26(statePath, "utf8"));
74642
75121
  if (prev.turnKey === args.turnKey && typeof prev.retryCount === "number") {
74643
75122
  retryCount = prev.retryCount;
74644
75123
  }
@@ -74654,8 +75133,8 @@ function writeSilentEndState2(args, deps) {
74654
75133
  timestamp: Date.now()
74655
75134
  };
74656
75135
  try {
74657
- mkdirSync26(dirname11(statePath), { recursive: true });
74658
- writeFileSync23(statePath, JSON.stringify(state3), "utf8");
75136
+ mkdirSync27(dirname11(statePath), { recursive: true });
75137
+ writeFileSync24(statePath, JSON.stringify(state3), "utf8");
74659
75138
  emitLog2(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
74660
75139
  `);
74661
75140
  } catch (err) {
@@ -74665,10 +75144,10 @@ function writeSilentEndState2(args, deps) {
74665
75144
  }
74666
75145
  function clearSilentEndState2(turnKey3, deps) {
74667
75146
  const statePath = resolveStatePath3(deps);
74668
- if (!existsSync25(statePath))
75147
+ if (!existsSync26(statePath))
74669
75148
  return;
74670
75149
  try {
74671
- const prev = JSON.parse(readFileSync25(statePath, "utf8"));
75150
+ const prev = JSON.parse(readFileSync26(statePath, "utf8"));
74672
75151
  if (prev.turnKey != null && prev.turnKey !== turnKey3)
74673
75152
  return;
74674
75153
  unlinkSync13(statePath);
@@ -74690,6 +75169,12 @@ function decideCapturedProseDelivery(args, deps) {
74690
75169
  const text4 = typeof state3.pendingText === "string" ? state3.pendingText : "";
74691
75170
  if (text4.trim().length < minChars)
74692
75171
  return { deliver: false, reason: "no-substantive-prose" };
75172
+ if (deps?.isBlockShown?.(state3.turnId, text4) === true) {
75173
+ return { deliver: false, reason: "ephemeral-shown" };
75174
+ }
75175
+ if (deps?.backstopDeliveredNonceHit?.(state3.turnId) === true) {
75176
+ return { deliver: false, reason: "already-delivered" };
75177
+ }
74693
75178
  return { deliver: true, text: text4, reason: "captured-prose" };
74694
75179
  }
74695
75180
  function settleCapturedProseDelivery(outcome, effects) {
@@ -74702,10 +75187,10 @@ function settleCapturedProseDelivery(outcome, effects) {
74702
75187
  }
74703
75188
  function readSilentEndState2(deps) {
74704
75189
  const statePath = resolveStatePath3(deps);
74705
- if (!existsSync25(statePath))
75190
+ if (!existsSync26(statePath))
74706
75191
  return null;
74707
75192
  try {
74708
- return JSON.parse(readFileSync25(statePath, "utf8"));
75193
+ return JSON.parse(readFileSync26(statePath, "utf8"));
74709
75194
  } catch {
74710
75195
  return null;
74711
75196
  }
@@ -75229,24 +75714,23 @@ ${url}`;
75229
75714
  replyButtonMeta = extractAgentButtonMeta(redactedKeyboard);
75230
75715
  replyMarkup = { inline_keyboard: wrapAgentCallbacks(redactedKeyboard) };
75231
75716
  }
75232
- if (useOnDemandButton && voiceOutPlan.ttsChunks.length > 0 && voiceOutPlan.ttsChunks[0].length > 0) {
75233
- if (!mayInjectListenButton(rawKeyboard)) {
75234
- process.stderr.write(`telegram gateway: voice-out on-demand: agent supplied inline_keyboard \u2014 skipping Listen button (single_use collision gate)
75717
+ if (useOnDemandButton) {
75718
+ const listenPlan = planListenButton({ voiceOutPlan, rawKeyboard });
75719
+ if (listenPlan == null) {
75720
+ const hasSpeakableText = voiceOutPlan.ttsChunks.length > 0 && voiceOutPlan.ttsChunks[0].length > 0;
75721
+ if (hasSpeakableText && !mayInjectListenButton(rawKeyboard)) {
75722
+ process.stderr.write(`telegram gateway: voice-out on-demand: agent supplied inline_keyboard \u2014 skipping Listen button (single_use collision gate)
75235
75723
  `);
75724
+ }
75236
75725
  } else {
75237
- const token = mintVoiceOnDemandToken();
75238
- voiceOnDemandCache.put(token, {
75239
- text: voiceOutPlan.ttsChunks[0],
75240
- ...voiceOutPlan.voice != null ? { voice: voiceOutPlan.voice } : {},
75241
- speed: voiceOutPlan.speed
75242
- });
75243
- replyMarkup = buildListenKeyboard(token);
75244
- if (eagerVoiceEnabled()) {
75726
+ voiceOnDemandCache.put(listenPlan.token, listenPlan.payload);
75727
+ replyMarkup = listenPlan.replyMarkup;
75728
+ if (eagerVoiceEnabled2()) {
75245
75729
  voicePreSynthQueue.enqueue({
75246
- token,
75247
- text: voiceOutPlan.ttsChunks[0],
75248
- ...voiceOutPlan.voice != null ? { voice: voiceOutPlan.voice } : {},
75249
- speed: voiceOutPlan.speed
75730
+ token: listenPlan.token,
75731
+ text: listenPlan.payload.text,
75732
+ ...listenPlan.payload.voice != null ? { voice: listenPlan.payload.voice } : {},
75733
+ speed: listenPlan.payload.speed
75250
75734
  });
75251
75735
  }
75252
75736
  }
@@ -75825,34 +76309,6 @@ function endsWithSilentMarker(text4) {
75825
76309
  return false;
75826
76310
  return isSilentFlushMarker(lines[lines.length - 1]);
75827
76311
  }
75828
- var FLUSH_SUBSTANTIVE_MIN_CHARS = 200;
75829
- function selectFlushDeliveryText(blocks, followedByToolUse) {
75830
- const candidates = blocks.map((b, i) => ({ text: b.trim(), followedByToolUse: followedByToolUse?.[i] })).filter((c) => c.text.length > 0);
75831
- if (candidates.length === 0)
75832
- return "";
75833
- if (candidates.length === 1)
75834
- return candidates[0].text;
75835
- const answer = candidates[candidates.length - 1].text;
75836
- const preceding = candidates.slice(0, -1);
75837
- const allNarration = preceding.every((c) => c.followedByToolUse === true ? isNarrationBlock(c.text) || c.text.trim().length < FLUSH_SUBSTANTIVE_MIN_CHARS : c.followedByToolUse === false ? false : isNarrationBlock(c.text));
75838
- return allNarration ? answer : candidates.map((c) => c.text).join(`
75839
-
75840
- `);
75841
- }
75842
- var NARRATION_OPENER = /^(let me\b|lemme\b|i'?ll\b|i will\b|i am going to\b|i'?m going to\b|i'?m about to\b|going to\b|first,?\s+(?:let me|i'?ll|i will)\b|now,?\s+(?:let me|i'?ll|i will)\b|next,?\s+(?:let me|i'?ll|i will)\b|let'?s\b)/i;
75843
- var NARRATION_TRAILER = /(?:\.{3}|\u2026|:)\s*$/;
75844
- function isTrailingNarrationLine(block) {
75845
- const t = block.trim();
75846
- if (t.length === 0 || t.length >= FLUSH_SUBSTANTIVE_MIN_CHARS)
75847
- return false;
75848
- if (t.includes(`
75849
- `))
75850
- return false;
75851
- return NARRATION_TRAILER.test(t);
75852
- }
75853
- function isNarrationBlock(block) {
75854
- return NARRATION_OPENER.test(block.trimStart()) || isTrailingNarrationLine(block);
75855
- }
75856
76312
  function decideTurnFlush(input) {
75857
76313
  const flushEnabled = input.flushEnabled !== false;
75858
76314
  if (!flushEnabled)
@@ -75872,10 +76328,14 @@ function decideTurnFlush(input) {
75872
76328
  return { kind: "skip", reason: "silent-marker" };
75873
76329
  if (endsWithSilentMarker(joined))
75874
76330
  return { kind: "skip", reason: "silent-marker" };
75875
- return {
75876
- kind: "flush",
75877
- text: selectFlushDeliveryText(input.capturedText, input.capturedBlockMeta)
75878
- };
76331
+ const selected = selectBackstopDelivery(input.capturedText.map((text4, i) => ({
76332
+ text: text4,
76333
+ followedByToolUse: input.capturedBlockMeta?.[i]
76334
+ })));
76335
+ if (selected == null || selected.text.trim().length === 0) {
76336
+ return { kind: "skip", reason: "empty-text" };
76337
+ }
76338
+ return { kind: "flush", text: selected.text };
75879
76339
  }
75880
76340
 
75881
76341
  // answer-stream.ts
@@ -76265,20 +76725,20 @@ var INTERRUPTED_VIA = new Set([
76265
76725
  init_rich_send();
76266
76726
 
76267
76727
  // runtime-metrics.ts
76268
- import { mkdirSync as mkdirSync28, appendFileSync as appendFileSync5 } from "node:fs";
76269
- import { dirname as dirname12, join as join31 } from "node:path";
76728
+ import { mkdirSync as mkdirSync29, appendFileSync as appendFileSync6 } from "node:fs";
76729
+ import { dirname as dirname12, join as join32 } from "node:path";
76270
76730
  function resolveJsonlPath2() {
76271
76731
  const override = process.env.SWITCHROOM_RUNTIME_METRICS_PATH;
76272
76732
  if (override && override.trim() !== "")
76273
76733
  return override.trim();
76274
76734
  const base = process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent";
76275
- return join31(base, "runtime-metrics.jsonl");
76735
+ return join32(base, "runtime-metrics.jsonl");
76276
76736
  }
76277
76737
  function appendJsonl2(line) {
76278
76738
  const path2 = resolveJsonlPath2();
76279
76739
  try {
76280
- mkdirSync28(dirname12(path2), { recursive: true });
76281
- appendFileSync5(path2, line + `
76740
+ mkdirSync29(dirname12(path2), { recursive: true });
76741
+ appendFileSync6(path2, line + `
76282
76742
  `, "utf-8");
76283
76743
  } catch (err) {
76284
76744
  process.stderr.write(`runtime-metrics: jsonl write failed: ${err.message}
@@ -76768,28 +77228,28 @@ function detectStatusSurfaceDegraded(t) {
76768
77228
  // gateway/turn-active-marker.ts
76769
77229
  import {
76770
77230
  closeSync as closeSync5,
76771
- existsSync as existsSync28,
76772
- mkdirSync as mkdirSync29,
77231
+ existsSync as existsSync29,
77232
+ mkdirSync as mkdirSync30,
76773
77233
  openSync as openSync5,
76774
- readFileSync as readFileSync26,
77234
+ readFileSync as readFileSync27,
76775
77235
  statSync as statSync11,
76776
77236
  unlinkSync as unlinkSync14,
76777
77237
  utimesSync,
76778
- writeFileSync as writeFileSync24
77238
+ writeFileSync as writeFileSync25
76779
77239
  } from "node:fs";
76780
- import { join as join32 } from "node:path";
77240
+ import { join as join33 } from "node:path";
76781
77241
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
76782
77242
  var TURN_ACTIVE_HARD_TTL_MS = 10 * 60000;
76783
77243
  function writeTurnActiveMarker(stateDir, marker) {
76784
77244
  try {
76785
- mkdirSync29(stateDir, { recursive: true });
76786
- writeFileSync24(join32(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
77245
+ mkdirSync30(stateDir, { recursive: true });
77246
+ writeFileSync25(join33(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
76787
77247
  `, { mode: 384 });
76788
77248
  } catch {}
76789
77249
  }
76790
77250
  function touchTurnActiveMarker(stateDir) {
76791
- const path2 = join32(stateDir, TURN_ACTIVE_MARKER_FILE);
76792
- if (!existsSync28(path2))
77251
+ const path2 = join33(stateDir, TURN_ACTIVE_MARKER_FILE);
77252
+ if (!existsSync29(path2))
76793
77253
  return;
76794
77254
  const now = new Date;
76795
77255
  try {
@@ -76803,11 +77263,11 @@ function touchTurnActiveMarker(stateDir) {
76803
77263
  }
76804
77264
  function removeTurnActiveMarker(stateDir) {
76805
77265
  try {
76806
- unlinkSync14(join32(stateDir, TURN_ACTIVE_MARKER_FILE));
77266
+ unlinkSync14(join33(stateDir, TURN_ACTIVE_MARKER_FILE));
76807
77267
  } catch {}
76808
77268
  }
76809
77269
  function readTurnActiveMarkerAgeMs(stateDir, now) {
76810
- const path2 = join32(stateDir, TURN_ACTIVE_MARKER_FILE);
77270
+ const path2 = join33(stateDir, TURN_ACTIVE_MARKER_FILE);
76811
77271
  try {
76812
77272
  const st = statSync11(path2);
76813
77273
  return (now ?? Date.now()) - st.mtimeMs;
@@ -77229,7 +77689,10 @@ function handleSessionEvent(deps, ev) {
77229
77689
  const turn = getCurrentTurn();
77230
77690
  if (turn == null)
77231
77691
  return;
77232
- clearAnswerReadyFlushTimeout(turn);
77692
+ if (!isEphemeralTool(ev.toolName)) {
77693
+ turn.capturedBlockMeta.fill(true);
77694
+ clearAnswerReadyFlushTimeout(turn);
77695
+ }
77233
77696
  resolvePendingNarrativeOnTool(turn, ev.toolName, ev.input);
77234
77697
  turn.toolCallCount++;
77235
77698
  touchTurnActiveMarker(STATE_DIR);
@@ -77264,7 +77727,9 @@ function handleSessionEvent(deps, ev) {
77264
77727
  const turn = getCurrentTurn();
77265
77728
  if (turn == null)
77266
77729
  return;
77267
- clearAnswerReadyFlushTimeout(turn);
77730
+ if (!isEphemeralTool(ev.toolName)) {
77731
+ clearAnswerReadyFlushTimeout(turn);
77732
+ }
77268
77733
  resetOrphanedReplyTimeout();
77269
77734
  if (isTelegramSurfaceTool(ev.toolName))
77270
77735
  return;
@@ -77642,6 +78107,11 @@ function handleSessionEvent(deps, ev) {
77642
78107
  }
77643
78108
  } catch {}
77644
78109
  }
78110
+ if (backstopAlreadyDelivered(turn.turnId, STATE_DIR)) {
78111
+ process.stderr.write(`telegram gateway: turn-flush skipped \u2014 turn ${turn.turnId} already delivered by a prior backstop (durable journal)
78112
+ `);
78113
+ return;
78114
+ }
77645
78115
  if (!backstopLatchClaimed) {
77646
78116
  process.stderr.write(`telegram gateway: turn-flush skipped \u2014 turn ${turn.turnId} already claimed the delivery latch
77647
78117
  `);
@@ -77704,6 +78174,19 @@ function handleSessionEvent(deps, ev) {
77704
78174
  chunkCount,
77705
78175
  cardMessageId: backstopCardMessageId
77706
78176
  });
78177
+ if (delivered) {
78178
+ try {
78179
+ journalExternalDelivery({
78180
+ turnNonce: turn.turnId,
78181
+ text: capturedText,
78182
+ tgMessageId: sentIds.length > 0 ? sentIds[0] : undefined,
78183
+ deliverySource: "flush"
78184
+ }, STATE_DIR);
78185
+ } catch (err) {
78186
+ process.stderr.write(`telegram gateway: turn-flush delivered but journal write failed (non-fatal): ${err.message}
78187
+ `);
78188
+ }
78189
+ }
77707
78190
  if (OBLIGATION_LEDGER_ENABLED) {
77708
78191
  if (delivered) {
77709
78192
  obligationLedger.close(turn.turnId);
@@ -77764,6 +78247,9 @@ function handleSessionEvent(deps, ev) {
77764
78247
  turnKey: tKey,
77765
78248
  turnId: turn.turnId,
77766
78249
  minChars: proseMinChars
78250
+ }, {
78251
+ isBlockShown: (nonce, text4) => isShownBlock(nonce ?? null, text4),
78252
+ backstopDeliveredNonceHit: (nonce) => backstopAlreadyDelivered(nonce ?? "", STATE_DIR)
77767
78253
  }) : { deliver: false, reason: "no-state" };
77768
78254
  if (proseDecision.deliver && proseDecision.text != null) {
77769
78255
  process.stderr.write(`telegram gateway: captured-prose delivery engaged on first silent-end chat=${chatId} turnKey=${tKey} (#3227)
@@ -77903,6 +78389,7 @@ class NarrativeFlushController {
77903
78389
  this.scheduler.disarm();
77904
78390
  if (this.pending != null) {
77905
78391
  this.effects.show(this.pending);
78392
+ this.markShown(this.pending);
77906
78393
  }
77907
78394
  this.pending = text4;
77908
78395
  this.scheduler.arm(() => this.onTimerFire(), this.flushMs);
@@ -77927,6 +78414,7 @@ class NarrativeFlushController {
77927
78414
  if (replyText != null && isDraftOfReply(pending, replyText))
77928
78415
  return;
77929
78416
  this.effects.show(pending);
78417
+ this.markShown(pending);
77930
78418
  }
77931
78419
  flushAtTurnEnd(lastReplyText) {
77932
78420
  this.scheduler.disarm();
@@ -77945,6 +78433,13 @@ class NarrativeFlushController {
77945
78433
  this.pending = null;
77946
78434
  this.timerShown = null;
77947
78435
  }
78436
+ markShown(text4) {
78437
+ if (this.effects.markDurableNarration == null)
78438
+ return;
78439
+ if (!isStructuralNarration(text4, true))
78440
+ return;
78441
+ this.effects.markDurableNarration(text4);
78442
+ }
77948
78443
  maybeRetract(replyText) {
77949
78444
  const shown = this.timerShown;
77950
78445
  if (shown == null)
@@ -78096,7 +78591,12 @@ function createNarrativeLane(deps) {
78096
78591
  let handle = null;
78097
78592
  return new NarrativeFlushController({
78098
78593
  show: (text4) => showNarrativeStep(turn, text4),
78099
- retractShown: (text4) => retractNarrativeLine(turn, text4)
78594
+ retractShown: (text4) => retractNarrativeLine(turn, text4),
78595
+ markDurableNarration: (text4) => {
78596
+ if (turn.turnId == null)
78597
+ return;
78598
+ appendShownBlock(turn.turnId, text4);
78599
+ }
78100
78600
  }, {
78101
78601
  arm: (fn, ms) => {
78102
78602
  if (handle != null)
@@ -78694,19 +79194,19 @@ function resolveAgentDirFromEnv() {
78694
79194
  }
78695
79195
 
78696
79196
  // active-reactions.ts
78697
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync25, renameSync as renameSync9, existsSync as existsSync29, unlinkSync as unlinkSync15 } from "node:fs";
78698
- import { join as join33 } from "node:path";
79197
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync26, renameSync as renameSync10, existsSync as existsSync30, unlinkSync as unlinkSync15 } from "node:fs";
79198
+ import { join as join34 } from "node:path";
78699
79199
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
78700
79200
  function reactionsPath(agentDir) {
78701
- return join33(agentDir, ACTIVE_REACTIONS_FILENAME);
79201
+ return join34(agentDir, ACTIVE_REACTIONS_FILENAME);
78702
79202
  }
78703
79203
  function readActiveReactions(agentDir) {
78704
79204
  const p = reactionsPath(agentDir);
78705
- if (!existsSync29(p))
79205
+ if (!existsSync30(p))
78706
79206
  return [];
78707
79207
  let raw;
78708
79208
  try {
78709
- raw = readFileSync27(p, "utf-8");
79209
+ raw = readFileSync28(p, "utf-8");
78710
79210
  } catch {
78711
79211
  return [];
78712
79212
  }
@@ -78738,9 +79238,9 @@ function writeActiveReactions(agentDir, reactions) {
78738
79238
  }
78739
79239
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
78740
79240
  try {
78741
- writeFileSync25(tmp, JSON.stringify(reactions) + `
79241
+ writeFileSync26(tmp, JSON.stringify(reactions) + `
78742
79242
  `, "utf-8");
78743
- renameSync9(tmp, p);
79243
+ renameSync10(tmp, p);
78744
79244
  } catch {}
78745
79245
  }
78746
79246
  function addActiveReaction(agentDir, reaction) {
@@ -78762,19 +79262,19 @@ function clearActiveReactions(agentDir) {
78762
79262
  }
78763
79263
 
78764
79264
  // active-reactions.ts
78765
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync26, renameSync as renameSync10, existsSync as existsSync30, unlinkSync as unlinkSync16 } from "node:fs";
78766
- import { join as join34 } from "node:path";
79265
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync27, renameSync as renameSync11, existsSync as existsSync31, unlinkSync as unlinkSync16 } from "node:fs";
79266
+ import { join as join35 } from "node:path";
78767
79267
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
78768
79268
  function reactionsPath2(agentDir) {
78769
- return join34(agentDir, ACTIVE_REACTIONS_FILENAME2);
79269
+ return join35(agentDir, ACTIVE_REACTIONS_FILENAME2);
78770
79270
  }
78771
79271
  function readActiveReactions2(agentDir) {
78772
79272
  const p = reactionsPath2(agentDir);
78773
- if (!existsSync30(p))
79273
+ if (!existsSync31(p))
78774
79274
  return [];
78775
79275
  let raw;
78776
79276
  try {
78777
- raw = readFileSync28(p, "utf-8");
79277
+ raw = readFileSync29(p, "utf-8");
78778
79278
  } catch {
78779
79279
  return [];
78780
79280
  }
@@ -79642,17 +80142,17 @@ async function approvalRecord(args, opts) {
79642
80142
  }
79643
80143
 
79644
80144
  // quota-check.ts
79645
- import { readFileSync as readFileSync29, existsSync as existsSync31 } from "fs";
79646
- import { join as join35 } from "path";
80145
+ import { readFileSync as readFileSync30, existsSync as existsSync32 } from "fs";
80146
+ import { join as join36 } from "path";
79647
80147
  var OAUTH_BETA2 = "oauth-2025-04-20";
79648
80148
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
79649
80149
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
79650
80150
  function readOauthToken2(claudeConfigDir) {
79651
- const tokenFile = join35(claudeConfigDir, ".oauth-token");
79652
- if (!existsSync31(tokenFile))
80151
+ const tokenFile = join36(claudeConfigDir, ".oauth-token");
80152
+ if (!existsSync32(tokenFile))
79653
80153
  return null;
79654
80154
  try {
79655
- const raw = readFileSync29(tokenFile, "utf-8").trim();
80155
+ const raw = readFileSync30(tokenFile, "utf-8").trim();
79656
80156
  return raw.length > 0 ? raw : null;
79657
80157
  } catch {
79658
80158
  return null;
@@ -80988,15 +81488,15 @@ async function menuWithBannerStatic(deps, banner) {
80988
81488
  }
80989
81489
 
80990
81490
  // gateway/session-model-file.ts
80991
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync27, renameSync as renameSync11, rmSync as rmSync4 } from "node:fs";
80992
- import { join as join36 } from "node:path";
81491
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync28, renameSync as renameSync12, rmSync as rmSync4 } from "node:fs";
81492
+ import { join as join37 } from "node:path";
80993
81493
  var SESSION_MODEL_FILE = ".session-model";
80994
81494
  var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
80995
81495
  var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
80996
81496
  function atomicWrite(path2, content3) {
80997
81497
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
80998
- writeFileSync27(tmp, content3, "utf8");
80999
- renameSync11(tmp, path2);
81498
+ writeFileSync28(tmp, content3, "utf8");
81499
+ renameSync12(tmp, path2);
81000
81500
  }
81001
81501
  function serializeSessionModel(rec) {
81002
81502
  return `${JSON.stringify({
@@ -81011,28 +81511,28 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
81011
81511
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
81012
81512
  }
81013
81513
  try {
81014
- rmSync4(join36(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81514
+ rmSync4(join37(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81015
81515
  } catch {}
81016
- atomicWrite(join36(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
81516
+ atomicWrite(join37(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
81017
81517
  }
81018
81518
  function readSessionModelFileRaw(agentDir) {
81019
81519
  try {
81020
- return readFileSync30(join36(agentDir, SESSION_MODEL_FILE), "utf8");
81520
+ return readFileSync31(join37(agentDir, SESSION_MODEL_FILE), "utf8");
81021
81521
  } catch {
81022
81522
  return null;
81023
81523
  }
81024
81524
  }
81025
81525
  function clearSessionModelFile(agentDir) {
81026
81526
  try {
81027
- rmSync4(join36(agentDir, SESSION_MODEL_FILE), { force: true });
81527
+ rmSync4(join37(agentDir, SESSION_MODEL_FILE), { force: true });
81028
81528
  } catch {}
81029
81529
  }
81030
81530
  function consumeSessionModelCarrierOnHealthyBoot(agentDir) {
81031
81531
  try {
81032
- rmSync4(join36(agentDir, SESSION_MODEL_FILE), { force: true });
81532
+ rmSync4(join37(agentDir, SESSION_MODEL_FILE), { force: true });
81033
81533
  } catch {}
81034
81534
  try {
81035
- rmSync4(join36(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81535
+ rmSync4(join37(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81036
81536
  } catch {}
81037
81537
  }
81038
81538
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -81041,12 +81541,12 @@ function restoreSessionModelFileRaw(agentDir, raw) {
81041
81541
  return;
81042
81542
  }
81043
81543
  try {
81044
- atomicWrite(join36(agentDir, SESSION_MODEL_FILE), raw);
81544
+ atomicWrite(join37(agentDir, SESSION_MODEL_FILE), raw);
81045
81545
  } catch {}
81046
81546
  }
81047
81547
  function readConfiguredDefaultModel(agentDir) {
81048
81548
  try {
81049
- const v = readFileSync30(join36(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
81549
+ const v = readFileSync31(join37(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
81050
81550
  return v.length > 0 ? v : null;
81051
81551
  } catch {
81052
81552
  return null;
@@ -81058,12 +81558,12 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
81058
81558
  if (!EFFORT_LEVEL_RE.test(level)) {
81059
81559
  throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
81060
81560
  }
81061
- atomicWrite(join36(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
81561
+ atomicWrite(join37(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
81062
81562
  `);
81063
81563
  }
81064
81564
  function clearSessionEffortFile(agentDir) {
81065
81565
  try {
81066
- rmSync4(join36(agentDir, SESSION_EFFORT_FILE), { force: true });
81566
+ rmSync4(join37(agentDir, SESSION_EFFORT_FILE), { force: true });
81067
81567
  } catch {}
81068
81568
  }
81069
81569
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -81086,13 +81586,13 @@ function writePremiumRecoveryFile(agentDir, premiumModel, chats) {
81086
81586
  if (clean.length === 0) {
81087
81587
  throw new Error("refusing to persist premium-recovery marker with no chats to notify");
81088
81588
  }
81089
- atomicWrite(join36(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
81589
+ atomicWrite(join37(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
81090
81590
  `);
81091
81591
  }
81092
81592
  function readPremiumRecoveryFile(agentDir) {
81093
81593
  let raw;
81094
81594
  try {
81095
- raw = readFileSync30(join36(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
81595
+ raw = readFileSync31(join37(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
81096
81596
  } catch {
81097
81597
  return null;
81098
81598
  }
@@ -81105,7 +81605,7 @@ function readPremiumRecoveryFile(agentDir) {
81105
81605
  }
81106
81606
  function clearPremiumRecoveryFile(agentDir) {
81107
81607
  try {
81108
- rmSync4(join36(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
81608
+ rmSync4(join37(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
81109
81609
  } catch {}
81110
81610
  }
81111
81611
 
@@ -81394,7 +81894,7 @@ async function discoverModels(agentName3, opts = {}) {
81394
81894
  }
81395
81895
 
81396
81896
  // ../src/agents/scaffold.ts
81397
- import { dirname as dirname14, isAbsolute, join as join39, relative, resolve as resolve8 } from "node:path";
81897
+ import { dirname as dirname14, isAbsolute, join as join40, relative, resolve as resolve8 } from "node:path";
81398
81898
  init_atomic();
81399
81899
 
81400
81900
  // ../src/agents/agent-uid.ts
@@ -81411,7 +81911,7 @@ init_merge();
81411
81911
  init_timezone();
81412
81912
 
81413
81913
  // ../src/cli/agent-config.ts
81414
- import { join as join37 } from "node:path";
81914
+ import { join as join38 } from "node:path";
81415
81915
  import { homedir as homedir12 } from "node:os";
81416
81916
 
81417
81917
  // ../src/cli/helpers.ts
@@ -81432,12 +81932,12 @@ var WEBKITE_VAULT_KEYS = new Set([
81432
81932
  init_overlay_loader();
81433
81933
 
81434
81934
  // ../src/cli/agent-config.ts
81435
- var AUDIT_ROOT = join37(homedir12(), ".switchroom", "audit");
81935
+ var AUDIT_ROOT = join38(homedir12(), ".switchroom", "audit");
81436
81936
 
81437
81937
  // ../src/agents/profiles.ts
81438
81938
  var import_handlebars = __toESM(require_lib(), 1);
81439
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync28, existsSync as existsSync32, readdirSync as readdirSync7, statSync as statSync12, copyFileSync, mkdirSync as mkdirSync30, realpathSync as realpathSync2 } from "node:fs";
81440
- import { resolve as resolve7, join as join38, sep as pathSep } from "node:path";
81939
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync29, existsSync as existsSync33, readdirSync as readdirSync7, statSync as statSync12, copyFileSync, mkdirSync as mkdirSync31, realpathSync as realpathSync2 } from "node:fs";
81940
+ import { resolve as resolve7, join as join39, sep as pathSep } from "node:path";
81441
81941
  function resolveProfilesRoot() {
81442
81942
  const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
81443
81943
  if (envOverride) {
@@ -81448,7 +81948,7 @@ function resolveProfilesRoot() {
81448
81948
  resolve7(import.meta.dirname, "profiles")
81449
81949
  ];
81450
81950
  for (const candidate of candidates) {
81451
- if (existsSync32(candidate)) {
81951
+ if (existsSync33(candidate)) {
81452
81952
  return candidate;
81453
81953
  }
81454
81954
  }
@@ -81464,9 +81964,9 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
81464
81964
  var SHARED_FRAGMENTS_DIR = resolve7(PROFILES_ROOT, "_shared");
81465
81965
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
81466
81966
  for (const name of SHARED_FRAGMENTS) {
81467
- const fragPath = join38(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
81468
- if (existsSync32(fragPath)) {
81469
- import_handlebars.default.registerPartial(name, readFileSync31(fragPath, "utf-8"));
81967
+ const fragPath = join39(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
81968
+ if (existsSync33(fragPath)) {
81969
+ import_handlebars.default.registerPartial(name, readFileSync32(fragPath, "utf-8"));
81470
81970
  }
81471
81971
  }
81472
81972
 
@@ -82073,7 +82573,7 @@ init_overlay_loader();
82073
82573
  init_merge();
82074
82574
  init_timezone();
82075
82575
  var import_yaml4 = __toESM(require_dist(), 1);
82076
- import { readFileSync as readFileSync32, existsSync as existsSync33 } from "node:fs";
82576
+ import { readFileSync as readFileSync33, existsSync as existsSync34 } from "node:fs";
82077
82577
  import { homedir as homedir13 } from "node:os";
82078
82578
  import { resolve as resolve9 } from "node:path";
82079
82579
 
@@ -82149,7 +82649,7 @@ function findConfigFile2(startDir) {
82149
82649
  resolve9(userDir, "clerk.yml")
82150
82650
  ].filter(Boolean);
82151
82651
  for (const path2 of searchPaths) {
82152
- if (existsSync33(path2)) {
82652
+ if (existsSync34(path2)) {
82153
82653
  return path2;
82154
82654
  }
82155
82655
  }
@@ -82157,12 +82657,12 @@ function findConfigFile2(startDir) {
82157
82657
  }
82158
82658
  function loadConfig2(configPath) {
82159
82659
  const filePath = configPath ?? findConfigFile2();
82160
- if (!existsSync33(filePath)) {
82660
+ if (!existsSync34(filePath)) {
82161
82661
  throw new ConfigError2(`Config file not found: ${filePath}`);
82162
82662
  }
82163
82663
  let raw;
82164
82664
  try {
82165
- raw = readFileSync32(filePath, "utf-8");
82665
+ raw = readFileSync33(filePath, "utf-8");
82166
82666
  } catch (err) {
82167
82667
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
82168
82668
  ` ${err.message}`
@@ -82692,15 +83192,15 @@ function topicForRecipient(args) {
82692
83192
  }
82693
83193
 
82694
83194
  // ../src/agents/perf.ts
82695
- import { existsSync as existsSync34, readFileSync as readFileSync33 } from "node:fs";
83195
+ import { existsSync as existsSync35, readFileSync as readFileSync34 } from "node:fs";
82696
83196
  function readTurnUsages(jsonlPath, lastN) {
82697
- if (!existsSync34(jsonlPath))
83197
+ if (!existsSync35(jsonlPath))
82698
83198
  return [];
82699
83199
  if (lastN <= 0)
82700
83200
  return [];
82701
83201
  let raw;
82702
83202
  try {
82703
- raw = readFileSync33(jsonlPath, "utf-8");
83203
+ raw = readFileSync34(jsonlPath, "utf-8");
82704
83204
  } catch {
82705
83205
  return [];
82706
83206
  }
@@ -82791,8 +83291,8 @@ function numField(obj, key) {
82791
83291
  }
82792
83292
 
82793
83293
  // gateway/context-occupancy.ts
82794
- import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync29 } from "node:fs";
82795
- import { join as join40 } from "node:path";
83294
+ import { mkdirSync as mkdirSync32, writeFileSync as writeFileSync30 } from "node:fs";
83295
+ import { join as join41 } from "node:path";
82796
83296
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
82797
83297
  var TIGHT_FRACTION = 0.8;
82798
83298
  function buildContextOccupancy(occupancy, cap, now) {
@@ -82815,9 +83315,9 @@ function buildContextOccupancy(occupancy, cap, now) {
82815
83315
  }
82816
83316
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
82817
83317
  try {
82818
- const path2 = join40(stateDir, CONTEXT_OCCUPANCY_FILENAME);
82819
- (deps?.mkdir ?? ((p, o) => mkdirSync31(p, o)))(stateDir, { recursive: true });
82820
- (deps?.writeFile ?? ((p, d) => writeFileSync29(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
83318
+ const path2 = join41(stateDir, CONTEXT_OCCUPANCY_FILENAME);
83319
+ (deps?.mkdir ?? ((p, o) => mkdirSync32(p, o)))(stateDir, { recursive: true });
83320
+ (deps?.writeFile ?? ((p, d) => writeFileSync30(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
82821
83321
  `);
82822
83322
  } catch {}
82823
83323
  }
@@ -82971,7 +83471,7 @@ function nextCompactNotify(state6, ev) {
82971
83471
  }
82972
83472
 
82973
83473
  // gateway/hostd-dispatch.ts
82974
- import { existsSync as existsSync35 } from "node:fs";
83474
+ import { existsSync as existsSync36 } from "node:fs";
82975
83475
  import { randomBytes as randomBytes7 } from "node:crypto";
82976
83476
  init_loader();
82977
83477
  var _hostdEnabled2;
@@ -82995,13 +83495,13 @@ function hostdSocketPath2(agentName3) {
82995
83495
  function hostdWillBeUsed2(agentName3) {
82996
83496
  if (!isHostdEnabled2())
82997
83497
  return false;
82998
- return existsSync35(hostdSocketPath2(agentName3));
83498
+ return existsSync36(hostdSocketPath2(agentName3));
82999
83499
  }
83000
83500
  async function tryHostdDispatch2(agentName3, req, timeoutMs = 5000) {
83001
83501
  if (!isHostdEnabled2())
83002
83502
  return "not-configured";
83003
83503
  const sockPath = hostdSocketPath2(agentName3);
83004
- if (!existsSync35(sockPath))
83504
+ if (!existsSync36(sockPath))
83005
83505
  return "not-configured";
83006
83506
  try {
83007
83507
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -83030,7 +83530,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
83030
83530
  if (!isHostdEnabled2())
83031
83531
  return "not-configured";
83032
83532
  const sockPath = hostdSocketPath2(agentName3);
83033
- if (!existsSync35(sockPath))
83533
+ if (!existsSync36(sockPath))
83034
83534
  return "not-configured";
83035
83535
  const now = opts.now ?? Date.now;
83036
83536
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -83149,7 +83649,7 @@ function createDmPinSweeper(deps) {
83149
83649
 
83150
83650
  // gateway/webhook-ingest-server.ts
83151
83651
  import net4 from "node:net";
83152
- import { chmodSync as chmodSync10, existsSync as existsSync36, unlinkSync as unlinkSync17 } from "node:fs";
83652
+ import { chmodSync as chmodSync10, existsSync as existsSync37, unlinkSync as unlinkSync17 } from "node:fs";
83153
83653
  var MAX_REQUEST_BYTES = 1024 * 1024;
83154
83654
  function fdOf(conn) {
83155
83655
  const handle = conn._handle;
@@ -83161,7 +83661,7 @@ function startWebhookIngestServer(opts) {
83161
83661
  const log = opts.log ?? ((s) => process.stderr.write(s));
83162
83662
  const allowed = new Set(opts.allowedUids);
83163
83663
  try {
83164
- if (existsSync36(opts.socketPath))
83664
+ if (existsSync37(opts.socketPath))
83165
83665
  unlinkSync17(opts.socketPath);
83166
83666
  } catch (err) {
83167
83667
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
@@ -83254,7 +83754,7 @@ function startWebhookIngestServer(opts) {
83254
83754
  server.close();
83255
83755
  } catch {}
83256
83756
  try {
83257
- if (existsSync36(opts.socketPath))
83757
+ if (existsSync37(opts.socketPath))
83258
83758
  unlinkSync17(opts.socketPath);
83259
83759
  } catch {}
83260
83760
  }
@@ -83262,20 +83762,20 @@ function startWebhookIngestServer(opts) {
83262
83762
  }
83263
83763
 
83264
83764
  // ../src/web/webhook-gateway-record.ts
83265
- import { appendFileSync as appendFileSync7, mkdirSync as mkdirSync34 } from "fs";
83266
- import { join as join43 } from "path";
83765
+ import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync35 } from "fs";
83766
+ import { join as join44 } from "path";
83267
83767
  import { homedir as homedir15 } from "os";
83268
83768
 
83269
83769
  // ../src/web/webhook-handler.ts
83270
- import { appendFileSync as appendFileSync6, existsSync as existsSync37, mkdirSync as mkdirSync32, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "fs";
83271
- import { join as join41 } from "path";
83770
+ import { appendFileSync as appendFileSync7, existsSync as existsSync38, mkdirSync as mkdirSync33, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
83771
+ import { join as join42 } from "path";
83272
83772
  var DEDUP_MAX = 1000;
83273
83773
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
83274
83774
  function loadDedupFile(path2) {
83275
83775
  try {
83276
- if (!existsSync37(path2))
83776
+ if (!existsSync38(path2))
83277
83777
  return {};
83278
- const raw = JSON.parse(readFileSync34(path2, "utf-8"));
83778
+ const raw = JSON.parse(readFileSync35(path2, "utf-8"));
83279
83779
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
83280
83780
  } catch {
83281
83781
  return {};
@@ -83289,7 +83789,7 @@ function saveDedupFile(path2, deliveries, now) {
83289
83789
  }
83290
83790
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
83291
83791
  const final = Object.fromEntries(sorted);
83292
- writeFileSync30(path2, JSON.stringify({ deliveries: final }), {
83792
+ writeFileSync31(path2, JSON.stringify({ deliveries: final }), {
83293
83793
  mode: 384
83294
83794
  });
83295
83795
  }
@@ -83297,8 +83797,8 @@ var agentDedupCache = new Map;
83297
83797
  function createFileDedupStore(resolveAgentDir) {
83298
83798
  return {
83299
83799
  check(agent, deliveryId, now) {
83300
- const telegramDir = join41(resolveAgentDir(agent), "telegram");
83301
- const filePath = join41(telegramDir, "webhook-dedup.json");
83800
+ const telegramDir = join42(resolveAgentDir(agent), "telegram");
83801
+ const filePath = join42(telegramDir, "webhook-dedup.json");
83302
83802
  if (!agentDedupCache.has(agent)) {
83303
83803
  agentDedupCache.set(agent, loadDedupFile(filePath));
83304
83804
  }
@@ -83308,7 +83808,7 @@ function createFileDedupStore(resolveAgentDir) {
83308
83808
  }
83309
83809
  deliveries[deliveryId] = now;
83310
83810
  try {
83311
- mkdirSync32(telegramDir, { recursive: true });
83811
+ mkdirSync33(telegramDir, { recursive: true });
83312
83812
  saveDedupFile(filePath, deliveries, now);
83313
83813
  } catch {}
83314
83814
  return;
@@ -83319,8 +83819,8 @@ var tokenBuckets = new Map;
83319
83819
  var throttleIssueWindow = new Map;
83320
83820
 
83321
83821
  // ../src/web/webhook-dispatch.ts
83322
- import { existsSync as existsSync38, mkdirSync as mkdirSync33, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
83323
- import { join as join42 } from "path";
83822
+ import { existsSync as existsSync39, mkdirSync as mkdirSync34, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
83823
+ import { join as join43 } from "path";
83324
83824
  import { homedir as homedir14 } from "os";
83325
83825
 
83326
83826
  // ../src/agent-scheduler/ipc-client.ts
@@ -83619,9 +84119,9 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
83619
84119
  }
83620
84120
  function loadCooldownFile(path2) {
83621
84121
  try {
83622
- if (!existsSync38(path2))
84122
+ if (!existsSync39(path2))
83623
84123
  return {};
83624
- const raw = JSON.parse(readFileSync35(path2, "utf-8"));
84124
+ const raw = JSON.parse(readFileSync36(path2, "utf-8"));
83625
84125
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
83626
84126
  } catch {
83627
84127
  return {};
@@ -83629,7 +84129,7 @@ function loadCooldownFile(path2) {
83629
84129
  }
83630
84130
  function saveCooldownFile(path2, dispatches) {
83631
84131
  try {
83632
- writeFileSync31(path2, JSON.stringify({ dispatches }), {
84132
+ writeFileSync32(path2, JSON.stringify({ dispatches }), {
83633
84133
  mode: 384
83634
84134
  });
83635
84135
  } catch {}
@@ -83640,8 +84140,8 @@ function createFileCooldownStore(resolveAgentDir) {
83640
84140
  isCoolingDown(agent, key, cooldownMs, now) {
83641
84141
  if (cooldownMs <= 0)
83642
84142
  return false;
83643
- const telegramDir = join42(resolveAgentDir(agent), "telegram");
83644
- const filePath = join42(telegramDir, "webhook-cooldown.json");
84143
+ const telegramDir = join43(resolveAgentDir(agent), "telegram");
84144
+ const filePath = join43(telegramDir, "webhook-cooldown.json");
83645
84145
  if (!cache.has(agent)) {
83646
84146
  cache.set(agent, loadCooldownFile(filePath));
83647
84147
  }
@@ -83652,7 +84152,7 @@ function createFileCooldownStore(resolveAgentDir) {
83652
84152
  }
83653
84153
  dispatches[key] = now;
83654
84154
  try {
83655
- mkdirSync33(telegramDir, { recursive: true });
84155
+ mkdirSync34(telegramDir, { recursive: true });
83656
84156
  saveCooldownFile(filePath, dispatches);
83657
84157
  } catch {}
83658
84158
  return false;
@@ -83698,9 +84198,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
83698
84198
  }
83699
84199
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
83700
84200
  const log = deps.log ?? ((s) => process.stderr.write(s));
83701
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir14(), ".switchroom", "agents", a));
84201
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir14(), ".switchroom", "agents", a));
83702
84202
  const now = (deps.now ?? Date.now)();
83703
- const socketPath = join42(resolveAgentDir(agent), "telegram", "gateway.sock");
84203
+ const socketPath = join43(resolveAgentDir(agent), "telegram", "gateway.sock");
83704
84204
  const inbound = {
83705
84205
  type: "inbound",
83706
84206
  chatId: ctx.chatId,
@@ -83772,7 +84272,7 @@ function evaluateDispatch(args, deps = {}) {
83772
84272
  const log = deps.log ?? ((s) => process.stderr.write(s));
83773
84273
  const now = (deps.now ?? Date.now)();
83774
84274
  const nowDate = deps.nowDate ?? (() => new Date(now));
83775
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir14(), ".switchroom", "agents", a));
84275
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir14(), ".switchroom", "agents", a));
83776
84276
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
83777
84277
  if (!DISPATCH_SOURCES.includes(args.source))
83778
84278
  return 0;
@@ -83850,10 +84350,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
83850
84350
  function recordWebhookEvent(rec, deps = {}) {
83851
84351
  const log = deps.log ?? ((s) => process.stderr.write(s));
83852
84352
  const now = rec.ts || (deps.now ?? Date.now)();
83853
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir15(), ".switchroom", "agents", a));
84353
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join44(homedir15(), ".switchroom", "agents", a));
83854
84354
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
83855
84355
  const agent = rec.agent;
83856
- const telegramDir = join43(resolveAgentDir(agent), "telegram");
84356
+ const telegramDir = join44(resolveAgentDir(agent), "telegram");
83857
84357
  if (rec.source === "github" && rec.delivery_id) {
83858
84358
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
83859
84359
  if (originalTs !== undefined) {
@@ -83862,9 +84362,9 @@ function recordWebhookEvent(rec, deps = {}) {
83862
84362
  return { status: "deduped", ts: originalTs };
83863
84363
  }
83864
84364
  }
83865
- const logPath = join43(telegramDir, "webhook-events.jsonl");
84365
+ const logPath = join44(telegramDir, "webhook-events.jsonl");
83866
84366
  try {
83867
- mkdirSync34(telegramDir, { recursive: true });
84367
+ mkdirSync35(telegramDir, { recursive: true });
83868
84368
  const record = {
83869
84369
  ts: now,
83870
84370
  source: rec.source,
@@ -83872,7 +84372,7 @@ function recordWebhookEvent(rec, deps = {}) {
83872
84372
  rendered_text: rec.rendered_text,
83873
84373
  payload: rec.payload
83874
84374
  };
83875
- appendFileSync7(logPath, JSON.stringify(record) + `
84375
+ appendFileSync8(logPath, JSON.stringify(record) + `
83876
84376
  `, { mode: 384 });
83877
84377
  } catch (err) {
83878
84378
  log(`webhook-gateway: agent='${agent}' source='${rec.source}' write failed: ${err.message}
@@ -83974,7 +84474,7 @@ function recordWebhookEvent(rec, deps = {}) {
83974
84474
 
83975
84475
  // gateway/ipc-server.ts
83976
84476
  init_format();
83977
- import { renameSync as renameSync13, unlinkSync as unlinkSync18, chmodSync as chmodSync11 } from "fs";
84477
+ import { renameSync as renameSync14, unlinkSync as unlinkSync18, chmodSync as chmodSync11 } from "fs";
83978
84478
  var MAX_BUFFER_SIZE = 1024 * 1024;
83979
84479
  var VALID_OPERATOR_KINDS = new Set([
83980
84480
  "credentials-expired",
@@ -84183,7 +84683,7 @@ function createIpcServer(options) {
84183
84683
  heartbeatTimeoutMs = 30000
84184
84684
  } = options;
84185
84685
  try {
84186
- renameSync13(socketPath, socketPath + ".bak");
84686
+ renameSync14(socketPath, socketPath + ".bak");
84187
84687
  } catch {}
84188
84688
  try {
84189
84689
  unlinkSync18(socketPath + ".bak");
@@ -84573,7 +85073,7 @@ function createIpcServer(options) {
84573
85073
  clientBySocketId.clear();
84574
85074
  server.stop(true);
84575
85075
  try {
84576
- renameSync13(socketPath, socketPath + ".bak");
85076
+ renameSync14(socketPath, socketPath + ".bak");
84577
85077
  } catch {}
84578
85078
  }
84579
85079
  };
@@ -84883,15 +85383,15 @@ function buildMs365CardText(p) {
84883
85383
  const lines = [];
84884
85384
  lines.push(`\uD83D\uDCC4 Microsoft 365 write approval`);
84885
85385
  lines.push("");
84886
- lines.push(`Agent: ${truncate4(p.agentName, 64)}`);
84887
- lines.push(`Tool: ${truncate4(p.toolName.replace(/^mcp__/, ""), 96)}`);
84888
- lines.push(`Item: ${truncate4(p.itemDisplayName, 256)}`);
85386
+ lines.push(`Agent: ${truncate3(p.agentName, 64)}`);
85387
+ lines.push(`Tool: ${truncate3(p.toolName.replace(/^mcp__/, ""), 96)}`);
85388
+ lines.push(`Item: ${truncate3(p.itemDisplayName, 256)}`);
84889
85389
  if (p.itemId !== "(new)") {
84890
- lines.push(`ID: ${truncate4(p.itemId, 96)}`);
85390
+ lines.push(`ID: ${truncate3(p.itemId, 96)}`);
84891
85391
  }
84892
- lines.push(`Account: ${truncate4(p.accountEmail, 96)}`);
85392
+ lines.push(`Account: ${truncate3(p.accountEmail, 96)}`);
84893
85393
  if (p.eventWhen) {
84894
- lines.push(`When: ${truncate4(p.eventWhen, 96)}`);
85394
+ lines.push(`When: ${truncate3(p.eventWhen, 96)}`);
84895
85395
  }
84896
85396
  if (typeof p.sizeBytesBefore === "number" || typeof p.sizeBytesAfter === "number") {
84897
85397
  const before = p.sizeBytesBefore ?? 0;
@@ -84901,27 +85401,27 @@ function buildMs365CardText(p) {
84901
85401
  lines.push(`Size: ${humanBytes(before)} \u2192 ${humanBytes(after)} (${sign}${humanBytes(delta)})`);
84902
85402
  }
84903
85403
  if (p.deepLink) {
84904
- lines.push(`Link: ${truncate4(p.deepLink, 256)}`);
85404
+ lines.push(`Link: ${truncate3(p.deepLink, 256)}`);
84905
85405
  }
84906
85406
  if (p.changes && p.changes.length > 0) {
84907
85407
  lines.push("");
84908
85408
  lines.push("Changes:");
84909
85409
  for (const c of p.changes.slice(0, 8)) {
84910
- const before = c.before !== undefined ? truncate4(c.before, 96) : "(none)";
84911
- const after = c.after !== undefined ? truncate4(c.after, 96) : "(cleared)";
85410
+ const before = c.before !== undefined ? truncate3(c.before, 96) : "(none)";
85411
+ const after = c.after !== undefined ? truncate3(c.after, 96) : "(cleared)";
84912
85412
  lines.push(`\u2022 ${c.field}: ${before} \u2192 ${after}`);
84913
85413
  }
84914
85414
  }
84915
85415
  if (p.agentRationale) {
84916
85416
  lines.push("");
84917
- lines.push(`\uD83D\uDCAC ${truncate4(p.agentRationale, 512)}`);
85417
+ lines.push(`\uD83D\uDCAC ${truncate3(p.agentRationale, 512)}`);
84918
85418
  }
84919
85419
  lines.push("");
84920
85420
  lines.push(p.changes && p.changes.length > 0 ? "\u26a0\ufe0f Attestation (RFC \u00a78 v1.5): the diff above is derived from live Graph state + the mutation payload. Verify before approving." : "\u26a0\ufe0f Weak attestation (RFC \u00a78 v1): operator should click through to verify the actual change before approving. Structural diff coming v1.5.");
84921
85421
  return hardenCardBreaks(lines.join(`
84922
85422
  `));
84923
85423
  }
84924
- function truncate4(s, n) {
85424
+ function truncate3(s, n) {
84925
85425
  const oneLine = s.replace(/[\r\n\t]+/g, " ");
84926
85426
  if (oneLine.length <= n)
84927
85427
  return oneLine;
@@ -88233,6 +88733,246 @@ function buildSilencePokeOptions(deps) {
88233
88733
  };
88234
88734
  }
88235
88735
 
88736
+ // tool-labels.ts
88737
+ var MAX_LABEL_CHARS = 60;
88738
+ var MAX_BASH_CHARS = 40;
88739
+ var MAX_DESCRIPTION_CHARS = 160;
88740
+ function basename8(p) {
88741
+ if (!p)
88742
+ return "";
88743
+ const parts = p.split("/").filter(Boolean);
88744
+ return parts.length > 0 ? parts[parts.length - 1] : p;
88745
+ }
88746
+ function shortenGrepPath(p) {
88747
+ if (!p)
88748
+ return "repo";
88749
+ const hadTrailingSlash = /\/+$/.test(p);
88750
+ const trimmed = p.replace(/\/+$/, "");
88751
+ const parts = trimmed.split("/").filter(Boolean);
88752
+ if (parts.length === 0)
88753
+ return "repo";
88754
+ const last = parts[parts.length - 1];
88755
+ if (hadTrailingSlash)
88756
+ return `${last}/`;
88757
+ if (last.startsWith(".") && !last.slice(1).includes("."))
88758
+ return last;
88759
+ if (!last.includes("."))
88760
+ return `${last}/`;
88761
+ return last;
88762
+ }
88763
+ function hostFromUrl(u) {
88764
+ if (!u)
88765
+ return "";
88766
+ try {
88767
+ return new URL(u).host;
88768
+ } catch {
88769
+ return truncate4(u);
88770
+ }
88771
+ }
88772
+ function truncate4(s, n = MAX_LABEL_CHARS) {
88773
+ if (s.length <= n)
88774
+ return s;
88775
+ return s.slice(0, n - 1) + "\u2026";
88776
+ }
88777
+ function stripHtml(s) {
88778
+ return s.replace(/<\/?[a-zA-Z][^>]*>/g, "");
88779
+ }
88780
+ function firstLine(s) {
88781
+ const idx = s.indexOf(`
88782
+ `);
88783
+ return idx === -1 ? s : s.slice(0, idx);
88784
+ }
88785
+ function toolLabel(tool, input, preamble, precomputedLabel) {
88786
+ if (precomputedLabel && precomputedLabel.trim().length > 0) {
88787
+ return truncate4(firstLine(precomputedLabel.trim()), MAX_DESCRIPTION_CHARS);
88788
+ }
88789
+ if (!input || typeof input !== "object")
88790
+ return "";
88791
+ const str = (k) => typeof input[k] === "string" ? input[k] : undefined;
88792
+ const preambleLabel = () => {
88793
+ if (!preamble)
88794
+ return null;
88795
+ if (preamble.includes(`
88796
+ `))
88797
+ return null;
88798
+ const trimmed = preamble.trim();
88799
+ if (!trimmed)
88800
+ return null;
88801
+ if (trimmed.length > MAX_DESCRIPTION_CHARS)
88802
+ return null;
88803
+ return trimmed;
88804
+ };
88805
+ switch (tool) {
88806
+ case "Read":
88807
+ case "Write":
88808
+ case "NotebookEdit":
88809
+ case "Edit": {
88810
+ const pre = preambleLabel();
88811
+ if (pre)
88812
+ return pre;
88813
+ return truncate4(basename8(str("file_path") ?? ""));
88814
+ }
88815
+ case "Bash":
88816
+ case "BashOutput": {
88817
+ const description = str("description");
88818
+ if (description)
88819
+ return truncate4(firstLine(description), MAX_DESCRIPTION_CHARS);
88820
+ const pre = preambleLabel();
88821
+ if (pre)
88822
+ return pre;
88823
+ const cmd = str("command") ?? str("bash_id") ?? "";
88824
+ return truncate4(firstLine(cmd), MAX_BASH_CHARS);
88825
+ }
88826
+ case "KillShell":
88827
+ return truncate4(str("shell_id") ?? "");
88828
+ case "Glob": {
88829
+ const pre = preambleLabel();
88830
+ if (pre)
88831
+ return pre;
88832
+ return truncate4(str("pattern") ?? "");
88833
+ }
88834
+ case "Grep": {
88835
+ const pre = preambleLabel();
88836
+ if (pre)
88837
+ return pre;
88838
+ const pat = str("pattern") ?? "";
88839
+ if (!pat)
88840
+ return "";
88841
+ const path2 = str("path");
88842
+ const where = shortenGrepPath(path2 ?? "");
88843
+ return truncate4(`"${pat}" (in ${where})`);
88844
+ }
88845
+ case "WebFetch":
88846
+ return truncate4(hostFromUrl(str("url") ?? ""));
88847
+ case "WebSearch": {
88848
+ const q = str("query") ?? "";
88849
+ return q ? truncate4(`"${q}"`) : "";
88850
+ }
88851
+ case "Task":
88852
+ case "Agent": {
88853
+ const desc = str("description") ?? str("subagent_type") ?? "";
88854
+ return truncate4(desc);
88855
+ }
88856
+ case "TodoWrite":
88857
+ case "TaskCreate":
88858
+ case "TaskUpdate":
88859
+ case "TaskList":
88860
+ case "TaskGet":
88861
+ case "TaskStop":
88862
+ case "TaskOutput":
88863
+ return "";
88864
+ case "Skill":
88865
+ return truncate4(str("skill") ?? "");
88866
+ case "SlashCommand":
88867
+ return truncate4(str("command") ?? "");
88868
+ case "ToolSearch": {
88869
+ const q = str("query") ?? "";
88870
+ if (!q)
88871
+ return "";
88872
+ const selectMatch = q.match(/^\s*select\s*:\s*(.+)$/i);
88873
+ if (selectMatch) {
88874
+ const names = selectMatch[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0).join(", ");
88875
+ return truncate4(`Loading schema: ${names}`);
88876
+ }
88877
+ return truncate4(`Searching tools: ${q}`);
88878
+ }
88879
+ default:
88880
+ if (tool.startsWith("mcp__")) {
88881
+ const description = str("description");
88882
+ if (description)
88883
+ return truncate4(firstLine(stripHtml(description)), MAX_DESCRIPTION_CHARS);
88884
+ const label = mcpBaseLabel(tool);
88885
+ const query3 = str("query") ?? str("text") ?? str("name");
88886
+ if (label && query3) {
88887
+ const budget = Math.max(8, MAX_LABEL_CHARS - label.length - 4);
88888
+ const preview = truncate4(firstLine(stripHtml(query3)), budget);
88889
+ return `${label} (${preview})`;
88890
+ }
88891
+ if (label)
88892
+ return truncate4(label);
88893
+ }
88894
+ for (const k of ["description", "file_path", "path", "url", "query", "pattern", "command"]) {
88895
+ const v = str(k);
88896
+ if (v != null && v.length > 0) {
88897
+ if (k === "file_path" || k === "path")
88898
+ return truncate4(basename8(v));
88899
+ if (k === "url")
88900
+ return truncate4(hostFromUrl(v));
88901
+ if (k === "description")
88902
+ return truncate4(firstLine(v), MAX_DESCRIPTION_CHARS);
88903
+ return truncate4(firstLine(v));
88904
+ }
88905
+ }
88906
+ return "";
88907
+ }
88908
+ }
88909
+ function mcpBaseLabel(tool) {
88910
+ if (!tool.startsWith("mcp__"))
88911
+ return "";
88912
+ const parts = tool.slice("mcp__".length).split("__");
88913
+ if (parts.length < 2)
88914
+ return "";
88915
+ const rawServer = parts[0];
88916
+ const action = parts.slice(1).join("__");
88917
+ if (!rawServer || !action)
88918
+ return "";
88919
+ return `${prettifyServer(rawServer)}: ${action}`;
88920
+ }
88921
+ function prettifyServer(name) {
88922
+ const LABELS = {
88923
+ "switchroom-telegram": "Telegram"
88924
+ };
88925
+ if (LABELS[name])
88926
+ return LABELS[name];
88927
+ if (!name)
88928
+ return name;
88929
+ return name.charAt(0).toUpperCase() + name.slice(1);
88930
+ }
88931
+
88932
+ // gateway/background-shell-liveness.ts
88933
+ var TERMINAL_STATUSES = new Set(["completed", "failed", "killed"]);
88934
+ function applyBackgroundShellLiveness(registry, key, ev) {
88935
+ if (ev.kind === "tool_result") {
88936
+ if (ev.backgroundTaskId != null && ev.backgroundTaskId.length > 0) {
88937
+ registry.noteBackgroundShellAlive(key, ev.backgroundTaskId);
88938
+ }
88939
+ return;
88940
+ }
88941
+ if (ev.kind === "task_notification") {
88942
+ if (ev.taskId.length > 0 && TERMINAL_STATUSES.has(ev.status)) {
88943
+ registry.noteBackgroundShellDead(key, ev.taskId);
88944
+ }
88945
+ return;
88946
+ }
88947
+ if (ev.kind === "tool_use" && ev.toolName === "KillShell") {
88948
+ const killId = ev.input?.shell_id;
88949
+ if (typeof killId === "string" && killId.length > 0) {
88950
+ registry.noteBackgroundShellDead(key, killId);
88951
+ }
88952
+ }
88953
+ }
88954
+
88955
+ // gateway/silence-poke-session-event.ts
88956
+ function applySilencePokeSessionEvent(silencePoke, pendingProgress, key, ev) {
88957
+ if (ev.kind === "thinking") {
88958
+ silencePoke.noteThinking(key, Date.now());
88959
+ } else if (ev.kind === "tool_use") {
88960
+ if (ev.toolUseId != null && ev.toolUseId.length > 0 && !isTelegramSurfaceTool(ev.toolName)) {
88961
+ const label = toolLabel(ev.toolName, ev.input, undefined, ev.precomputedLabel);
88962
+ silencePoke.noteToolStart(key, ev.toolUseId, ev.toolName, label.length > 0 ? label : null, Date.now());
88963
+ const evInput = ev.input;
88964
+ if (ev.toolName === "Agent" || ev.toolName === "Task" || ev.toolName === "Bash" && evInput?.run_in_background === true) {
88965
+ pendingProgress.noteAsyncDispatch(key);
88966
+ }
88967
+ }
88968
+ } else if (ev.kind === "tool_result") {
88969
+ if (ev.toolUseId != null && ev.toolUseId.length > 0) {
88970
+ silencePoke.noteToolEnd(key, ev.toolUseId, Date.now());
88971
+ }
88972
+ }
88973
+ applyBackgroundShellLiveness(silencePoke, key, ev);
88974
+ }
88975
+
88236
88976
  // gateway/inbound-delivery-machine-dispatch.ts
88237
88977
  function dispatchEffects(effects, ctx) {
88238
88978
  for (const effect of effects) {
@@ -88508,27 +89248,27 @@ function skillProposalKeyboard(id) {
88508
89248
  // ../src/self-improve/skill-proposals.ts
88509
89249
  import {
88510
89250
  closeSync as closeSync7,
88511
- existsSync as existsSync39,
88512
- mkdirSync as mkdirSync35,
89251
+ existsSync as existsSync40,
89252
+ mkdirSync as mkdirSync36,
88513
89253
  openSync as openSync7,
88514
- readFileSync as readFileSync36,
89254
+ readFileSync as readFileSync37,
88515
89255
  writeSync as writeSync5
88516
89256
  } from "node:fs";
88517
- import { join as join44 } from "node:path";
89257
+ import { join as join45 } from "node:path";
88518
89258
  import { randomUUID as randomUUID5 } from "node:crypto";
88519
89259
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
88520
89260
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
88521
89261
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
88522
89262
  var PROPOSAL_SIM_THRESHOLD = 0.5;
88523
89263
  function proposalsPath2(stateDir) {
88524
- return join44(stateDir, PROPOSALS_FILE2);
89264
+ return join45(stateDir, PROPOSALS_FILE2);
88525
89265
  }
88526
89266
  function rejectedPath2(stateDir) {
88527
- return join44(stateDir, REJECTED_FILE2);
89267
+ return join45(stateDir, REJECTED_FILE2);
88528
89268
  }
88529
89269
  function ensureDir3(stateDir) {
88530
- if (!existsSync39(stateDir)) {
88531
- mkdirSync35(stateDir, { recursive: true, mode: 493 });
89270
+ if (!existsSync40(stateDir)) {
89271
+ mkdirSync36(stateDir, { recursive: true, mode: 493 });
88532
89272
  }
88533
89273
  }
88534
89274
  function appendLine2(path2, obj) {
@@ -88541,11 +89281,11 @@ function appendLine2(path2, obj) {
88541
89281
  }
88542
89282
  }
88543
89283
  function readLines2(path2, isValid2) {
88544
- if (!existsSync39(path2))
89284
+ if (!existsSync40(path2))
88545
89285
  return [];
88546
89286
  let raw;
88547
89287
  try {
88548
- raw = readFileSync36(path2, "utf-8");
89288
+ raw = readFileSync37(path2, "utf-8");
88549
89289
  } catch {
88550
89290
  return [];
88551
89291
  }
@@ -89142,11 +89882,11 @@ function escapeBody2(s) {
89142
89882
  }
89143
89883
 
89144
89884
  // gateway/pid-file.ts
89145
- import { writeFileSync as writeFileSync32, readFileSync as readFileSync37, unlinkSync as unlinkSync19, renameSync as renameSync14 } from "node:fs";
89885
+ import { writeFileSync as writeFileSync33, readFileSync as readFileSync38, unlinkSync as unlinkSync19, renameSync as renameSync15 } from "node:fs";
89146
89886
  function writePidFile(path2, record) {
89147
89887
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89148
- writeFileSync32(tmp, JSON.stringify(record), "utf-8");
89149
- renameSync14(tmp, path2);
89888
+ writeFileSync33(tmp, JSON.stringify(record), "utf-8");
89889
+ renameSync15(tmp, path2);
89150
89890
  }
89151
89891
  function clearPidFile(path2) {
89152
89892
  try {
@@ -89161,10 +89901,10 @@ import {
89161
89901
  writeFile as writeFileAsync,
89162
89902
  readFile as readFileAsync
89163
89903
  } from "node:fs/promises";
89164
- import { readFileSync as readFileSync38 } from "node:fs";
89904
+ import { readFileSync as readFileSync39 } from "node:fs";
89165
89905
  function readCurrentBootId() {
89166
89906
  try {
89167
- const stat = readFileSync38("/proc/1/stat", "utf-8");
89907
+ const stat = readFileSync39("/proc/1/stat", "utf-8");
89168
89908
  const lastParen = stat.lastIndexOf(")");
89169
89909
  if (lastParen < 0)
89170
89910
  return null;
@@ -89367,15 +90107,15 @@ function safeCount(fn) {
89367
90107
  }
89368
90108
 
89369
90109
  // gateway/session-marker.ts
89370
- import { writeFileSync as writeFileSync33, readFileSync as readFileSync39, renameSync as renameSync15, unlinkSync as unlinkSync20 } from "node:fs";
90110
+ import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync16, unlinkSync as unlinkSync20 } from "node:fs";
89371
90111
  function writeSessionMarker(path2, marker) {
89372
90112
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89373
- writeFileSync33(tmp, JSON.stringify(marker), "utf-8");
89374
- renameSync15(tmp, path2);
90113
+ writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
90114
+ renameSync16(tmp, path2);
89375
90115
  }
89376
90116
  function readSessionMarker(path2) {
89377
90117
  try {
89378
- const raw = readFileSync39(path2, "utf-8");
90118
+ const raw = readFileSync40(path2, "utf-8");
89379
90119
  const parsed = JSON.parse(raw);
89380
90120
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
89381
90121
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -89397,16 +90137,16 @@ function shouldFireRestartBanner(input) {
89397
90137
  }
89398
90138
 
89399
90139
  // gateway/clean-shutdown-marker.ts
89400
- import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync16, unlinkSync as unlinkSync21 } from "node:fs";
90140
+ import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync17, unlinkSync as unlinkSync21 } from "node:fs";
89401
90141
  var DEFAULT_MAX_AGE_MS = 60000;
89402
90142
  function writeCleanShutdownMarker(path2, marker) {
89403
90143
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89404
- writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
89405
- renameSync16(tmp, path2);
90144
+ writeFileSync35(tmp, JSON.stringify(marker), "utf-8");
90145
+ renameSync17(tmp, path2);
89406
90146
  }
89407
90147
  function readCleanShutdownMarker(path2) {
89408
90148
  try {
89409
- const raw = readFileSync40(path2, "utf-8");
90149
+ const raw = readFileSync41(path2, "utf-8");
89410
90150
  const parsed = JSON.parse(raw);
89411
90151
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
89412
90152
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -89778,16 +90518,16 @@ function classifyAdminGate(text4, myAgentName) {
89778
90518
 
89779
90519
  // subagent-watcher.ts
89780
90520
  import {
89781
- existsSync as existsSync40,
90521
+ existsSync as existsSync41,
89782
90522
  openSync as openSync8,
89783
90523
  readSync as readSync2,
89784
90524
  statSync as statSync13,
89785
90525
  closeSync as closeSync8,
89786
90526
  watch,
89787
90527
  readdirSync as readdirSync8,
89788
- readFileSync as readFileSync41
90528
+ readFileSync as readFileSync42
89789
90529
  } from "fs";
89790
- import { join as join45 } from "path";
90530
+ import { join as join46 } from "path";
89791
90531
 
89792
90532
  // session-tail.ts
89793
90533
  function sanitizeCwdToProjectName2(cwd) {
@@ -90137,7 +90877,7 @@ function backfillJsonlAgentId(db3, jsonlPath, agentId, log) {
90137
90877
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
90138
90878
  let meta;
90139
90879
  try {
90140
- const raw = readFileSync41(metaPath, "utf8");
90880
+ const raw = readFileSync42(metaPath, "utf8");
90141
90881
  meta = JSON.parse(raw);
90142
90882
  } catch {
90143
90883
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -90592,7 +91332,7 @@ function startSubagentWatcher(config) {
90592
91332
  clearTimeout(ref.ref);
90593
91333
  });
90594
91334
  const fs2 = config.fs ?? {
90595
- existsSync: existsSync40,
91335
+ existsSync: existsSync41,
90596
91336
  readdirSync: readdirSync8,
90597
91337
  statSync: statSync13,
90598
91338
  openSync: openSync8,
@@ -91110,8 +91850,8 @@ function startSubagentWatcher(config) {
91110
91850
  if (stopped)
91111
91851
  return;
91112
91852
  pruneVanishedDirWatchers();
91113
- const claudeHome = join45(agentDir, ".claude");
91114
- const projectsRoot = join45(claudeHome, "projects");
91853
+ const claudeHome = join46(agentDir, ".claude");
91854
+ const projectsRoot = join46(claudeHome, "projects");
91115
91855
  if (!fs2.existsSync(projectsRoot))
91116
91856
  return;
91117
91857
  let projectDirs;
@@ -91145,7 +91885,7 @@ function startSubagentWatcher(config) {
91145
91885
  continue;
91146
91886
  }
91147
91887
  warnedForeignSlugs.delete(pDir);
91148
- const projectPath = join45(projectsRoot, pDir);
91888
+ const projectPath = join46(projectsRoot, pDir);
91149
91889
  let sessionDirs;
91150
91890
  try {
91151
91891
  sessionDirs = fs2.readdirSync(projectPath);
@@ -91155,7 +91895,7 @@ function startSubagentWatcher(config) {
91155
91895
  for (const sDir of sessionDirs) {
91156
91896
  if (sDir.endsWith(".jsonl"))
91157
91897
  continue;
91158
- const subagentsPath = join45(projectPath, sDir, "subagents");
91898
+ const subagentsPath = join46(projectPath, sDir, "subagents");
91159
91899
  if (!fs2.existsSync(subagentsPath))
91160
91900
  continue;
91161
91901
  const watchAndScan = (dirPath) => {
@@ -91164,7 +91904,7 @@ function startSubagentWatcher(config) {
91164
91904
  const w = fs2.watch(dirPath, (_event, filename) => {
91165
91905
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
91166
91906
  return;
91167
- const filePath = join45(dirPath, filename.toString());
91907
+ const filePath = join46(dirPath, filename.toString());
91168
91908
  if (!knownFiles.has(filePath)) {
91169
91909
  scanSubagentsDir(dirPath);
91170
91910
  }
@@ -91178,7 +91918,7 @@ function startSubagentWatcher(config) {
91178
91918
  scanSubagentsDir(dirPath);
91179
91919
  };
91180
91920
  watchAndScan(subagentsPath);
91181
- const workflowsPath = join45(subagentsPath, "workflows");
91921
+ const workflowsPath = join46(subagentsPath, "workflows");
91182
91922
  if (fs2.existsSync(workflowsPath)) {
91183
91923
  let wfDirs;
91184
91924
  try {
@@ -91188,7 +91928,7 @@ function startSubagentWatcher(config) {
91188
91928
  }
91189
91929
  for (const wfDir of wfDirs) {
91190
91930
  try {
91191
- const wfPath = join45(workflowsPath, wfDir);
91931
+ const wfPath = join46(workflowsPath, wfDir);
91192
91932
  if (!fs2.statSync(wfPath).isDirectory())
91193
91933
  continue;
91194
91934
  watchAndScan(wfPath);
@@ -91208,7 +91948,7 @@ function startSubagentWatcher(config) {
91208
91948
  for (const e of entries) {
91209
91949
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
91210
91950
  continue;
91211
- const filePath = join45(subagentsPath, e);
91951
+ const filePath = join46(subagentsPath, e);
91212
91952
  if (knownFiles.has(filePath))
91213
91953
  continue;
91214
91954
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -91336,37 +92076,37 @@ function startSubagentWatcher(config) {
91336
92076
 
91337
92077
  // ../src/worktree/registry.ts
91338
92078
  import {
91339
- mkdirSync as mkdirSync36,
91340
- writeFileSync as writeFileSync35,
91341
- readFileSync as readFileSync42,
92079
+ mkdirSync as mkdirSync37,
92080
+ writeFileSync as writeFileSync36,
92081
+ readFileSync as readFileSync43,
91342
92082
  readdirSync as readdirSync9,
91343
92083
  unlinkSync as unlinkSync22,
91344
- existsSync as existsSync41,
91345
- renameSync as renameSync17
92084
+ existsSync as existsSync42,
92085
+ renameSync as renameSync18
91346
92086
  } from "node:fs";
91347
- import { join as join46, resolve as resolve10 } from "node:path";
92087
+ import { join as join47, resolve as resolve10 } from "node:path";
91348
92088
  import { homedir as homedir16 } from "node:os";
91349
92089
  function registryDir() {
91350
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join46(homedir16(), ".switchroom", "worktrees"));
92090
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join47(homedir16(), ".switchroom", "worktrees"));
91351
92091
  }
91352
92092
  function recordPath(id) {
91353
- return join46(registryDir(), `${id}.json`);
92093
+ return join47(registryDir(), `${id}.json`);
91354
92094
  }
91355
92095
  function ensureDir4() {
91356
- mkdirSync36(registryDir(), { recursive: true });
92096
+ mkdirSync37(registryDir(), { recursive: true });
91357
92097
  }
91358
92098
  function writeRecord(record) {
91359
92099
  ensureDir4();
91360
92100
  const target = recordPath(record.id);
91361
92101
  const tmp = `${target}.tmp${process.pid}`;
91362
- writeFileSync35(tmp, JSON.stringify(record, null, 2) + `
92102
+ writeFileSync36(tmp, JSON.stringify(record, null, 2) + `
91363
92103
  `, { mode: 384 });
91364
- renameSync17(tmp, target);
92104
+ renameSync18(tmp, target);
91365
92105
  }
91366
92106
  function readRecord(id) {
91367
92107
  const path2 = recordPath(id);
91368
92108
  try {
91369
- const raw = readFileSync42(path2, "utf8");
92109
+ const raw = readFileSync43(path2, "utf8");
91370
92110
  return JSON.parse(raw);
91371
92111
  } catch {
91372
92112
  return null;
@@ -91396,7 +92136,7 @@ function touchHeartbeat(id, onAfterRead) {
91396
92136
  writeRecord({ ...rec, heartbeatAt: new Date().toISOString() });
91397
92137
  }
91398
92138
  function recordExists(id) {
91399
- return existsSync41(recordPath(id));
92139
+ return existsSync42(recordPath(id));
91400
92140
  }
91401
92141
 
91402
92142
  // worktree-watch-cwds.ts
@@ -91528,15 +92268,15 @@ function determineRestartReason(opts) {
91528
92268
  init_boot_card();
91529
92269
 
91530
92270
  // gateway/update-announce.ts
91531
- import { existsSync as existsSync46, mkdirSync as mkdirSync40, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync49 } from "node:fs";
91532
- import { join as join51 } from "node:path";
92271
+ import { existsSync as existsSync47, mkdirSync as mkdirSync41, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync50 } from "node:fs";
92272
+ import { join as join52 } from "node:path";
91533
92273
  import { homedir as homedir18 } from "node:os";
91534
92274
 
91535
92275
  // ../src/host-control/audit-reader.ts
91536
92276
  import { homedir as homedir17 } from "node:os";
91537
- import { join as join50 } from "node:path";
92277
+ import { join as join51 } from "node:path";
91538
92278
  function defaultAuditLogPath(home2 = homedir17()) {
91539
- return join50(home2, ".switchroom", "host-control-audit.log");
92279
+ return join51(home2, ".switchroom", "host-control-audit.log");
91540
92280
  }
91541
92281
  function parseAuditLine(line) {
91542
92282
  const trimmed = line.trim();
@@ -91659,8 +92399,8 @@ function readAndFilter(raw, filters, limit) {
91659
92399
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
91660
92400
  function readLastTerminalUpdateAudit(opts = {}) {
91661
92401
  const path2 = opts.auditLogPath ?? defaultAuditLogPath();
91662
- const exists = opts.exists ?? existsSync46;
91663
- const readFile = opts.readFile ?? ((p) => readFileSync49(p, "utf-8"));
92402
+ const exists = opts.exists ?? existsSync47;
92403
+ const readFile = opts.readFile ?? ((p) => readFileSync50(p, "utf-8"));
91664
92404
  if (!exists(path2))
91665
92405
  return null;
91666
92406
  let raw;
@@ -91721,15 +92461,15 @@ function renderUpdateOutcomeLine(entry) {
91721
92461
  `);
91722
92462
  }
91723
92463
  function claimUpdateAnnouncement(requestId, opts = {}) {
91724
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join51(homedir18(), ".switchroom");
91725
- const dir = join51(stateDir, "update-announced");
92464
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join52(homedir18(), ".switchroom");
92465
+ const dir = join52(stateDir, "update-announced");
91726
92466
  try {
91727
- mkdirSync40(dir, { recursive: true });
92467
+ mkdirSync41(dir, { recursive: true });
91728
92468
  } catch {
91729
92469
  return false;
91730
92470
  }
91731
92471
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
91732
- const path2 = join51(dir, safeId);
92472
+ const path2 = join52(dir, safeId);
91733
92473
  try {
91734
92474
  const fd = openSync9(path2, "wx");
91735
92475
  closeSync9(fd);
@@ -91749,7 +92489,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
91749
92489
 
91750
92490
  // issues-card.ts
91751
92491
  init_card_format();
91752
- import { readFileSync as readFileSync50, writeFileSync as writeFileSync40 } from "node:fs";
92492
+ import { readFileSync as readFileSync51, writeFileSync as writeFileSync41 } from "node:fs";
91753
92493
  var SEVERITY_EMOJI = {
91754
92494
  info: "\u2139\ufe0f",
91755
92495
  warn: "\u26a0\ufe0f",
@@ -91841,7 +92581,7 @@ function extractRetryAfterSecs2(err) {
91841
92581
  var COOLDOWN_JITTER_MS2 = 500;
91842
92582
  function readPersistedMessageId(path2, log) {
91843
92583
  try {
91844
- const raw = readFileSync50(path2, "utf8");
92584
+ const raw = readFileSync51(path2, "utf8");
91845
92585
  const parsed = JSON.parse(raw);
91846
92586
  const v = parsed.messageId;
91847
92587
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -91857,7 +92597,7 @@ function readPersistedMessageId(path2, log) {
91857
92597
  }
91858
92598
  function writePersistedMessageId(path2, messageId, log) {
91859
92599
  try {
91860
- writeFileSync40(path2, JSON.stringify({ messageId }) + `
92600
+ writeFileSync41(path2, JSON.stringify({ messageId }) + `
91861
92601
  `, { mode: 384 });
91862
92602
  } catch (err) {
91863
92603
  log(`issues-card: persist write failed (${err.message})`);
@@ -91950,24 +92690,24 @@ function createIssuesCardHandle(opts) {
91950
92690
  }
91951
92691
 
91952
92692
  // issues-watcher.ts
91953
- import { existsSync as existsSync48, statSync as statSync16 } from "node:fs";
91954
- import { join as join53 } from "node:path";
92693
+ import { existsSync as existsSync49, statSync as statSync16 } from "node:fs";
92694
+ import { join as join54 } from "node:path";
91955
92695
 
91956
92696
  // ../src/issues/store.ts
91957
92697
  import {
91958
92698
  closeSync as closeSync10,
91959
- existsSync as existsSync47,
91960
- mkdirSync as mkdirSync41,
92699
+ existsSync as existsSync48,
92700
+ mkdirSync as mkdirSync42,
91961
92701
  openSync as openSync10,
91962
92702
  readdirSync as readdirSync11,
91963
- readFileSync as readFileSync51,
91964
- renameSync as renameSync20,
92703
+ readFileSync as readFileSync52,
92704
+ renameSync as renameSync21,
91965
92705
  statSync as statSync15,
91966
92706
  unlinkSync as unlinkSync23,
91967
- writeFileSync as writeFileSync41,
92707
+ writeFileSync as writeFileSync42,
91968
92708
  writeSync as writeSync6
91969
92709
  } from "node:fs";
91970
- import { join as join52 } from "node:path";
92710
+ import { join as join53 } from "node:path";
91971
92711
  import { randomBytes as randomBytes8 } from "node:crypto";
91972
92712
  import { execSync } from "node:child_process";
91973
92713
 
@@ -91986,12 +92726,12 @@ init_redact();
91986
92726
  var ISSUES_FILE = "issues.jsonl";
91987
92727
  var ISSUES_LOCK = "issues.lock";
91988
92728
  function readAll(stateDir) {
91989
- const path2 = join52(stateDir, ISSUES_FILE);
91990
- if (!existsSync47(path2))
92729
+ const path2 = join53(stateDir, ISSUES_FILE);
92730
+ if (!existsSync48(path2))
91991
92731
  return [];
91992
92732
  let raw;
91993
92733
  try {
91994
- raw = readFileSync51(path2, "utf-8");
92734
+ raw = readFileSync52(path2, "utf-8");
91995
92735
  } catch {
91996
92736
  return [];
91997
92737
  }
@@ -92023,7 +92763,7 @@ function list2(stateDir, opts = {}) {
92023
92763
  });
92024
92764
  }
92025
92765
  function resolve11(stateDir, fingerprint, nowFn = Date.now) {
92026
- if (!existsSync47(join52(stateDir, ISSUES_FILE)))
92766
+ if (!existsSync48(join53(stateDir, ISSUES_FILE)))
92027
92767
  return 0;
92028
92768
  return withLock(stateDir, () => {
92029
92769
  const all2 = readAll(stateDir);
@@ -92041,14 +92781,14 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
92041
92781
  });
92042
92782
  }
92043
92783
  function writeAll(stateDir, events) {
92044
- const path2 = join52(stateDir, ISSUES_FILE);
92784
+ const path2 = join53(stateDir, ISSUES_FILE);
92045
92785
  sweepOrphanTmpFiles(stateDir);
92046
92786
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes8(4).toString("hex")}`;
92047
92787
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
92048
92788
  `) + `
92049
92789
  `;
92050
- writeFileSync41(tmp, body, "utf-8");
92051
- renameSync20(tmp, path2);
92790
+ writeFileSync42(tmp, body, "utf-8");
92791
+ renameSync21(tmp, path2);
92052
92792
  }
92053
92793
  var ORPHAN_TMP_TTL_MS = 60000;
92054
92794
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -92063,7 +92803,7 @@ function sweepOrphanTmpFiles(stateDir) {
92063
92803
  for (const entry of entries) {
92064
92804
  if (!entry.startsWith(TMP_PREFIX))
92065
92805
  continue;
92066
- const tmpPath2 = join52(stateDir, entry);
92806
+ const tmpPath2 = join53(stateDir, entry);
92067
92807
  try {
92068
92808
  const stat = statSync15(tmpPath2);
92069
92809
  if (stat.mtimeMs < cutoff) {
@@ -92075,7 +92815,7 @@ function sweepOrphanTmpFiles(stateDir) {
92075
92815
  var LOCK_RETRY_MS = 25;
92076
92816
  var LOCK_TIMEOUT_MS = 1e4;
92077
92817
  function withLock(stateDir, fn) {
92078
- const lockPath = join52(stateDir, ISSUES_LOCK);
92818
+ const lockPath = join53(stateDir, ISSUES_LOCK);
92079
92819
  const startedAt = Date.now();
92080
92820
  let fd = null;
92081
92821
  while (fd === null) {
@@ -92110,7 +92850,7 @@ function withLock(stateDir, fn) {
92110
92850
  function tryStealStaleLock(lockPath) {
92111
92851
  let pidStr;
92112
92852
  try {
92113
- pidStr = readFileSync51(lockPath, "utf-8").trim();
92853
+ pidStr = readFileSync52(lockPath, "utf-8").trim();
92114
92854
  } catch {
92115
92855
  return true;
92116
92856
  }
@@ -92160,7 +92900,7 @@ function isIssueEvent(v) {
92160
92900
  // issues-watcher.ts
92161
92901
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
92162
92902
  function startIssuesWatcher(opts) {
92163
- const path2 = join53(opts.stateDir, ISSUES_FILE);
92903
+ const path2 = join54(opts.stateDir, ISSUES_FILE);
92164
92904
  const log = opts.log ?? (() => {});
92165
92905
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
92166
92906
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -92208,7 +92948,7 @@ function startIssuesWatcher(opts) {
92208
92948
  };
92209
92949
  }
92210
92950
  function defaultSignatureProvider(path2) {
92211
- if (!existsSync48(path2))
92951
+ if (!existsSync49(path2))
92212
92952
  return null;
92213
92953
  try {
92214
92954
  const stat = statSync16(path2);
@@ -92919,8 +93659,8 @@ function readBashCommand(inputPreview) {
92919
93659
  }
92920
93660
 
92921
93661
  // gateway/scoped-grant-store.ts
92922
- import { readFileSync as readFileSync52, writeFileSync as writeFileSync42 } from "node:fs";
92923
- import { join as join54 } from "node:path";
93662
+ import { readFileSync as readFileSync53, writeFileSync as writeFileSync43 } from "node:fs";
93663
+ import { join as join55 } from "node:path";
92924
93664
 
92925
93665
  // scoped-approval.ts
92926
93666
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -92962,11 +93702,11 @@ function scopedGrantPersistEnabled(env = process.env) {
92962
93702
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
92963
93703
  }
92964
93704
  function createScopedGrantStore(stateDir, env = process.env) {
92965
- const filePath = join54(stateDir, "scoped-grants.json");
93705
+ const filePath = join55(stateDir, "scoped-grants.json");
92966
93706
  const enabled7 = scopedGrantPersistEnabled(env);
92967
93707
  function read() {
92968
93708
  try {
92969
- const raw = readFileSync52(filePath, "utf-8");
93709
+ const raw = readFileSync53(filePath, "utf-8");
92970
93710
  const parsed = JSON.parse(raw);
92971
93711
  return Array.isArray(parsed) ? parsed : [];
92972
93712
  } catch {
@@ -92984,7 +93724,7 @@ function createScopedGrantStore(stateDir, env = process.env) {
92984
93724
  if (!enabled7)
92985
93725
  return;
92986
93726
  try {
92987
- writeFileSync42(filePath, JSON.stringify(serializeScopedGrants(store2)), {
93727
+ writeFileSync43(filePath, JSON.stringify(serializeScopedGrants(store2)), {
92988
93728
  encoding: "utf-8",
92989
93729
  mode: 384
92990
93730
  });
@@ -93335,8 +94075,8 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
93335
94075
 
93336
94076
  // credits-watch.ts
93337
94077
  init_card_format();
93338
- import { readFileSync as readFileSync53, writeFileSync as writeFileSync43, existsSync as existsSync49, mkdirSync as mkdirSync42 } from "fs";
93339
- import { join as join55 } from "path";
94078
+ import { readFileSync as readFileSync54, writeFileSync as writeFileSync44, existsSync as existsSync50, mkdirSync as mkdirSync43 } from "fs";
94079
+ import { join as join56 } from "path";
93340
94080
  var STATE_FILE = "credits-watch.json";
93341
94081
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
93342
94082
  var KNOWN_CREDIT_REASONS = [
@@ -93358,12 +94098,12 @@ function emptyCreditState() {
93358
94098
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
93359
94099
  }
93360
94100
  function readClaudeJsonOverage(claudeConfigDir) {
93361
- const path2 = join55(claudeConfigDir, ".claude.json");
93362
- if (!existsSync49(path2))
94101
+ const path2 = join56(claudeConfigDir, ".claude.json");
94102
+ if (!existsSync50(path2))
93363
94103
  return null;
93364
94104
  let raw;
93365
94105
  try {
93366
- raw = readFileSync53(path2, "utf-8");
94106
+ raw = readFileSync54(path2, "utf-8");
93367
94107
  } catch {
93368
94108
  return null;
93369
94109
  }
@@ -93441,11 +94181,11 @@ function humanizeReason(reason) {
93441
94181
  }
93442
94182
  }
93443
94183
  function loadCreditState(stateDir) {
93444
- const path2 = join55(stateDir, STATE_FILE);
93445
- if (!existsSync49(path2))
94184
+ const path2 = join56(stateDir, STATE_FILE);
94185
+ if (!existsSync50(path2))
93446
94186
  return emptyCreditState();
93447
94187
  try {
93448
- const raw = readFileSync53(path2, "utf-8");
94188
+ const raw = readFileSync54(path2, "utf-8");
93449
94189
  const parsed = JSON.parse(raw);
93450
94190
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
93451
94191
  return {
@@ -93457,17 +94197,17 @@ function loadCreditState(stateDir) {
93457
94197
  return emptyCreditState();
93458
94198
  }
93459
94199
  function saveCreditState(stateDir, state7) {
93460
- mkdirSync42(stateDir, { recursive: true });
93461
- const path2 = join55(stateDir, STATE_FILE);
93462
- writeFileSync43(path2, JSON.stringify(state7, null, 2) + `
94200
+ mkdirSync43(stateDir, { recursive: true });
94201
+ const path2 = join56(stateDir, STATE_FILE);
94202
+ writeFileSync44(path2, JSON.stringify(state7, null, 2) + `
93463
94203
  `, { mode: 384 });
93464
94204
  }
93465
94205
 
93466
94206
  // quota-watch.ts
93467
94207
  init_auth_snapshot_format();
93468
94208
  init_card_format();
93469
- import { readFileSync as readFileSync54, writeFileSync as writeFileSync44, existsSync as existsSync50, mkdirSync as mkdirSync43 } from "fs";
93470
- import { join as join56 } from "path";
94209
+ import { readFileSync as readFileSync55, writeFileSync as writeFileSync45, existsSync as existsSync51, mkdirSync as mkdirSync44 } from "fs";
94210
+ import { join as join57 } from "path";
93471
94211
  var STATE_FILE2 = "quota-watch.json";
93472
94212
  function emptyQuotaWatchState() {
93473
94213
  return {};
@@ -93705,11 +94445,11 @@ function buildRecoveryMessage(agentName3, snap) {
93705
94445
  `);
93706
94446
  }
93707
94447
  function loadQuotaWatchState(stateDir) {
93708
- const path2 = join56(stateDir, STATE_FILE2);
93709
- if (!existsSync50(path2))
94448
+ const path2 = join57(stateDir, STATE_FILE2);
94449
+ if (!existsSync51(path2))
93710
94450
  return emptyQuotaWatchState();
93711
94451
  try {
93712
- const raw = readFileSync54(path2, "utf-8");
94452
+ const raw = readFileSync55(path2, "utf-8");
93713
94453
  const parsed = JSON.parse(raw);
93714
94454
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
93715
94455
  return emptyQuotaWatchState();
@@ -93726,9 +94466,9 @@ function loadQuotaWatchState(stateDir) {
93726
94466
  }
93727
94467
  }
93728
94468
  function saveQuotaWatchState(stateDir, state7) {
93729
- mkdirSync43(stateDir, { recursive: true });
93730
- const path2 = join56(stateDir, STATE_FILE2);
93731
- writeFileSync44(path2, JSON.stringify(state7, null, 2) + `
94469
+ mkdirSync44(stateDir, { recursive: true });
94470
+ const path2 = join57(stateDir, STATE_FILE2);
94471
+ writeFileSync45(path2, JSON.stringify(state7, null, 2) + `
93732
94472
  `, { mode: 384 });
93733
94473
  }
93734
94474
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -93776,27 +94516,27 @@ init_auth_snapshot_format2();
93776
94516
  // gateway/turn-active-marker.ts
93777
94517
  import {
93778
94518
  closeSync as closeSync11,
93779
- existsSync as existsSync51,
93780
- mkdirSync as mkdirSync44,
94519
+ existsSync as existsSync52,
94520
+ mkdirSync as mkdirSync45,
93781
94521
  openSync as openSync11,
93782
- readFileSync as readFileSync55,
94522
+ readFileSync as readFileSync56,
93783
94523
  statSync as statSync17,
93784
94524
  unlinkSync as unlinkSync24,
93785
94525
  utimesSync as utimesSync2,
93786
- writeFileSync as writeFileSync45
94526
+ writeFileSync as writeFileSync46
93787
94527
  } from "node:fs";
93788
- import { join as join57 } from "node:path";
94528
+ import { join as join58 } from "node:path";
93789
94529
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
93790
94530
  var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
93791
94531
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
93792
94532
  function removeTurnActiveMarker2(stateDir) {
93793
94533
  try {
93794
- unlinkSync24(join57(stateDir, TURN_ACTIVE_MARKER_FILE2));
94534
+ unlinkSync24(join58(stateDir, TURN_ACTIVE_MARKER_FILE2));
93795
94535
  } catch {}
93796
94536
  }
93797
94537
  function sweepStaleTurnActiveMarker(stateDir, opts) {
93798
- const path2 = join57(stateDir, TURN_ACTIVE_MARKER_FILE2);
93799
- if (!existsSync51(path2))
94538
+ const path2 = join58(stateDir, TURN_ACTIVE_MARKER_FILE2);
94539
+ if (!existsSync52(path2))
93800
94540
  return false;
93801
94541
  const now = opts.now ?? Date.now();
93802
94542
  try {
@@ -93808,7 +94548,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
93808
94548
  return false;
93809
94549
  let payload = null;
93810
94550
  try {
93811
- payload = readFileSync55(path2, "utf8");
94551
+ payload = readFileSync56(path2, "utf8");
93812
94552
  } catch {}
93813
94553
  unlinkSync24(path2);
93814
94554
  if (opts.onRemove) {
@@ -93826,7 +94566,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
93826
94566
  }
93827
94567
  }
93828
94568
  function readTurnActiveMarkerAgeMs2(stateDir, now) {
93829
- const path2 = join57(stateDir, TURN_ACTIVE_MARKER_FILE2);
94569
+ const path2 = join58(stateDir, TURN_ACTIVE_MARKER_FILE2);
93830
94570
  try {
93831
94571
  const st = statSync17(path2);
93832
94572
  return (now ?? Date.now()) - st.mtimeMs;
@@ -93839,19 +94579,19 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
93839
94579
  }
93840
94580
 
93841
94581
  // gateway/gateway-heartbeat.ts
93842
- import { mkdirSync as mkdirSync45, utimesSync as utimesSync3, writeFileSync as writeFileSync46 } from "node:fs";
93843
- import { join as join58 } from "node:path";
94582
+ import { mkdirSync as mkdirSync46, utimesSync as utimesSync3, writeFileSync as writeFileSync47 } from "node:fs";
94583
+ import { join as join59 } from "node:path";
93844
94584
  var GATEWAY_HEARTBEAT_FILE = "gateway-heartbeat";
93845
94585
  var GATEWAY_HEARTBEAT_INTERVAL_MS = 15000;
93846
94586
  function touchGatewayHeartbeat(stateDir) {
93847
- const path2 = join58(stateDir, GATEWAY_HEARTBEAT_FILE);
94587
+ const path2 = join59(stateDir, GATEWAY_HEARTBEAT_FILE);
93848
94588
  const now = new Date;
93849
94589
  try {
93850
94590
  utimesSync3(path2, now, now);
93851
94591
  } catch {
93852
94592
  try {
93853
- mkdirSync45(stateDir, { recursive: true });
93854
- writeFileSync46(path2, `${Date.now()}
94593
+ mkdirSync46(stateDir, { recursive: true });
94594
+ writeFileSync47(path2, `${Date.now()}
93855
94595
  `, { mode: 384 });
93856
94596
  } catch {}
93857
94597
  }
@@ -93894,11 +94634,23 @@ async function sweepOutbox(deps) {
93894
94634
  textAlreadyDelivered: resolved != null && deps.textAlreadyDelivered(resolved.chatId, resolved.threadId, record2.text),
93895
94635
  routable: resolved != null,
93896
94636
  routePrefix,
93897
- quietMs: deps.quietMs ?? OUTBOX_QUIET_MS
94637
+ quietMs: deps.quietMs ?? OUTBOX_QUIET_MS,
94638
+ shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir)
93898
94639
  });
93899
94640
  if (decision.action !== "send" && decision.action !== "send-delayed") {
93900
94641
  summary.skipped++;
93901
- if (decision.action === "skip-journaled") {
94642
+ if (decision.action === "skip-ephemeral-shown") {
94643
+ appendDelivered({
94644
+ turnNonce: record2.turnNonce,
94645
+ textSha256: record2.textSha256,
94646
+ ts: now,
94647
+ deliverySource: "sweep",
94648
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true
94649
+ }, deps.stateDir);
94650
+ clearOutboxRecord(record2.turnNonce, deps.stateDir);
94651
+ log(`outbox-sweep: suppressed ephemeral-shown nonce=${record2.turnNonce}
94652
+ `);
94653
+ } else if (decision.action === "skip-journaled") {
93902
94654
  clearOutboxRecord(record2.turnNonce, deps.stateDir);
93903
94655
  } else if (decision.action === "skip-dedup") {
93904
94656
  appendDelivered({
@@ -93949,10 +94701,38 @@ function chatFromTurnKey(turnKey3) {
93949
94701
  return { chatId, threadId: Number.isFinite(t) ? t : null };
93950
94702
  }
93951
94703
  var OUTBOX_SWEEP_INTERVAL_MS = 5000;
94704
+ function createOutboxSend(deps) {
94705
+ return async (chatId, threadId, text4) => {
94706
+ const bot = deps.getBot();
94707
+ if (bot == null)
94708
+ throw new Error("outbox-sweep: bot unavailable");
94709
+ if (text4.length === 0)
94710
+ return;
94711
+ const replyMarkup = deps.resolveReplyMarkup?.(chatId, threadId, text4);
94712
+ let lastId;
94713
+ const chunkCount = Math.ceil(text4.length / 4000);
94714
+ for (let i = 0, idx = 0;i < text4.length; i += 4000, idx++) {
94715
+ const chunk2 = text4.slice(i, i + 4000);
94716
+ const isLast = idx === chunkCount - 1;
94717
+ const res = await retryWithThreadFallback(deps.retry, (tid) => {
94718
+ const base = tid != null ? { message_thread_id: tid } : {};
94719
+ const opts = isLast && replyMarkup != null ? { ...base, reply_markup: replyMarkup } : base;
94720
+ return bot.api.sendMessage(chatId, chunk2, opts);
94721
+ }, { threadId: threadId ?? undefined, chat_id: chatId, verb: "outbox-sweep.sendMessage" });
94722
+ lastId = res?.message_id;
94723
+ }
94724
+ return lastId;
94725
+ };
94726
+ }
93952
94727
  function startOutboxSweep(deps) {
93953
94728
  if (!deps.isGatewayMain || process.env.SWITCHROOM_TG_OUTBOX_DELIVERY === "0")
93954
94729
  return;
93955
94730
  const retry = createRetryApiCall({ log: deps.log });
94731
+ const send = createOutboxSend({
94732
+ getBot: deps.getBot,
94733
+ retry,
94734
+ ...deps.resolveReplyMarkup != null ? { resolveReplyMarkup: deps.resolveReplyMarkup } : {}
94735
+ });
93956
94736
  const tick3 = () => {
93957
94737
  const bot = deps.getBot();
93958
94738
  if (bot == null)
@@ -93960,15 +94740,7 @@ function startOutboxSweep(deps) {
93960
94740
  sweepOutbox({
93961
94741
  stateDir: deps.stateDir,
93962
94742
  log: deps.log,
93963
- send: async (chatId, threadId, text4) => {
93964
- let lastId;
93965
- for (let i = 0;i < text4.length; i += 4000) {
93966
- const chunk2 = text4.slice(i, i + 4000);
93967
- const res = await retryWithThreadFallback(retry, (tid) => bot.api.sendMessage(chatId, chunk2, tid != null ? { message_thread_id: tid } : {}), { threadId: threadId ?? undefined, chat_id: chatId, verb: "outbox-sweep.sendMessage" });
93968
- lastId = res?.message_id;
93969
- }
93970
- return lastId;
93971
- },
94743
+ send,
93972
94744
  textAlreadyDelivered: (chatId, threadId, text4) => deps.dedupCheck(chatId, threadId ?? undefined, text4),
93973
94745
  registryChainLookup: (taskId) => {
93974
94746
  const db3 = deps.getTurnsDb();
@@ -93986,10 +94758,10 @@ function startOutboxSweep(deps) {
93986
94758
  }
93987
94759
 
93988
94760
  // ../src/build-info.ts
93989
- var VERSION = "0.19.14";
93990
- var COMMIT_SHA = "1f1f69f8";
93991
- var COMMIT_DATE = "2026-07-24T07:19:40+10:00";
93992
- var LATEST_PR = 3514;
94761
+ var VERSION = "0.19.16";
94762
+ var COMMIT_SHA = "e905f237";
94763
+ var COMMIT_DATE = "2026-07-24T18:26:03+10:00";
94764
+ var LATEST_PR = null;
93993
94765
  var COMMITS_AHEAD_OF_TAG = 0;
93994
94766
 
93995
94767
  // gateway/boot-version.ts
@@ -94067,11 +94839,11 @@ init_peercred();
94067
94839
  import * as net5 from "node:net";
94068
94840
  import * as fs2 from "node:fs";
94069
94841
  import { homedir as homedir19 } from "node:os";
94070
- import { join as join59 } from "node:path";
94842
+ import { join as join60 } from "node:path";
94071
94843
  var DEFAULT_TIMEOUT_MS4 = 2000;
94072
94844
  var UNLOCK_TIMEOUT_MS = 30000;
94073
- var LEGACY_SOCKET_PATH2 = join59(homedir19(), ".switchroom", "vault-broker.sock");
94074
- var OPERATOR_SOCKET_PATH2 = join59(homedir19(), ".switchroom", "broker-operator", "sock");
94845
+ var LEGACY_SOCKET_PATH2 = join60(homedir19(), ".switchroom", "vault-broker.sock");
94846
+ var OPERATOR_SOCKET_PATH2 = join60(homedir19(), ".switchroom", "broker-operator", "sock");
94075
94847
  function defaultBrokerSocketPath2() {
94076
94848
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
94077
94849
  return OPERATOR_SOCKET_PATH2;
@@ -94953,8 +95725,8 @@ function resolveVaultApprovalPosture(broker) {
94953
95725
  }
94954
95726
 
94955
95727
  // registry/turns-schema.ts
94956
- import { chmodSync as chmodSync12, mkdirSync as mkdirSync46 } from "fs";
94957
- import { join as join60 } from "path";
95728
+ import { chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "fs";
95729
+ import { join as join61 } from "path";
94958
95730
  var DatabaseClass3 = null;
94959
95731
  function loadDatabaseClass3() {
94960
95732
  if (DatabaseClass3 != null)
@@ -95026,9 +95798,9 @@ function applySchema(db3) {
95026
95798
  }
95027
95799
  function openTurnsDb(agentDir) {
95028
95800
  const Database = loadDatabaseClass3();
95029
- const dir = join60(agentDir, "telegram");
95030
- mkdirSync46(dir, { recursive: true, mode: 448 });
95031
- const path2 = join60(dir, "registry.db");
95801
+ const dir = join61(agentDir, "telegram");
95802
+ mkdirSync47(dir, { recursive: true, mode: 448 });
95803
+ const path2 = join61(dir, "registry.db");
95032
95804
  const db3 = new Database(path2, { create: true });
95033
95805
  applySchema(db3);
95034
95806
  try {
@@ -95372,7 +96144,7 @@ function selectResumeBuilder(endedVia, opts) {
95372
96144
  }
95373
96145
 
95374
96146
  // gateway/bridge-dead-watchdog.ts
95375
- import { readFileSync as readFileSync57, writeFileSync as writeFileSync47, renameSync as renameSync21, unlinkSync as unlinkSync25 } from "node:fs";
96147
+ import { readFileSync as readFileSync58, writeFileSync as writeFileSync48, renameSync as renameSync22, unlinkSync as unlinkSync25 } from "node:fs";
95376
96148
 
95377
96149
  // gateway/cron-session.ts
95378
96150
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -95405,7 +96177,7 @@ function readFreshCrashLogTail(path2, opts = {}) {
95405
96177
  const nowMs3 = opts.nowMs ?? Date.now();
95406
96178
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
95407
96179
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
95408
- const readFile = opts.readFile ?? ((p) => readFileSync57(p, "utf8"));
96180
+ const readFile = opts.readFile ?? ((p) => readFileSync58(p, "utf8"));
95409
96181
  let raw;
95410
96182
  try {
95411
96183
  raw = readFile(path2);
@@ -95426,13 +96198,13 @@ function readFreshCrashLogTail(path2, opts = {}) {
95426
96198
  }
95427
96199
  function writeBridgeDeadEscalationMarker(path2, marker) {
95428
96200
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
95429
- writeFileSync47(tmp, JSON.stringify(marker), "utf8");
95430
- renameSync21(tmp, path2);
96201
+ writeFileSync48(tmp, JSON.stringify(marker), "utf8");
96202
+ renameSync22(tmp, path2);
95431
96203
  }
95432
96204
  function consumeBridgeDeadEscalationMarker(path2, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
95433
96205
  let marker = null;
95434
96206
  try {
95435
- const parsed = JSON.parse(readFileSync57(path2, "utf8"));
96207
+ const parsed = JSON.parse(readFileSync58(path2, "utf8"));
95436
96208
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
95437
96209
  const age = nowMs3 - parsed.ts;
95438
96210
  if (age >= 0 && age < maxAgeMs) {
@@ -95585,7 +96357,7 @@ function createBridgeDeadWatchdog(opts) {
95585
96357
  }
95586
96358
 
95587
96359
  // gateway/boot-probes.ts
95588
- import { readFileSync as readFileSync58, readdirSync as readdirSync12, existsSync as existsSync53 } from "fs";
96360
+ import { readFileSync as readFileSync59, readdirSync as readdirSync12, existsSync as existsSync54 } from "fs";
95589
96361
  init_quota_cache();
95590
96362
  init_quota_check();
95591
96363
  import { execFile as execFileCb2 } from "child_process";
@@ -95593,7 +96365,7 @@ import { promisify as promisify2 } from "util";
95593
96365
  var execFile2 = promisify2(execFileCb2);
95594
96366
  var realProcFs2 = {
95595
96367
  readdir: (p) => readdirSync12(p),
95596
- readFile: (p) => readFileSync58(p, "utf-8")
96368
+ readFile: (p) => readFileSync59(p, "utf-8")
95597
96369
  };
95598
96370
  function findAgentProcessInContainer2(fs3 = realProcFs2) {
95599
96371
  let entries;
@@ -95843,7 +96615,7 @@ if (isGatewayMain) {
95843
96615
  shutdownAnalytics();
95844
96616
  });
95845
96617
  }
95846
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join61(homedir20(), ".claude", "channels", "telegram");
96618
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join62(homedir20(), ".claude", "channels", "telegram");
95847
96619
  var permCardStore = createPermissionCardStore(STATE_DIR);
95848
96620
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
95849
96621
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -95882,7 +96654,7 @@ function alwaysAllowDrainDeps() {
95882
96654
  return {
95883
96655
  readConfigText: () => {
95884
96656
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
95885
- return readFileSync59(cfgPath, "utf8");
96657
+ return readFileSync60(cfgPath, "utf8");
95886
96658
  },
95887
96659
  resolveAllowList: (_configText, agentName3) => {
95888
96660
  const cfg = loadConfig2();
@@ -95943,11 +96715,11 @@ function scheduleAlwaysAllowPersistDrain() {
95943
96715
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
95944
96716
  timer3.unref?.();
95945
96717
  }
95946
- var ACCESS_FILE = join61(STATE_DIR, "access.json");
95947
- var APPROVED_DIR = join61(STATE_DIR, "approved");
95948
- var ENV_FILE = join61(STATE_DIR, ".env");
95949
- var INBOX_DIR = join61(STATE_DIR, "inbox");
95950
- var PEOPLE_FILE = join61(STATE_DIR, "people.json");
96718
+ var ACCESS_FILE = join62(STATE_DIR, "access.json");
96719
+ var APPROVED_DIR = join62(STATE_DIR, "approved");
96720
+ var ENV_FILE = join62(STATE_DIR, ".env");
96721
+ var INBOX_DIR = join62(STATE_DIR, "inbox");
96722
+ var PEOPLE_FILE = join62(STATE_DIR, "people.json");
95951
96723
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
95952
96724
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
95953
96725
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -96012,7 +96784,7 @@ function formatBootVersion() {
96012
96784
  }
96013
96785
  try {
96014
96786
  chmodSync14(ENV_FILE, 384);
96015
- for (const line of readFileSync59(ENV_FILE, "utf8").split(`
96787
+ for (const line of readFileSync60(ENV_FILE, "utf8").split(`
96016
96788
  `)) {
96017
96789
  const m = line.match(/^(\w+)=(.*)$/);
96018
96790
  if (m && process.env[m[1]] === undefined)
@@ -96033,7 +96805,7 @@ var bot;
96033
96805
  var lastGetUpdatesHeartbeatMs = Date.now();
96034
96806
  var GRAMMY_VERSION = (() => {
96035
96807
  try {
96036
- const raw = readFileSync59(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
96808
+ const raw = readFileSync60(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
96037
96809
  return JSON.parse(raw).version ?? "unknown";
96038
96810
  } catch {
96039
96811
  return "unknown";
@@ -96096,7 +96868,7 @@ function assertSendable(f) {
96096
96868
  } catch {
96097
96869
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
96098
96870
  }
96099
- const inbox = join61(stateReal, "inbox");
96871
+ const inbox = join62(stateReal, "inbox");
96100
96872
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
96101
96873
  throw new Error(`refusing to send channel state: ${f}`);
96102
96874
  }
@@ -96115,7 +96887,7 @@ function assertSendable(f) {
96115
96887
  }
96116
96888
  function readAccessFile() {
96117
96889
  try {
96118
- const raw = readFileSync59(ACCESS_FILE, "utf8");
96890
+ const raw = readFileSync60(ACCESS_FILE, "utf8");
96119
96891
  const parsed = JSON.parse(raw);
96120
96892
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
96121
96893
  const groups = {};
@@ -96155,7 +96927,7 @@ function readAccessFile() {
96155
96927
  if (err.code === "ENOENT")
96156
96928
  return defaultAccess();
96157
96929
  try {
96158
- renameSync22(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
96930
+ renameSync23(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
96159
96931
  } catch {}
96160
96932
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
96161
96933
  `);
@@ -96177,7 +96949,7 @@ function loadAccess() {
96177
96949
  }
96178
96950
  function readPeopleFile() {
96179
96951
  try {
96180
- const raw = readFileSync59(PEOPLE_FILE, "utf8");
96952
+ const raw = readFileSync60(PEOPLE_FILE, "utf8");
96181
96953
  const parsed = JSON.parse(raw);
96182
96954
  if (!Array.isArray(parsed.entries))
96183
96955
  return [];
@@ -96199,11 +96971,11 @@ function assertAllowedChat(chat_id) {
96199
96971
  function saveAccess(a) {
96200
96972
  if (STATIC)
96201
96973
  return;
96202
- mkdirSync48(STATE_DIR, { recursive: true, mode: 448 });
96974
+ mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
96203
96975
  const tmp = ACCESS_FILE + ".tmp";
96204
- writeFileSync49(tmp, JSON.stringify(a, null, 2) + `
96976
+ writeFileSync50(tmp, JSON.stringify(a, null, 2) + `
96205
96977
  `, { mode: 384 });
96206
- renameSync22(tmp, ACCESS_FILE);
96978
+ renameSync23(tmp, ACCESS_FILE);
96207
96979
  }
96208
96980
  function pruneExpired(a) {
96209
96981
  const now = Date.now();
@@ -96221,7 +96993,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
96221
96993
  if (isGatewayMain && HISTORY_ENABLED) {
96222
96994
  try {
96223
96995
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
96224
- process.stderr.write(`telegram gateway: history capture enabled at ${join61(STATE_DIR, "history.db")}
96996
+ process.stderr.write(`telegram gateway: history capture enabled at ${join62(STATE_DIR, "history.db")}
96225
96997
  `);
96226
96998
  } catch (err) {
96227
96999
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -96240,12 +97012,12 @@ if (isGatewayMain)
96240
97012
  let markerTurnKey = null;
96241
97013
  let markerAgeMs = null;
96242
97014
  try {
96243
- const markerPath = join61(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
96244
- if (existsSync55(markerPath)) {
97015
+ const markerPath = join62(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
97016
+ if (existsSync56(markerPath)) {
96245
97017
  const st = statSync20(markerPath);
96246
97018
  markerAgeMs = Date.now() - st.mtimeMs;
96247
97019
  try {
96248
- const payload = JSON.parse(readFileSync59(markerPath, "utf8"));
97020
+ const payload = JSON.parse(readFileSync60(markerPath, "utf8"));
96249
97021
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
96250
97022
  markerTurnKey = payload.turnKey;
96251
97023
  }
@@ -96265,10 +97037,10 @@ if (isGatewayMain)
96265
97037
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
96266
97038
  `);
96267
97039
  } else {
96268
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join61(agentDir, "telegram", "registry.db")}
97040
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join62(agentDir, "telegram", "registry.db")}
96269
97041
  `);
96270
97042
  }
96271
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join61(STATE_DIR, "bridge-dead-escalation.json"));
97043
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join62(STATE_DIR, "bridge-dead-escalation.json"));
96272
97044
  if (bridgeDeadMarker != null) {
96273
97045
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
96274
97046
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -96281,7 +97053,7 @@ if (isGatewayMain)
96281
97053
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
96282
97054
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
96283
97055
  if (pending2 != null && selfAgent) {
96284
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join61(STATE_DIR, "clean-shutdown.json");
97056
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join62(STATE_DIR, "clean-shutdown.json");
96285
97057
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
96286
97058
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
96287
97059
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -96394,7 +97166,7 @@ if (isGatewayMain)
96394
97166
  `);
96395
97167
  }
96396
97168
  }
96397
- const pendingEnvPath = join61(agentDir, ".pending-turn.env");
97169
+ const pendingEnvPath = join62(agentDir, ".pending-turn.env");
96398
97170
  try {
96399
97171
  if (pending2 != null) {
96400
97172
  const lines = [
@@ -96408,13 +97180,13 @@ if (isGatewayMain)
96408
97180
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
96409
97181
  ];
96410
97182
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
96411
- writeFileSync49(pendingEnvTmp, lines.join(`
97183
+ writeFileSync50(pendingEnvTmp, lines.join(`
96412
97184
  `) + `
96413
97185
  `, { mode: 384 });
96414
- renameSync22(pendingEnvTmp, pendingEnvPath);
97186
+ renameSync23(pendingEnvTmp, pendingEnvPath);
96415
97187
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
96416
97188
  `);
96417
- } else if (existsSync55(pendingEnvPath)) {
97189
+ } else if (existsSync56(pendingEnvPath)) {
96418
97190
  rmSync6(pendingEnvPath, { force: true });
96419
97191
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
96420
97192
  `);
@@ -96522,7 +97294,7 @@ function checkApprovals() {
96522
97294
  return;
96523
97295
  }
96524
97296
  for (const senderId of files) {
96525
- const file = join61(APPROVED_DIR, senderId);
97297
+ const file = join62(APPROVED_DIR, senderId);
96526
97298
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync6(file, { force: true }), (err) => {
96527
97299
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
96528
97300
  `);
@@ -96695,12 +97467,12 @@ function noteAgentOutputAt(key, ts) {
96695
97467
  lastAgentOutputAt.delete(oldest);
96696
97468
  }
96697
97469
  }
96698
- var OBLIGATION_STORE_PATH = join61(STATE_DIR, "obligations.json");
97470
+ var OBLIGATION_STORE_PATH = join62(STATE_DIR, "obligations.json");
96699
97471
  var obligationStoreFs = {
96700
- readFileSync: (p) => readFileSync59(p, "utf8"),
96701
- writeFileSync: (p, d) => writeFileSync49(p, d),
96702
- renameSync: (a, b) => renameSync22(a, b),
96703
- existsSync: (p) => existsSync55(p)
97472
+ readFileSync: (p) => readFileSync60(p, "utf8"),
97473
+ writeFileSync: (p, d) => writeFileSync50(p, d),
97474
+ renameSync: (a, b) => renameSync23(a, b),
97475
+ existsSync: (p) => existsSync56(p)
96704
97476
  };
96705
97477
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
96706
97478
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -97479,9 +98251,9 @@ function emitTurnRecord(turn, endedAt) {
97479
98251
  return;
97480
98252
  }
97481
98253
  },
97482
- rename: (from, to) => renameSync22(from, to)
98254
+ rename: (from, to) => renameSync23(from, to)
97483
98255
  });
97484
- appendFileSync8(turnsPath, rec);
98256
+ appendFileSync9(turnsPath, rec);
97485
98257
  } catch {}
97486
98258
  }
97487
98259
  function maybeProactiveCompact() {
@@ -98107,7 +98879,7 @@ function sweepStaleAlwaysAllowCorrelations(now = Date.now()) {
98107
98879
  var MENTAL_MODEL_CORRELATION_TTL_MS = 720000;
98108
98880
  var pendingMentalModelCorrelations = createSweepableStore((entry, now) => now - entry.createdAt > MENTAL_MODEL_CORRELATION_TTL_MS);
98109
98881
  function mentalModelCorrelationKey(agentName3, unifiedDiff) {
98110
- return `${agentName3}::${createHash5("sha256").update(unifiedDiff).digest("hex")}`;
98882
+ return `${agentName3}::${createHash6("sha256").update(unifiedDiff).digest("hex")}`;
98111
98883
  }
98112
98884
  function sweepStaleMentalModelCorrelations(now = Date.now()) {
98113
98885
  pendingMentalModelCorrelations.sweep(now);
@@ -99043,28 +99815,28 @@ var statusPinState = new Map;
99043
99815
  var statusPinChatIds = new Map;
99044
99816
  var statusPinPinnedAt = new Map;
99045
99817
  var statusPinRightsCache = new PinRightsCache2;
99046
- var STATUS_PIN_STORE_PATH = join61(STATE_DIR, "status-pins.json");
99818
+ var STATUS_PIN_STORE_PATH = join62(STATE_DIR, "status-pins.json");
99047
99819
  var statusPinStoreFs = {
99048
- readFileSync: (p) => readFileSync59(p, "utf8"),
99049
- writeFileSync: (p, d) => writeFileSync49(p, d),
99050
- renameSync: (a, b) => renameSync22(a, b),
99051
- existsSync: (p) => existsSync55(p)
99820
+ readFileSync: (p) => readFileSync60(p, "utf8"),
99821
+ writeFileSync: (p, d) => writeFileSync50(p, d),
99822
+ renameSync: (a, b) => renameSync23(a, b),
99823
+ existsSync: (p) => existsSync56(p)
99052
99824
  };
99053
99825
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
99054
- var ACTIVITY_CARD_STORE_PATH = join61(STATE_DIR, "activity-cards-pending.json");
99826
+ var ACTIVITY_CARD_STORE_PATH = join62(STATE_DIR, "activity-cards-pending.json");
99055
99827
  var activityCardStoreFs = {
99056
- readFileSync: (p) => readFileSync59(p, "utf8"),
99057
- writeFileSync: (p, d) => writeFileSync49(p, d),
99058
- renameSync: (a, b) => renameSync22(a, b),
99059
- existsSync: (p) => existsSync55(p)
99828
+ readFileSync: (p) => readFileSync60(p, "utf8"),
99829
+ writeFileSync: (p, d) => writeFileSync50(p, d),
99830
+ renameSync: (a, b) => renameSync23(a, b),
99831
+ existsSync: (p) => existsSync56(p)
99060
99832
  };
99061
99833
  var activityCardPersistEnabled = !STATIC;
99062
- var QUEUED_CARD_STORE_PATH = join61(STATE_DIR, "queued-cards-pending.json");
99834
+ var QUEUED_CARD_STORE_PATH = join62(STATE_DIR, "queued-cards-pending.json");
99063
99835
  var queuedCardStoreFs = {
99064
- readFileSync: (p) => readFileSync59(p, "utf8"),
99065
- writeFileSync: (p, d) => writeFileSync49(p, d),
99066
- renameSync: (a, b) => renameSync22(a, b),
99067
- existsSync: (p) => existsSync55(p)
99836
+ readFileSync: (p) => readFileSync60(p, "utf8"),
99837
+ writeFileSync: (p, d) => writeFileSync50(p, d),
99838
+ renameSync: (a, b) => renameSync23(a, b),
99839
+ existsSync: (p) => existsSync56(p)
99068
99840
  };
99069
99841
  var queuedCardPersistEnabled = !STATIC;
99070
99842
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -99437,12 +100209,12 @@ var getPinnedProgressCardMessageId = null;
99437
100209
  var completeProgressCardTurn = null;
99438
100210
  var subagentWatcher = null;
99439
100211
  var workerActivityFeed = null;
99440
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join61(STATE_DIR, "gateway.sock");
100212
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join62(STATE_DIR, "gateway.sock");
99441
100213
  if (isGatewayMain)
99442
- mkdirSync48(STATE_DIR, { recursive: true, mode: 448 });
99443
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join61(STATE_DIR, "gateway.pid.json");
99444
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join61(STATE_DIR, "gateway-session.json");
99445
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join61(STATE_DIR, "clean-shutdown.json");
100214
+ mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
100215
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join62(STATE_DIR, "gateway.pid.json");
100216
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join62(STATE_DIR, "gateway-session.json");
100217
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join62(STATE_DIR, "clean-shutdown.json");
99446
100218
  var GATEWAY_STARTED_AT_MS = Date.now();
99447
100219
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
99448
100220
  var activeBootCard = null;
@@ -99471,7 +100243,7 @@ function ensureIssuesCard(chatId, threadId) {
99471
100243
  bot: botApi,
99472
100244
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
99473
100245
  `),
99474
- persistPath: join61(stateDir, "issues-card.json")
100246
+ persistPath: join62(stateDir, "issues-card.json")
99475
100247
  });
99476
100248
  activeIssuesWatcher = startIssuesWatcher({
99477
100249
  stateDir,
@@ -99647,7 +100419,7 @@ function runDeliveryConfirmSweep() {
99647
100419
  }
99648
100420
  var _deliveryConfirmSweep = isGatewayMain ? setInterval(runDeliveryConfirmSweep, DELIVERY_CONFIRM_SWEEP_MS) : undefined;
99649
100421
  _deliveryConfirmSweep?.unref?.();
99650
- startOutboxSweep({ isGatewayMain, stateDir: STATE_DIR, getBot: () => bot, getTurnsDb: () => turnsDb, dedupCheck: (c, t, x) => outboundDedup.check(c, t, x, Date.now()) != null, log: (l) => process.stderr.write(l) });
100422
+ startOutboxSweep({ isGatewayMain, stateDir: STATE_DIR, getBot: () => bot, getTurnsDb: () => turnsDb, dedupCheck: (c, t, x) => outboundDedup.check(c, t, x, Date.now()) != null, resolveReplyMarkup: makeOutboxListenMarkupResolver({ resolveVoiceOutPlan: (t) => resolveVoiceOutPlan(loadAccess().voice_out, t), cachePut: (token, payload) => voiceOnDemandCache.put(token, payload), eagerVoiceEnabled, enqueuePreSynth: (j) => voicePreSynthQueue.enqueue(j) }), log: (l) => process.stderr.write(l) });
99651
100423
  if (isGatewayMain)
99652
100424
  startTimer2({
99653
100425
  editMessage: async (ctx) => {
@@ -99669,13 +100441,13 @@ if (isGatewayMain)
99669
100441
  var inboundSpool;
99670
100442
  if (isGatewayMain)
99671
100443
  inboundSpool = STATIC ? undefined : createInboundSpool({
99672
- path: join61(STATE_DIR, "inbound-spool.jsonl"),
100444
+ path: join62(STATE_DIR, "inbound-spool.jsonl"),
99673
100445
  fs: {
99674
- appendFileSync: (p, d) => appendFileSync8(p, d),
99675
- readFileSync: (p) => readFileSync59(p, "utf8"),
99676
- writeFileSync: (p, d) => writeFileSync49(p, d),
99677
- renameSync: (a, b) => renameSync22(a, b),
99678
- existsSync: (p) => existsSync55(p),
100446
+ appendFileSync: (p, d) => appendFileSync9(p, d),
100447
+ readFileSync: (p) => readFileSync60(p, "utf8"),
100448
+ writeFileSync: (p, d) => writeFileSync50(p, d),
100449
+ renameSync: (a, b) => renameSync23(a, b),
100450
+ existsSync: (p) => existsSync56(p),
99679
100451
  statSizeSync: (p) => statSync20(p).size
99680
100452
  },
99681
100453
  onDegraded: (info) => {
@@ -99737,13 +100509,13 @@ async function maybeRedeliverUndeliveredAnswer() {
99737
100509
  let transcriptText;
99738
100510
  try {
99739
100511
  const projectsDir = getProjectsDirForCwd();
99740
- const path2 = join61(projectsDir, `${sessionId}.jsonl`);
99741
- if (!existsSync55(path2)) {
100512
+ const path2 = join62(projectsDir, `${sessionId}.jsonl`);
100513
+ if (!existsSync56(path2)) {
99742
100514
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path2}); skipping
99743
100515
  `);
99744
100516
  return;
99745
100517
  }
99746
- transcriptText = readFileSync59(path2, "utf8");
100518
+ transcriptText = readFileSync60(path2, "utf8");
99747
100519
  } catch (err) {
99748
100520
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
99749
100521
  `);
@@ -99902,8 +100674,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
99902
100674
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
99903
100675
  isShuttingDown: () => shuttingDown,
99904
100676
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
99905
- crashLogPath: join61(STATE_DIR, "bridge-crash.log"),
99906
- markerPath: join61(STATE_DIR, "bridge-dead-escalation.json"),
100677
+ crashLogPath: join62(STATE_DIR, "bridge-crash.log"),
100678
+ markerPath: join62(STATE_DIR, "bridge-dead-escalation.json"),
99907
100679
  log: (line) => process.stderr.write(`${line}
99908
100680
  `),
99909
100681
  priorStreak: bridgeDeadPriorStreak,
@@ -100009,8 +100781,8 @@ if (isGatewayMain)
100009
100781
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
100010
100782
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
100011
100783
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
100012
- configSnapshotPath: join61(resolvedAgentDirForCard, ".config-snapshot.json"),
100013
- bootCardStatePath: join61(resolvedAgentDirForCard, ".boot-card-msgid.json"),
100784
+ configSnapshotPath: join62(resolvedAgentDirForCard, ".config-snapshot.json"),
100785
+ bootCardStatePath: join62(resolvedAgentDirForCard, ".boot-card-msgid.json"),
100014
100786
  floodStatePath: FLOOD_STATE_PATH,
100015
100787
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
100016
100788
  }, ackMsgId).then((handle) => {
@@ -100120,22 +100892,7 @@ if (isGatewayMain)
100120
100892
  notifyHaltBoundaryWaiters();
100121
100893
  if (currentTurn != null) {
100122
100894
  const key = statusKey(currentTurn.sessionChatId, currentTurn.sessionThreadId);
100123
- if (ev.kind === "thinking") {
100124
- noteThinking(key, Date.now());
100125
- } else if (ev.kind === "tool_use") {
100126
- if (ev.toolUseId != null && ev.toolUseId.length > 0 && !isTelegramSurfaceTool2(ev.toolName)) {
100127
- const label = toolLabel(ev.toolName, ev.input, undefined, ev.precomputedLabel);
100128
- noteToolStart(key, ev.toolUseId, ev.toolName, label.length > 0 ? label : null, Date.now());
100129
- const evInput = ev.input;
100130
- if (ev.toolName === "Agent" || ev.toolName === "Task" || ev.toolName === "Bash" && evInput?.run_in_background === true) {
100131
- noteAsyncDispatch(key);
100132
- }
100133
- }
100134
- } else if (ev.kind === "tool_result") {
100135
- if (ev.toolUseId != null && ev.toolUseId.length > 0) {
100136
- noteToolEnd(key, ev.toolUseId, Date.now());
100137
- }
100138
- }
100895
+ applySilencePokeSessionEvent(exports_silence_poke, exports_pending_work_progress, key, ev);
100139
100896
  }
100140
100897
  },
100141
100898
  onPermissionRequest(_client, msg) {
@@ -100692,7 +101449,7 @@ if (isGatewayMain)
100692
101449
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
100693
101450
  if (Number.isInteger(receiverUid))
100694
101451
  allowedUids.push(receiverUid);
100695
- const socketPath = join61(STATE_DIR, "webhook.sock");
101452
+ const socketPath = join62(STATE_DIR, "webhook.sock");
100696
101453
  const webhookInject = (agentName3, inbound) => {
100697
101454
  const msg = inbound;
100698
101455
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -100920,9 +101677,9 @@ function redactOutboundText(text5, site) {
100920
101677
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
100921
101678
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
100922
101679
  var voiceOnDemandCache = new VoiceOnDemandCache({
100923
- persistPath: join61(STATE_DIR, "voice-ondemand.json")
101680
+ persistPath: join62(STATE_DIR, "voice-ondemand.json")
100924
101681
  });
100925
- var VOICE_CACHE_DIR = join61(STATE_DIR, "voice-cache");
101682
+ var VOICE_CACHE_DIR = join62(STATE_DIR, "voice-cache");
100926
101683
  var voicePreSynthQueue = new PreSynthQueue({
100927
101684
  runJob: async (job) => {
100928
101685
  const sidecarToken = await materializeSidecarToken2();
@@ -101324,11 +102081,11 @@ async function executeSendGif(rawArgs) {
101324
102081
  };
101325
102082
  }
101326
102083
  async function publishToTelegraph(text5, shortName, authorName) {
101327
- const accountPath = join61(STATE_DIR, "telegraph-account.json");
102084
+ const accountPath = join62(STATE_DIR, "telegraph-account.json");
101328
102085
  let account = null;
101329
102086
  try {
101330
- if (existsSync55(accountPath)) {
101331
- const raw = readFileSync59(accountPath, "utf-8");
102087
+ if (existsSync56(accountPath)) {
102088
+ const raw = readFileSync60(accountPath, "utf-8");
101332
102089
  const parsed = JSON.parse(raw);
101333
102090
  if (parsed.shortName && parsed.accessToken) {
101334
102091
  account = parsed;
@@ -101347,8 +102104,8 @@ async function publishToTelegraph(text5, shortName, authorName) {
101347
102104
  }
101348
102105
  account = created.value;
101349
102106
  try {
101350
- mkdirSync48(STATE_DIR, { recursive: true, mode: 448 });
101351
- writeFileSync49(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
102107
+ mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
102108
+ writeFileSync50(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
101352
102109
  } catch (err) {
101353
102110
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
101354
102111
  `);
@@ -101464,7 +102221,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
101464
102221
  }
101465
102222
  function readLiveSwitchroomConfigText() {
101466
102223
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
101467
- return readFileSync59(cfgPath, "utf8");
102224
+ return readFileSync60(cfgPath, "utf8");
101468
102225
  }
101469
102226
  async function executeReact(args) {
101470
102227
  if (!args.chat_id)
@@ -101503,9 +102260,9 @@ async function executeDownloadAttachment(args) {
101503
102260
  fileUniqueId: file.file_unique_id,
101504
102261
  now: Date.now()
101505
102262
  });
101506
- mkdirSync48(INBOX_DIR, { recursive: true, mode: 448 });
102263
+ mkdirSync49(INBOX_DIR, { recursive: true, mode: 448 });
101507
102264
  assertInsideInbox2(INBOX_DIR, dlPath);
101508
- writeFileSync49(dlPath, buf, { mode: 384 });
102265
+ writeFileSync50(dlPath, buf, { mode: 384 });
101509
102266
  return { content: [{ type: "text", text: dlPath }] };
101510
102267
  }
101511
102268
  async function executeEditMessage(args) {
@@ -102865,14 +103622,14 @@ function restartMarkerPath() {
102865
103622
  const agentDir = resolveAgentDirFromEnv();
102866
103623
  if (!agentDir)
102867
103624
  return null;
102868
- return join61(agentDir, "restart-pending.json");
103625
+ return join62(agentDir, "restart-pending.json");
102869
103626
  }
102870
103627
  function writeRestartMarker(marker) {
102871
103628
  const p = restartMarkerPath();
102872
103629
  if (!p)
102873
103630
  return;
102874
103631
  try {
102875
- writeFileSync49(p, JSON.stringify(marker));
103632
+ writeFileSync50(p, JSON.stringify(marker));
102876
103633
  lastPlannedRestartAt = Date.now();
102877
103634
  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}
102878
103635
  `);
@@ -102891,7 +103648,7 @@ function readRestartMarker() {
102891
103648
  if (!p)
102892
103649
  return null;
102893
103650
  try {
102894
- return JSON.parse(readFileSync59(p, "utf8"));
103651
+ return JSON.parse(readFileSync60(p, "utf8"));
102895
103652
  } catch {
102896
103653
  return null;
102897
103654
  }
@@ -103040,7 +103797,7 @@ var _dockerReachable;
103040
103797
  function isDockerReachable() {
103041
103798
  if (_dockerReachable !== undefined)
103042
103799
  return _dockerReachable;
103043
- if (!existsSync55("/var/run/docker.sock")) {
103800
+ if (!existsSync56("/var/run/docker.sock")) {
103044
103801
  _dockerReachable = false;
103045
103802
  return _dockerReachable;
103046
103803
  }
@@ -103057,12 +103814,12 @@ function _resetDockerReachableCache() {
103057
103814
  }
103058
103815
  function spawnSwitchroomDetached(args, onFailure) {
103059
103816
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
103060
- const logPath = join61(STATE_DIR, "detached-spawn.log");
103817
+ const logPath = join62(STATE_DIR, "detached-spawn.log");
103061
103818
  let outFd = null;
103062
103819
  try {
103063
- mkdirSync48(STATE_DIR, { recursive: true });
103820
+ mkdirSync49(STATE_DIR, { recursive: true });
103064
103821
  outFd = openSync12(logPath, "a");
103065
- writeFileSync49(logPath, `
103822
+ writeFileSync50(logPath, `
103066
103823
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
103067
103824
  `, { flag: "a" });
103068
103825
  } catch {}
@@ -103088,7 +103845,7 @@ function spawnSwitchroomDetached(args, onFailure) {
103088
103845
  return;
103089
103846
  let tail = "";
103090
103847
  try {
103091
- const full = readFileSync59(logPath, "utf8");
103848
+ const full = readFileSync60(logPath, "utf8");
103092
103849
  tail = full.split(`
103093
103850
  `).slice(-30).join(`
103094
103851
  `).trim();
@@ -103350,10 +104107,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
103350
104107
  }
103351
104108
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
103352
104109
  try {
103353
- const auditPath = join61(homedir20(), ".switchroom", "vault-audit.log");
103354
- if (!existsSync55(auditPath))
104110
+ const auditPath = join62(homedir20(), ".switchroom", "vault-audit.log");
104111
+ if (!existsSync56(auditPath))
103355
104112
  return [];
103356
- const raw = readFileSync59(auditPath, "utf8");
104113
+ const raw = readFileSync60(auditPath, "utf8");
103357
104114
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
103358
104115
  } catch {
103359
104116
  return [];
@@ -103404,7 +104161,7 @@ async function buildAgentMetadata(agentName3) {
103404
104161
  try {
103405
104162
  const agentDir = resolveAgentDirFromEnv();
103406
104163
  if (agentDir) {
103407
- const raw = readFileSync59(join61(agentDir, ".claude", ".claude.json"), "utf8");
104164
+ const raw = readFileSync60(join62(agentDir, ".claude", ".claude.json"), "utf8");
103408
104165
  claudeJson = JSON.parse(raw);
103409
104166
  }
103410
104167
  } catch {}
@@ -103533,7 +104290,7 @@ function buildModelDeps(restartCtx) {
103533
104290
  try {
103534
104291
  const agentDir = resolveAgentDirFromEnv();
103535
104292
  if (agentDir) {
103536
- const local = await fetchQuota2({ claudeConfigDir: join61(agentDir, ".claude") });
104293
+ const local = await fetchQuota2({ claudeConfigDir: join62(agentDir, ".claude") });
103537
104294
  if (local.ok)
103538
104295
  return formatQuotaLine2(local.data);
103539
104296
  }
@@ -103779,9 +104536,9 @@ function effortMenuReplyMarkup(reply) {
103779
104536
  function flushAgentHandoff(agentDir) {
103780
104537
  let removed = 0;
103781
104538
  for (const fname of [".handoff.md", ".handoff-topic"]) {
103782
- const p = join61(agentDir, fname);
104539
+ const p = join62(agentDir, fname);
103783
104540
  try {
103784
- if (existsSync55(p)) {
104541
+ if (existsSync56(p)) {
103785
104542
  unlinkSync27(p);
103786
104543
  removed++;
103787
104544
  }
@@ -103837,7 +104594,7 @@ async function handleNewCommand(ctx) {
103837
104594
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
103838
104595
  if (agentDir != null) {
103839
104596
  try {
103840
- writeFileSync49(join61(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
104597
+ writeFileSync50(join62(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
103841
104598
  `, "utf8");
103842
104599
  } catch (err) {
103843
104600
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -103950,16 +104707,16 @@ function buildFolderPickerDeps() {
103950
104707
  };
103951
104708
  }
103952
104709
  var lockoutOps = {
103953
- readFileSync: (p, enc) => readFileSync59(p, enc),
103954
- writeFileSync: (p, data, opts) => writeFileSync49(p, data, opts),
103955
- existsSync: (p) => existsSync55(p),
103956
- mkdirSync: (p, opts) => mkdirSync48(p, opts),
103957
- joinPath: (...parts) => join61(...parts)
104710
+ readFileSync: (p, enc) => readFileSync60(p, enc),
104711
+ writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
104712
+ existsSync: (p) => existsSync56(p),
104713
+ mkdirSync: (p, opts) => mkdirSync49(p, opts),
104714
+ joinPath: (...parts) => join62(...parts)
103958
104715
  };
103959
104716
  var FLEET_FALLBACK_DEDUP_MS = 30000;
103960
104717
  function isAuthBrokerSocketReachable() {
103961
104718
  try {
103962
- return existsSync55(resolveAuthBrokerSocketPath2());
104719
+ return existsSync56(resolveAuthBrokerSocketPath2());
103963
104720
  } catch {
103964
104721
  return false;
103965
104722
  }
@@ -104214,7 +104971,7 @@ async function runCreditWatch() {
104214
104971
  if (!agentDir)
104215
104972
  return;
104216
104973
  const agentName3 = getMyAgentName();
104217
- const claudeConfigDir = join61(agentDir, ".claude");
104974
+ const claudeConfigDir = join62(agentDir, ".claude");
104218
104975
  const stateDir = STATE_DIR;
104219
104976
  const reason = readClaudeJsonOverage(claudeConfigDir);
104220
104977
  const prev = loadCreditState(stateDir);
@@ -105970,7 +106727,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105970
106727
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
105971
106728
  return;
105972
106729
  }
105973
- const result = await fetchQuota2({ claudeConfigDir: join61(agentDir, ".claude") });
106730
+ const result = await fetchQuota2({ claudeConfigDir: join62(agentDir, ".claude") });
105974
106731
  if (!result.ok) {
105975
106732
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
105976
106733
  return;
@@ -106356,7 +107113,7 @@ ${interimLabel}` : interimLabel
106356
107113
  const unifiedDiff = (() => {
106357
107114
  try {
106358
107115
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
106359
- const raw = readFileSync59(cfgPath, "utf8");
107116
+ const raw = readFileSync60(cfgPath, "utf8");
106360
107117
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
106361
107118
  } catch (err) {
106362
107119
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -106634,6 +107391,7 @@ ${labelWithResume}` : labelWithResume,
106634
107391
  handleChecklistUpdate(ctx, "checklist_tasks_added", checklistHandlerDeps);
106635
107392
  });
106636
107393
  bot2.on("message:pinned_message", (ctx) => handlePinnedMessage(ctx, pinnedMessageHandlerDeps));
107394
+ bot2.on("message:rich_message", (ctx) => handleRichMessageMessage(ctx, { ...mediaEnvelopeDeps, handleInbound: handleInboundCoalesced }));
106637
107395
  installUnhandledMessageCatchAll(bot2, (ctx, text5) => routeInbound(ctx, text5, undefined, undefined, inboundRouterDeps), (line) => process.stderr.write(line));
106638
107396
  bot2.on("message_reaction", (ctx) => {
106639
107397
  handleMessageReaction(ctx);
@@ -107212,7 +107970,7 @@ async function startGateway() {
107212
107970
  return;
107213
107971
  }
107214
107972
  })();
107215
- const resolvedAgentDirForBootCard = agentDir ?? join61(homedir20(), ".switchroom", "agents", agentSlug);
107973
+ const resolvedAgentDirForBootCard = agentDir ?? join62(homedir20(), ".switchroom", "agents", agentSlug);
107216
107974
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
107217
107975
  agentName: agentDisplayName,
107218
107976
  agentSlug,
@@ -107226,8 +107984,8 @@ async function startGateway() {
107226
107984
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
107227
107985
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
107228
107986
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
107229
- configSnapshotPath: join61(resolvedAgentDirForBootCard, ".config-snapshot.json"),
107230
- bootCardStatePath: join61(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
107987
+ configSnapshotPath: join62(resolvedAgentDirForBootCard, ".config-snapshot.json"),
107988
+ bootCardStatePath: join62(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
107231
107989
  floodStatePath: FLOOD_STATE_PATH,
107232
107990
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
107233
107991
  }, ackMsgId);
@@ -107258,10 +108016,10 @@ async function startGateway() {
107258
108016
  try {
107259
108017
  const smAgentDir = resolveAgentDirFromEnv();
107260
108018
  if (smAgentDir) {
107261
- const activePath = join61(smAgentDir, ".active-session-model");
107262
- if (existsSync55(activePath)) {
108019
+ const activePath = join62(smAgentDir, ".active-session-model");
108020
+ if (existsSync56(activePath)) {
107263
108021
  try {
107264
- const launched = readFileSync59(activePath, "utf8").trim();
108022
+ const launched = readFileSync60(activePath, "utf8").trim();
107265
108023
  const configured = (() => {
107266
108024
  const d = switchroomExecJson(["agent", "list"]);
107267
108025
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -107294,24 +108052,24 @@ async function startGateway() {
107294
108052
  deliverModelSwitchBootNotice({
107295
108053
  ...modelBootCardDeps,
107296
108054
  confirmation,
107297
- hasSessionModelAlert: existsSync55(join61(smAgentDir, ".session-model-alert"))
108055
+ hasSessionModelAlert: existsSync56(join62(smAgentDir, ".session-model-alert"))
107298
108056
  });
107299
108057
  }
107300
108058
  } catch {}
107301
108059
  }
107302
- const activeEffortPath = join61(smAgentDir, ".active-session-effort");
107303
- if (existsSync55(activeEffortPath)) {
108060
+ const activeEffortPath = join62(smAgentDir, ".active-session-effort");
108061
+ if (existsSync56(activeEffortPath)) {
107304
108062
  try {
107305
- const launchedEffort = readFileSync59(activeEffortPath, "utf8").trim();
108063
+ const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
107306
108064
  const configuredEffort = getConfiguredEffortForPersist();
107307
108065
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
107308
108066
  } catch {}
107309
108067
  }
107310
- const alertPath = join61(smAgentDir, ".session-model-alert");
107311
- if (existsSync55(alertPath)) {
108068
+ const alertPath = join62(smAgentDir, ".session-model-alert");
108069
+ if (existsSync56(alertPath)) {
107312
108070
  let alertText = null;
107313
108071
  try {
107314
- alertText = readFileSync59(alertPath, "utf8").trim();
108072
+ alertText = readFileSync60(alertPath, "utf8").trim();
107315
108073
  } catch {
107316
108074
  alertText = null;
107317
108075
  }