switchroom 0.19.12 → 0.19.14

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 (26) hide show
  1. package/dist/cli/switchroom.js +1 -1
  2. package/dist/host-control/main.js +4 -2
  3. package/package.json +1 -1
  4. package/profiles/_base/cron-session.sh.hbs +5 -0
  5. package/profiles/_base/start.sh.hbs +11 -0
  6. package/telegram-plugin/dist/gateway/gateway.js +1402 -1032
  7. package/telegram-plugin/final-answer-detect.ts +23 -0
  8. package/telegram-plugin/gateway/gateway.ts +29 -36
  9. package/telegram-plugin/gateway/outbound-send-path.ts +115 -8
  10. package/telegram-plugin/gateway/outbox-sweep.ts +320 -0
  11. package/telegram-plugin/gateway/stream-render.ts +26 -50
  12. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +120 -2
  13. package/telegram-plugin/hooks/silent-end-scan.mjs +311 -0
  14. package/telegram-plugin/outbox.ts +489 -0
  15. package/telegram-plugin/scripts/bun-test-ci.sh +91 -0
  16. package/telegram-plugin/temporal-normalize.ts +347 -0
  17. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +22 -12
  18. package/telegram-plugin/tests/outbound-send-path.test.ts +191 -0
  19. package/telegram-plugin/tests/outbox-capture-scan.test.ts +222 -0
  20. package/telegram-plugin/tests/outbox-delivery.test.ts +278 -0
  21. package/telegram-plugin/tests/outbox-hook-capture.test.ts +112 -0
  22. package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +600 -0
  23. package/telegram-plugin/tests/silent-end-interrupt-stop-integration.test.ts +68 -63
  24. package/telegram-plugin/tests/silent-end.test.ts +21 -28
  25. package/telegram-plugin/tests/temporal-normalize.test.ts +222 -0
  26. package/telegram-plugin/tests/turn-flush-safety.test.ts +23 -18
@@ -29063,6 +29063,9 @@ function isSubstantiveFinalReply(input) {
29063
29063
  return true;
29064
29064
  return false;
29065
29065
  }
29066
+ function shouldJournalReplySiteDelivery(input) {
29067
+ return isSubstantiveFinalReply(input);
29068
+ }
29066
29069
  var FINAL_ANSWER_MIN_CHARS2 = 200;
29067
29070
 
29068
29071
  // history.ts
@@ -29088,8 +29091,8 @@ __export(exports_history, {
29088
29091
  _resetForTests: () => _resetForTests,
29089
29092
  MIN_PREFIX_MATCH_CHARS: () => MIN_PREFIX_MATCH_CHARS2
29090
29093
  });
29091
- import { chmodSync as chmodSync9, existsSync as existsSync25, mkdirSync as mkdirSync26 } from "fs";
29092
- import { join as join29 } from "path";
29094
+ import { chmodSync as chmodSync9, existsSync as existsSync26, mkdirSync as mkdirSync27 } from "fs";
29095
+ import { join as join30 } from "path";
29093
29096
  function loadDatabaseClass2() {
29094
29097
  if (DatabaseClass2 != null)
29095
29098
  return DatabaseClass2;
@@ -29120,8 +29123,8 @@ function initHistory2(stateDir, retentionDays = 30) {
29120
29123
  if (db2 != null)
29121
29124
  return;
29122
29125
  const Database = loadDatabaseClass2();
29123
- mkdirSync26(stateDir, { recursive: true, mode: 448 });
29124
- const path2 = join29(stateDir, "history.db");
29126
+ mkdirSync27(stateDir, { recursive: true, mode: 448 });
29127
+ const path2 = join30(stateDir, "history.db");
29125
29128
  dbPath2 = path2;
29126
29129
  db2 = new Database(path2, { create: true });
29127
29130
  db2.exec("PRAGMA journal_mode = WAL");
@@ -29187,7 +29190,7 @@ function initHistory2(stateDir, retentionDays = 30) {
29187
29190
  }
29188
29191
  for (const suffix of ["", "-shm", "-wal"]) {
29189
29192
  const f = path2 + suffix;
29190
- if (existsSync25(f)) {
29193
+ if (existsSync26(f)) {
29191
29194
  try {
29192
29195
  chmodSync9(f, 420);
29193
29196
  } catch {}
@@ -29239,7 +29242,7 @@ function checkpointWal2() {
29239
29242
  if (dbPath2) {
29240
29243
  for (const suffix of ["-shm", "-wal"]) {
29241
29244
  const f = dbPath2 + suffix;
29242
- if (existsSync25(f)) {
29245
+ if (existsSync26(f)) {
29243
29246
  try {
29244
29247
  chmodSync9(f, 420);
29245
29248
  } catch {}
@@ -29486,7 +29489,7 @@ var FLUSH_SUPPRESSION_WINDOW_MS = 2000;
29486
29489
  var init_turn_flush_suppression = () => {};
29487
29490
 
29488
29491
  // ../src/util/atomic.ts
29489
- import { closeSync as closeSync6, constants as constants2, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync11, 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 renameSync12, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29490
29493
  var TMP_OPEN_FLAGS;
29491
29494
  var init_atomic = __esm(() => {
29492
29495
  TMP_OPEN_FLAGS = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
@@ -32448,7 +32451,7 @@ var require_util = __commonJS((exports2) => {
32448
32451
  return path2;
32449
32452
  }
32450
32453
  exports2.normalize = normalize;
32451
- function join37(aRoot, aPath) {
32454
+ function join38(aRoot, aPath) {
32452
32455
  if (aRoot === "") {
32453
32456
  aRoot = ".";
32454
32457
  }
@@ -32480,7 +32483,7 @@ var require_util = __commonJS((exports2) => {
32480
32483
  }
32481
32484
  return joined;
32482
32485
  }
32483
- exports2.join = join37;
32486
+ exports2.join = join38;
32484
32487
  exports2.isAbsolute = function(aPath) {
32485
32488
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
32486
32489
  };
@@ -32653,7 +32656,7 @@ var require_util = __commonJS((exports2) => {
32653
32656
  parsed.path = parsed.path.substring(0, index2 + 1);
32654
32657
  }
32655
32658
  }
32656
- sourceURL = join37(urlGenerate(parsed), sourceURL);
32659
+ sourceURL = join38(urlGenerate(parsed), sourceURL);
32657
32660
  }
32658
32661
  return normalize(sourceURL);
32659
32662
  }
@@ -35258,19 +35261,19 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35258
35261
  }
35259
35262
 
35260
35263
  // gateway/quota-cache.ts
35261
- import { existsSync as existsSync41, readFileSync as readFileSync42, writeFileSync as writeFileSync35, mkdirSync as mkdirSync36 } from "fs";
35262
- import { join as join46, dirname as dirname15 } from "path";
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";
35263
35266
  function defaultCachePath() {
35264
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join46(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35267
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join47(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35265
35268
  }
35266
35269
  function readQuotaCache(opts = {}) {
35267
35270
  const path2 = opts.path ?? defaultCachePath();
35268
35271
  const now = opts.now ?? Date.now();
35269
- if (!existsSync41(path2))
35272
+ if (!existsSync42(path2))
35270
35273
  return null;
35271
35274
  let entry;
35272
35275
  try {
35273
- entry = JSON.parse(readFileSync42(path2, "utf8"));
35276
+ entry = JSON.parse(readFileSync43(path2, "utf8"));
35274
35277
  } catch {
35275
35278
  return null;
35276
35279
  }
@@ -35296,8 +35299,8 @@ function writeQuotaCache(result, opts = {}) {
35296
35299
  result
35297
35300
  };
35298
35301
  try {
35299
- mkdirSync36(dirname15(path2), { recursive: true });
35300
- writeFileSync35(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35302
+ mkdirSync37(dirname15(path2), { recursive: true });
35303
+ writeFileSync36(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35301
35304
  } catch {}
35302
35305
  }
35303
35306
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -35307,8 +35310,8 @@ var init_quota_cache = __esm(() => {
35307
35310
  });
35308
35311
 
35309
35312
  // gateway/boot-probes.ts
35310
- import { readFileSync as readFileSync43, readdirSync as readdirSync9, existsSync as existsSync42 } from "fs";
35311
- import { join as join47 } from "path";
35313
+ import { readFileSync as readFileSync44, readdirSync as readdirSync10, existsSync as existsSync43 } from "fs";
35314
+ import { join as join48 } from "path";
35312
35315
  import { execFile as execFileCb } from "child_process";
35313
35316
  import { promisify } from "util";
35314
35317
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -35350,11 +35353,11 @@ function mapPlan(billingType, hasExtra) {
35350
35353
  }
35351
35354
  async function probeAccount(agentDir) {
35352
35355
  return withTimeout("Account", (async () => {
35353
- const claudeDir = join47(agentDir, ".claude");
35354
- const claudeJsonPath = join47(claudeDir, ".claude.json");
35356
+ const claudeDir = join48(agentDir, ".claude");
35357
+ const claudeJsonPath = join48(claudeDir, ".claude.json");
35355
35358
  let cfg = {};
35356
35359
  try {
35357
- const raw = readFileSync43(claudeJsonPath, "utf8");
35360
+ const raw = readFileSync44(claudeJsonPath, "utf8");
35358
35361
  cfg = JSON.parse(raw);
35359
35362
  } catch {
35360
35363
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -35372,12 +35375,12 @@ async function probeAccount(agentDir) {
35372
35375
  let tokenStr = "";
35373
35376
  let status = "ok";
35374
35377
  for (const candidate of [
35375
- join47(claudeDir, ".oauth-token.meta.json"),
35376
- join47(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35378
+ join48(claudeDir, ".oauth-token.meta.json"),
35379
+ join48(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35377
35380
  ]) {
35378
- if (existsSync42(candidate)) {
35381
+ if (existsSync43(candidate)) {
35379
35382
  try {
35380
- const meta = JSON.parse(readFileSync43(candidate, "utf8"));
35383
+ const meta = JSON.parse(readFileSync44(candidate, "utf8"));
35381
35384
  if (meta.expiresAt) {
35382
35385
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
35383
35386
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -35552,9 +35555,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35552
35555
  if (!cgroup)
35553
35556
  return null;
35554
35557
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
35555
- if (!existsSync42(procsPath))
35558
+ if (!existsSync43(procsPath))
35556
35559
  return null;
35557
- const pidsRaw = readFileSync43(procsPath, "utf-8");
35560
+ const pidsRaw = readFileSync44(procsPath, "utf-8");
35558
35561
  const pids = pidsRaw.split(`
35559
35562
  `).map((s) => s.trim()).filter(Boolean);
35560
35563
  if (pids.length === 0)
@@ -35567,7 +35570,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35567
35570
  let rss = 0;
35568
35571
  let comm = "";
35569
35572
  try {
35570
- const status = readFileSync43(`/proc/${pid}/status`, "utf-8");
35573
+ const status = readFileSync44(`/proc/${pid}/status`, "utf-8");
35571
35574
  const rssLine = status.split(`
35572
35575
  `).find((l) => l.startsWith("VmRSS:"));
35573
35576
  if (rssLine) {
@@ -35579,7 +35582,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35579
35582
  continue;
35580
35583
  }
35581
35584
  try {
35582
- comm = readFileSync43(`/proc/${pid}/comm`, "utf-8").trim();
35585
+ comm = readFileSync44(`/proc/${pid}/comm`, "utf-8").trim();
35583
35586
  } catch {}
35584
35587
  candidates.push({ pid, rss, comm });
35585
35588
  }
@@ -35759,9 +35762,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
35759
35762
  let claudeDirForProbe = null;
35760
35763
  for (const candidate of [
35761
35764
  claudeConfigDir,
35762
- join47(claudeConfigDir, "accounts", "default")
35765
+ join48(claudeConfigDir, "accounts", "default")
35763
35766
  ]) {
35764
- if (existsSync42(join47(candidate, ".oauth-token"))) {
35767
+ if (existsSync43(join48(candidate, ".oauth-token"))) {
35765
35768
  claudeDirForProbe = candidate;
35766
35769
  break;
35767
35770
  }
@@ -35826,7 +35829,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
35826
35829
  }
35827
35830
  function readContainerBootTimeMsForProbe() {
35828
35831
  try {
35829
- const stat1 = readFileSync43("/proc/1/stat", "utf8");
35832
+ const stat1 = readFileSync44("/proc/1/stat", "utf8");
35830
35833
  const lastParen = stat1.lastIndexOf(")");
35831
35834
  if (lastParen < 0)
35832
35835
  return null;
@@ -35834,7 +35837,7 @@ function readContainerBootTimeMsForProbe() {
35834
35837
  const starttimeTicks = Number(after[19]);
35835
35838
  if (!Number.isFinite(starttimeTicks))
35836
35839
  return null;
35837
- const procStat = readFileSync43("/proc/stat", "utf8");
35840
+ const procStat = readFileSync44("/proc/stat", "utf8");
35838
35841
  const btimeLine = procStat.split(`
35839
35842
  `).find((l) => l.startsWith("btime "));
35840
35843
  if (!btimeLine)
@@ -35932,7 +35935,7 @@ async function probeUds(label, socketPath, opts = {}) {
35932
35935
  }
35933
35936
  return withTimeout(label, (async () => {
35934
35937
  if (!opts.connectImpl) {
35935
- if (!existsSync42(socketPath)) {
35938
+ if (!existsSync43(socketPath)) {
35936
35939
  return {
35937
35940
  status: "fail",
35938
35941
  label,
@@ -35996,7 +35999,7 @@ async function probeSkills(agentDir, opts = {}) {
35996
35999
  return withTimeout("Skills", (async () => {
35997
36000
  const fs2 = opts.fs ?? realSkillsFs;
35998
36001
  const max = opts.maxNamesShown ?? 3;
35999
- const skillsDir = join47(agentDir, ".claude", "skills");
36002
+ const skillsDir = join48(agentDir, ".claude", "skills");
36000
36003
  if (!fs2.exists(skillsDir)) {
36001
36004
  return { status: "ok", label: "Skills", detail: "no skills dir" };
36002
36005
  }
@@ -36011,17 +36014,17 @@ async function probeSkills(agentDir, opts = {}) {
36011
36014
  }
36012
36015
  const dangling = [];
36013
36016
  for (const name of entries) {
36014
- const skillPath = join47(skillsDir, name);
36017
+ const skillPath = join48(skillsDir, name);
36015
36018
  if (!fs2.exists(skillPath)) {
36016
36019
  dangling.push(name);
36017
36020
  continue;
36018
36021
  }
36019
- const skillMd = join47(skillPath, "SKILL.md");
36022
+ const skillMd = join48(skillPath, "SKILL.md");
36020
36023
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
36021
36024
  continue;
36022
36025
  }
36023
36026
  }
36024
- const overlayDir = opts.overlaySkillsDir ?? join47(agentDir, "skills.d");
36027
+ const overlayDir = opts.overlaySkillsDir ?? join48(agentDir, "skills.d");
36025
36028
  const overlaySlugs = new Set;
36026
36029
  if (fs2.exists(overlayDir)) {
36027
36030
  let overlayEntries = [];
@@ -36063,8 +36066,8 @@ function renderBucketedSkills(switchroom, agent) {
36063
36066
  }
36064
36067
  async function probeConnections(agentDir, opts = {}) {
36065
36068
  return withTimeout("Connections", (async () => {
36066
- const path2 = join47(agentDir, ".claude", "connection-health.json");
36067
- const read = opts.readFileImpl ?? ((p) => readFileSync43(p, "utf8"));
36069
+ const path2 = join48(agentDir, ".claude", "connection-health.json");
36070
+ const read = opts.readFileImpl ?? ((p) => readFileSync44(p, "utf8"));
36068
36071
  let issues = [];
36069
36072
  try {
36070
36073
  const parsed = JSON.parse(read(path2));
@@ -36094,25 +36097,25 @@ var init_boot_probes = __esm(() => {
36094
36097
  init_quota_check();
36095
36098
  execFile = promisify(execFileCb);
36096
36099
  realProcFs = {
36097
- readdir: (p) => readdirSync9(p),
36098
- readFile: (p) => readFileSync43(p, "utf-8")
36100
+ readdir: (p) => readdirSync10(p),
36101
+ readFile: (p) => readFileSync44(p, "utf-8")
36099
36102
  };
36100
36103
  realSchedulerFs = {
36101
- readFile: (p) => readFileSync43(p, "utf-8"),
36104
+ readFile: (p) => readFileSync44(p, "utf-8"),
36102
36105
  mtimeMs: (p) => {
36103
- const { statSync: statSync13 } = __require("fs");
36104
- return statSync13(p).mtimeMs;
36106
+ const { statSync: statSync14 } = __require("fs");
36107
+ return statSync14(p).mtimeMs;
36105
36108
  },
36106
- exists: (p) => existsSync42(p)
36109
+ exists: (p) => existsSync43(p)
36107
36110
  };
36108
36111
  realSkillsFs = {
36109
- readdir: (p) => readdirSync9(p),
36110
- exists: (p) => existsSync42(p)
36112
+ readdir: (p) => readdirSync10(p),
36113
+ exists: (p) => existsSync43(p)
36111
36114
  };
36112
36115
  });
36113
36116
 
36114
36117
  // gateway/boot-issue-cache.ts
36115
- import { existsSync as existsSync43, readFileSync as readFileSync44, writeFileSync as writeFileSync36, mkdirSync as mkdirSync37, renameSync as renameSync17 } from "fs";
36118
+ import { existsSync as existsSync44, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38, renameSync as renameSync18 } from "fs";
36116
36119
  import { dirname as dirname16 } from "path";
36117
36120
  function fingerprintProbe(key, r) {
36118
36121
  if (r.status === "ok")
@@ -36192,11 +36195,11 @@ function diffProbes(probes, cache, opts = {}) {
36192
36195
  return out;
36193
36196
  }
36194
36197
  function loadCache(path2, now = Date.now) {
36195
- if (!existsSync43(path2))
36198
+ if (!existsSync44(path2))
36196
36199
  return { ...EMPTY_CACHE, probes: {} };
36197
36200
  let raw;
36198
36201
  try {
36199
- raw = readFileSync44(path2, "utf-8");
36202
+ raw = readFileSync45(path2, "utf-8");
36200
36203
  } catch {
36201
36204
  return { ...EMPTY_CACHE, probes: {} };
36202
36205
  }
@@ -36205,7 +36208,7 @@ function loadCache(path2, now = Date.now) {
36205
36208
  parsed = JSON.parse(raw);
36206
36209
  } catch {
36207
36210
  try {
36208
- renameSync17(path2, `${path2}.corrupt-${now()}`);
36211
+ renameSync18(path2, `${path2}.corrupt-${now()}`);
36209
36212
  } catch {}
36210
36213
  return { ...EMPTY_CACHE, probes: {} };
36211
36214
  }
@@ -36239,10 +36242,10 @@ function applyAndSave(path2, cache, diff) {
36239
36242
  }
36240
36243
  }
36241
36244
  try {
36242
- mkdirSync37(dirname16(path2), { recursive: true });
36245
+ mkdirSync38(dirname16(path2), { recursive: true });
36243
36246
  const tmp = `${path2}.tmp`;
36244
- writeFileSync36(tmp, JSON.stringify(next), { mode: 384 });
36245
- renameSync17(tmp, path2);
36247
+ writeFileSync37(tmp, JSON.stringify(next), { mode: 384 });
36248
+ renameSync18(tmp, path2);
36246
36249
  } catch {}
36247
36250
  return next;
36248
36251
  }
@@ -36254,14 +36257,14 @@ var init_boot_issue_cache = __esm(() => {
36254
36257
  });
36255
36258
 
36256
36259
  // gateway/config-snapshot.ts
36257
- import { createHash as createHash3 } from "crypto";
36258
- import { existsSync as existsSync44, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38, renameSync as renameSync18 } from "fs";
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";
36259
36262
  import { dirname as dirname17 } from "path";
36260
36263
  function hashStringArray(items) {
36261
36264
  if (!items || items.length === 0)
36262
36265
  return null;
36263
36266
  const sorted = [...items].sort();
36264
- const raw = createHash3("sha256").update(sorted.join("\x00")).digest("hex");
36267
+ const raw = createHash4("sha256").update(sorted.join("\x00")).digest("hex");
36265
36268
  return raw.slice(0, 12);
36266
36269
  }
36267
36270
  function normalizeModel(model) {
@@ -36320,11 +36323,11 @@ function renderConfigChangeDim(dim) {
36320
36323
  }
36321
36324
  }
36322
36325
  function loadSnapshot(path2, now = Date.now) {
36323
- if (!existsSync44(path2))
36326
+ if (!existsSync45(path2))
36324
36327
  return null;
36325
36328
  let raw;
36326
36329
  try {
36327
- raw = readFileSync45(path2, "utf-8");
36330
+ raw = readFileSync46(path2, "utf-8");
36328
36331
  } catch {
36329
36332
  return null;
36330
36333
  }
@@ -36333,7 +36336,7 @@ function loadSnapshot(path2, now = Date.now) {
36333
36336
  parsed = JSON.parse(raw);
36334
36337
  } catch {
36335
36338
  try {
36336
- renameSync18(path2, `${path2}.corrupt-${now()}`);
36339
+ renameSync19(path2, `${path2}.corrupt-${now()}`);
36337
36340
  } catch {}
36338
36341
  return null;
36339
36342
  }
@@ -36354,10 +36357,10 @@ function loadSnapshot(path2, now = Date.now) {
36354
36357
  }
36355
36358
  function persistSnapshot(path2, snapshot) {
36356
36359
  try {
36357
- mkdirSync38(dirname17(path2), { recursive: true });
36360
+ mkdirSync39(dirname17(path2), { recursive: true });
36358
36361
  const tmp = `${path2}.tmp`;
36359
- writeFileSync37(tmp, JSON.stringify(snapshot), { mode: 384 });
36360
- renameSync18(tmp, path2);
36362
+ writeFileSync38(tmp, JSON.stringify(snapshot), { mode: 384 });
36363
+ renameSync19(tmp, path2);
36361
36364
  } catch {}
36362
36365
  }
36363
36366
  var init_config_snapshot = __esm(() => {
@@ -36365,13 +36368,13 @@ var init_config_snapshot = __esm(() => {
36365
36368
  });
36366
36369
 
36367
36370
  // gateway/boot-card-msgid.ts
36368
- import { readFileSync as readFileSync46, writeFileSync as writeFileSync38 } from "node:fs";
36371
+ import { readFileSync as readFileSync47, writeFileSync as writeFileSync39 } from "node:fs";
36369
36372
  function bootCardChatKey(chatId, threadId) {
36370
36373
  return `${chatId}:${threadId ?? ""}`;
36371
36374
  }
36372
36375
  function readStore(path2) {
36373
36376
  try {
36374
- const parsed = JSON.parse(readFileSync46(path2, "utf8"));
36377
+ const parsed = JSON.parse(readFileSync47(path2, "utf8"));
36375
36378
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
36376
36379
  return parsed;
36377
36380
  }
@@ -36390,7 +36393,7 @@ function saveBootCardMsgId(path2, chatKey3, messageId) {
36390
36393
  if (store2[chatKey3] === messageId)
36391
36394
  return;
36392
36395
  store2[chatKey3] = messageId;
36393
- writeFileSync38(path2, JSON.stringify(store2), "utf8");
36396
+ writeFileSync39(path2, JSON.stringify(store2), "utf8");
36394
36397
  } catch {}
36395
36398
  }
36396
36399
  var init_boot_card_msgid = () => {};
@@ -36408,8 +36411,8 @@ __export(exports_boot_card, {
36408
36411
  readRoutingMode: () => readRoutingMode,
36409
36412
  containerBootStartMs: () => containerBootStartMs
36410
36413
  });
36411
- import { join as join48 } from "path";
36412
- import { readFileSync as readFileSync47, statSync as statSync13 } from "fs";
36414
+ import { join as join49 } from "path";
36415
+ import { readFileSync as readFileSync48, statSync as statSync14 } from "fs";
36413
36416
  function resolvePersonaName(slug, loadConfig3) {
36414
36417
  try {
36415
36418
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -36431,7 +36434,7 @@ function shouldSkipDuplicateBootCard(gate, site) {
36431
36434
  }
36432
36435
  return { skip: false };
36433
36436
  }
36434
- function containerBootStartMs(fsImpl = { readFileSync: readFileSync47 }) {
36437
+ function containerBootStartMs(fsImpl = { readFileSync: readFileSync48 }) {
36435
36438
  try {
36436
36439
  const btimeLine = fsImpl.readFileSync("/proc/stat", "utf-8").split(`
36437
36440
  `).find((l) => l.startsWith("btime "));
@@ -36452,8 +36455,8 @@ function containerBootStartMs(fsImpl = { readFileSync: readFileSync47 }) {
36452
36455
  return null;
36453
36456
  }
36454
36457
  }
36455
- function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync47, statSync: statSync13 }) {
36456
- const path2 = join48(agentDir, ".routing-mode");
36458
+ function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync48, statSync: statSync14 }) {
36459
+ const path2 = join49(agentDir, ".routing-mode");
36457
36460
  try {
36458
36461
  const raw = fsImpl.readFileSync(path2, "utf-8");
36459
36462
  const line = raw.split(`
@@ -36560,7 +36563,7 @@ function renderBootCard(opts) {
36560
36563
  return stackCardLines(flatLines);
36561
36564
  }
36562
36565
  async function runAllProbes(opts) {
36563
- const claudeDir = join48(opts.agentDir, ".claude");
36566
+ const claudeDir = join49(opts.agentDir, ".claude");
36564
36567
  const probes = {};
36565
36568
  const slug = opts.agentSlug ?? opts.agentName;
36566
36569
  await Promise.allSettled([
@@ -37414,7 +37417,7 @@ __export(exports_tmux2, {
37414
37417
  captureAgentPane: () => captureAgentPane2
37415
37418
  });
37416
37419
  import { execFileSync as execFileSync8 } from "node:child_process";
37417
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync46, readdirSync as readdirSync12, statSync as statSync18, unlinkSync as unlinkSync25, writeFileSync as writeFileSync47 } from "node:fs";
37420
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync47, readdirSync as readdirSync13, statSync as statSync19, unlinkSync as unlinkSync26, writeFileSync as writeFileSync48 } from "node:fs";
37418
37421
  import { resolve as resolve12 } from "node:path";
37419
37422
  function captureAgentPane2(opts) {
37420
37423
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -37426,7 +37429,7 @@ function captureAgentPane2(opts) {
37426
37429
  const reasonSlug = sanitizeReason2(reason);
37427
37430
  const outPath = resolve12(outDir, `${ts}-${reasonSlug}.txt`);
37428
37431
  try {
37429
- mkdirSync46(outDir, { recursive: true, mode: 448 });
37432
+ mkdirSync47(outDir, { recursive: true, mode: 448 });
37430
37433
  } catch (err) {
37431
37434
  const msg = `mkdir crash-reports failed: ${err.message}`;
37432
37435
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -37465,7 +37468,7 @@ function captureAgentPane2(opts) {
37465
37468
  ` + `
37466
37469
  `;
37467
37470
  try {
37468
- writeFileSync47(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37471
+ writeFileSync48(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37469
37472
  mode: 384
37470
37473
  });
37471
37474
  } catch (err) {
@@ -37535,7 +37538,7 @@ function sleepSync3(ms) {
37535
37538
  function pruneOldReports2(dir, retain) {
37536
37539
  let entries;
37537
37540
  try {
37538
- entries = readdirSync12(dir);
37541
+ entries = readdirSync13(dir);
37539
37542
  } catch {
37540
37543
  return;
37541
37544
  }
@@ -37543,13 +37546,13 @@ function pruneOldReports2(dir, retain) {
37543
37546
  const full = resolve12(dir, n);
37544
37547
  let mtimeMs = 0;
37545
37548
  try {
37546
- mtimeMs = statSync18(full).mtimeMs;
37549
+ mtimeMs = statSync19(full).mtimeMs;
37547
37550
  } catch {}
37548
37551
  return { full, mtimeMs };
37549
37552
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
37550
37553
  for (const stale of files.slice(retain)) {
37551
37554
  try {
37552
- unlinkSync25(stale.full);
37555
+ unlinkSync26(stale.full);
37553
37556
  } catch {}
37554
37557
  }
37555
37558
  }
@@ -38286,7 +38289,7 @@ function buildGrantedKeyboard(scope) {
38286
38289
  const btn = scopeToOpenInDriveButton(scope);
38287
38290
  if (btn === null)
38288
38291
  return;
38289
- return new import_grammy14.InlineKeyboard().url(btn.text, btn.url);
38292
+ return new import_grammy15.InlineKeyboard().url(btn.text, btn.url);
38290
38293
  }
38291
38294
  async function handleApprovalCallback(ctx, data) {
38292
38295
  const parsed = parseApprovalCallback(data);
@@ -38332,11 +38335,11 @@ async function handleApprovalCallback(ctx, data) {
38332
38335
  } catch {}
38333
38336
  await ctx.answerCallbackQuery({ text: granted ? "Approved" : "Denied" });
38334
38337
  }
38335
- var import_grammy14;
38338
+ var import_grammy15;
38336
38339
  var init_approval_callback = __esm(() => {
38337
38340
  init_approval_card();
38338
38341
  init_client3();
38339
- import_grammy14 = __toESM(require_mod2(), 1);
38342
+ import_grammy15 = __toESM(require_mod2(), 1);
38340
38343
  });
38341
38344
 
38342
38345
  // ../src/telegram/materialize-bot-token.ts
@@ -38345,7 +38348,7 @@ __export(exports_materialize_bot_token, {
38345
38348
  materializeBotToken: () => materializeBotToken,
38346
38349
  BotTokenMaterializeError: () => BotTokenMaterializeError
38347
38350
  });
38348
- import { existsSync as existsSync53 } from "node:fs";
38351
+ import { existsSync as existsSync54 } from "node:fs";
38349
38352
  function pickConfiguredToken(config, agentName3) {
38350
38353
  if (agentName3) {
38351
38354
  const agent = config.agents?.[agentName3];
@@ -38359,7 +38362,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
38359
38362
  if (!passphrase)
38360
38363
  return null;
38361
38364
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
38362
- if (!existsSync53(vaultPath))
38365
+ if (!existsSync54(vaultPath))
38363
38366
  return null;
38364
38367
  try {
38365
38368
  const secrets = openVault(passphrase, vaultPath);
@@ -38509,28 +38512,28 @@ var init_approvals_commands = __esm(() => {
38509
38512
  });
38510
38513
 
38511
38514
  // gateway/gateway.ts
38512
- var import_grammy15 = __toESM(require_mod(), 1);
38515
+ var import_grammy16 = __toESM(require_mod(), 1);
38513
38516
  var import_runner3 = __toESM(require_mod3(), 1);
38514
- import { randomBytes as randomBytes10, createHash as createHash4 } from "crypto";
38517
+ import { randomBytes as randomBytes10, createHash as createHash5 } from "crypto";
38515
38518
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
38516
38519
  import {
38517
- readFileSync as readFileSync58,
38518
- writeFileSync as writeFileSync48,
38519
- mkdirSync as mkdirSync47,
38520
- readdirSync as readdirSync13,
38520
+ readFileSync as readFileSync59,
38521
+ writeFileSync as writeFileSync49,
38522
+ mkdirSync as mkdirSync48,
38523
+ readdirSync as readdirSync14,
38521
38524
  rmSync as rmSync6,
38522
- statSync as statSync19,
38523
- renameSync as renameSync21,
38525
+ statSync as statSync20,
38526
+ renameSync as renameSync22,
38524
38527
  realpathSync as realpathSync4,
38525
38528
  chmodSync as chmodSync14,
38526
38529
  openSync as openSync12,
38527
38530
  closeSync as closeSync12,
38528
- existsSync as existsSync54,
38529
- unlinkSync as unlinkSync26,
38530
- appendFileSync as appendFileSync7
38531
+ existsSync as existsSync55,
38532
+ unlinkSync as unlinkSync27,
38533
+ appendFileSync as appendFileSync8
38531
38534
  } from "fs";
38532
- import { homedir as homedir19 } from "os";
38533
- import { join as join60, sep as sep4, basename as basename15 } from "path";
38535
+ import { homedir as homedir20 } from "os";
38536
+ import { join as join61, sep as sep4, basename as basename15 } from "path";
38534
38537
 
38535
38538
  // plugin-logger.ts
38536
38539
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -41573,7 +41576,16 @@ import { AsyncLocalStorage } from "async_hooks";
41573
41576
 
41574
41577
  // retry-api-call.ts
41575
41578
  var import_grammy2 = __toESM(require_mod2(), 1);
41579
+ function isLocalResourceError(err) {
41580
+ const code = err?.code;
41581
+ if (typeof code === "string" && ["ENOSPC", "EDQUOT", "EIO", "ENOMEM"].includes(code)) {
41582
+ return true;
41583
+ }
41584
+ const msg = err instanceof Error ? err.message : String(err ?? "");
41585
+ return /\b(ENOSPC|EDQUOT|EIO|ENOMEM)\b/.test(msg) || /no space left on device/i.test(msg) || /disk quota exceeded/i.test(msg);
41586
+ }
41576
41587
  var LOCAL_RESOURCE_EXHAUSTED = "LOCAL_RESOURCE_EXHAUSTED";
41588
+ var GIVE_UP_MESSAGE = "retryApiCall: max retries exceeded";
41577
41589
  var FLOOD_WAIT_ACTIVE = "FLOOD_WAIT_ACTIVE";
41578
41590
  function makeFloodWaitActiveError(retryAfterSec, untilTs, original) {
41579
41591
  return Object.assign(new Error(FLOOD_WAIT_ACTIVE), {
@@ -41587,6 +41599,99 @@ function makeFloodWaitActiveError(retryAfterSec, untilTs, original) {
41587
41599
  function isFloodWaitActiveError(err) {
41588
41600
  return err instanceof Error && err.message === FLOOD_WAIT_ACTIVE;
41589
41601
  }
41602
+ var DEFAULT_MAX_FLOOD_SLEEP_MS = 120000;
41603
+ var DEFAULT_SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
41604
+ function createRetryApiCall(config = {}) {
41605
+ const maxRetries = config.maxRetries ?? 3;
41606
+ const maxFloodSleepMs = config.maxFloodSleepMs ?? DEFAULT_MAX_FLOOD_SLEEP_MS;
41607
+ const sleep = config.sleep ?? DEFAULT_SLEEP;
41608
+ const observer = config.observer;
41609
+ const log = config.log;
41610
+ const onFloodWait = config.onFloodWait;
41611
+ const floodWaitRemainingMs = config.floodWaitRemainingMs;
41612
+ function openWindowMs() {
41613
+ if (!floodWaitRemainingMs)
41614
+ return 0;
41615
+ try {
41616
+ const ms = floodWaitRemainingMs();
41617
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
41618
+ } catch {
41619
+ return 0;
41620
+ }
41621
+ }
41622
+ return async function retryApiCall(fn, opts) {
41623
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
41624
+ const remaining = openWindowMs();
41625
+ if (remaining > maxFloodSleepMs) {
41626
+ const retryAfterSec = Math.ceil(remaining / 1000);
41627
+ log?.(`telegram gateway: flood window still open for ${retryAfterSec}s \u2014 ` + `not issuing the ${opts?.verb ?? "api-call"} (would feed the ban)
41628
+ `);
41629
+ const gated = makeFloodWaitActiveError(retryAfterSec, Date.now() + remaining, null);
41630
+ observer?.onGiveUp?.({ attempts: attempt + 1, error: gated });
41631
+ throw gated;
41632
+ }
41633
+ try {
41634
+ return await fn();
41635
+ } catch (err) {
41636
+ const isGrammyErr = err instanceof import_grammy2.GrammyError;
41637
+ const msg = err instanceof Error ? err.message : String(err);
41638
+ const desc = isGrammyErr ? err.description : msg;
41639
+ if (isLocalResourceError(err)) {
41640
+ log?.(`telegram gateway: LOCAL resource exhaustion (${err.code ?? "disk/mem"}) \u2014 ` + `not retrying the send (would feed a flood ban); surfacing degraded state
41641
+ `);
41642
+ observer?.onGiveUp?.({ attempts: attempt + 1, error: err });
41643
+ throw Object.assign(new Error(LOCAL_RESOURCE_EXHAUSTED), { original: err });
41644
+ }
41645
+ if (isGrammyErr && err.error_code === 429) {
41646
+ const retryAfter = Number(err.parameters?.retry_after ?? 5);
41647
+ const delayMs = retryAfter * 1000;
41648
+ try {
41649
+ onFloodWait?.(retryAfter, opts);
41650
+ } catch {}
41651
+ if (delayMs > maxFloodSleepMs) {
41652
+ log?.(`telegram gateway: 429 flood ban of ${retryAfter}s exceeds the ` + `${Math.round(maxFloodSleepMs / 1000)}s in-process sleep ceiling \u2014 ` + `not sleeping it; surfacing degraded state
41653
+ `);
41654
+ observer?.onGiveUp?.({ attempts: attempt + 1, error: err });
41655
+ throw makeFloodWaitActiveError(retryAfter, Date.now() + delayMs, err);
41656
+ }
41657
+ log?.(`telegram gateway: 429 rate limited, waiting ${retryAfter}s
41658
+ `);
41659
+ observer?.onRetry?.({ attempt, reason: "flood_wait", delayMs });
41660
+ await sleep(delayMs);
41661
+ continue;
41662
+ }
41663
+ if (isGrammyErr && err.error_code === 400 && desc.includes("not modified")) {
41664
+ observer?.onBenign?.({ kind: "not_modified" });
41665
+ return;
41666
+ }
41667
+ if (isGrammyErr && err.error_code === 400 && (desc.includes("message to edit not found") || desc.includes("message to delete not found"))) {
41668
+ observer?.onBenign?.({
41669
+ kind: desc.includes("edit") ? "message_not_found" : "delete_not_found"
41670
+ });
41671
+ return;
41672
+ }
41673
+ if (isGrammyErr && err.error_code === 400 && desc.includes("thread not found") && opts?.threadId && opts?.chat_id) {
41674
+ throw Object.assign(new Error("THREAD_NOT_FOUND"), { original: err });
41675
+ }
41676
+ if (!isGrammyErr && (msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed") || msg.includes("ENOTFOUND"))) {
41677
+ if (attempt < maxRetries - 1) {
41678
+ const delayMs = Math.pow(2, attempt) * 1000;
41679
+ log?.(`telegram gateway: network error, retrying in ${delayMs / 1000}s: ${msg}
41680
+ `);
41681
+ observer?.onRetry?.({ attempt, reason: "network", delayMs });
41682
+ await sleep(delayMs);
41683
+ continue;
41684
+ }
41685
+ }
41686
+ observer?.onGiveUp?.({ attempts: attempt + 1, error: err });
41687
+ throw err;
41688
+ }
41689
+ }
41690
+ const giveUpErr = new Error(GIVE_UP_MESSAGE);
41691
+ observer?.onGiveUp?.({ attempts: maxRetries, error: giveUpErr });
41692
+ throw giveUpErr;
41693
+ };
41694
+ }
41590
41695
  async function retryWithThreadFallback(retry, send, opts) {
41591
41696
  try {
41592
41697
  return await retry(() => send(opts.threadId), {
@@ -61646,7 +61751,7 @@ function createChatLock() {
61646
61751
 
61647
61752
  // retry-api-call.ts
61648
61753
  var import_grammy6 = __toESM(require_mod2(), 1);
61649
- function isLocalResourceError(err) {
61754
+ function isLocalResourceError2(err) {
61650
61755
  const code2 = err?.code;
61651
61756
  if (typeof code2 === "string" && ["ENOSPC", "EDQUOT", "EIO", "ENOMEM"].includes(code2)) {
61652
61757
  return true;
@@ -61655,7 +61760,7 @@ function isLocalResourceError(err) {
61655
61760
  return /\b(ENOSPC|EDQUOT|EIO|ENOMEM)\b/.test(msg) || /no space left on device/i.test(msg) || /disk quota exceeded/i.test(msg);
61656
61761
  }
61657
61762
  var LOCAL_RESOURCE_EXHAUSTED2 = "LOCAL_RESOURCE_EXHAUSTED";
61658
- var GIVE_UP_MESSAGE = "retryApiCall: max retries exceeded";
61763
+ var GIVE_UP_MESSAGE2 = "retryApiCall: max retries exceeded";
61659
61764
  var FLOOD_WAIT_ACTIVE2 = "FLOOD_WAIT_ACTIVE";
61660
61765
  function makeFloodWaitActiveError2(retryAfterSec, untilTs, original) {
61661
61766
  return Object.assign(new Error(FLOOD_WAIT_ACTIVE2), {
@@ -61669,12 +61774,12 @@ function makeFloodWaitActiveError2(retryAfterSec, untilTs, original) {
61669
61774
  function isFloodWaitActiveError2(err) {
61670
61775
  return err instanceof Error && err.message === FLOOD_WAIT_ACTIVE2;
61671
61776
  }
61672
- var DEFAULT_MAX_FLOOD_SLEEP_MS = 120000;
61673
- var DEFAULT_SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
61777
+ var DEFAULT_MAX_FLOOD_SLEEP_MS2 = 120000;
61778
+ var DEFAULT_SLEEP2 = (ms) => new Promise((r) => setTimeout(r, ms));
61674
61779
  function createRetryApiCall2(config = {}) {
61675
61780
  const maxRetries = config.maxRetries ?? 3;
61676
- const maxFloodSleepMs = config.maxFloodSleepMs ?? DEFAULT_MAX_FLOOD_SLEEP_MS;
61677
- const sleep = config.sleep ?? DEFAULT_SLEEP;
61781
+ const maxFloodSleepMs = config.maxFloodSleepMs ?? DEFAULT_MAX_FLOOD_SLEEP_MS2;
61782
+ const sleep = config.sleep ?? DEFAULT_SLEEP2;
61678
61783
  const observer = config.observer;
61679
61784
  const log = config.log;
61680
61785
  const onFloodWait = config.onFloodWait;
@@ -61706,7 +61811,7 @@ function createRetryApiCall2(config = {}) {
61706
61811
  const isGrammyErr = err instanceof import_grammy6.GrammyError;
61707
61812
  const msg = err instanceof Error ? err.message : String(err);
61708
61813
  const desc = isGrammyErr ? err.description : msg;
61709
- if (isLocalResourceError(err)) {
61814
+ if (isLocalResourceError2(err)) {
61710
61815
  log?.(`telegram gateway: LOCAL resource exhaustion (${err.code ?? "disk/mem"}) \u2014 ` + `not retrying the send (would feed a flood ban); surfacing degraded state
61711
61816
  `);
61712
61817
  observer?.onGiveUp?.({ attempts: attempt + 1, error: err });
@@ -61757,7 +61862,7 @@ function createRetryApiCall2(config = {}) {
61757
61862
  throw err;
61758
61863
  }
61759
61864
  }
61760
- const giveUpErr = new Error(GIVE_UP_MESSAGE);
61865
+ const giveUpErr = new Error(GIVE_UP_MESSAGE2);
61761
61866
  observer?.onGiveUp?.({ attempts: maxRetries, error: giveUpErr });
61762
61867
  throw giveUpErr;
61763
61868
  };
@@ -69023,7 +69128,7 @@ var STARTUP_RETRY_DELAYS_MS = [
69023
69128
  32000,
69024
69129
  64000
69025
69130
  ];
69026
- var DEFAULT_SLEEP2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
69131
+ var DEFAULT_SLEEP3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
69027
69132
  function classifyStartupError(err) {
69028
69133
  if (!(err instanceof Error))
69029
69134
  return "other";
@@ -69043,7 +69148,7 @@ function classifyStartupError(err) {
69043
69148
  }
69044
69149
  async function gatewayStartupRetry(fn, opts = {}) {
69045
69150
  const delays = opts.delaysMs ?? STARTUP_RETRY_DELAYS_MS;
69046
- const sleep = opts.sleep ?? DEFAULT_SLEEP2;
69151
+ const sleep = opts.sleep ?? DEFAULT_SLEEP3;
69047
69152
  const onExhausted = opts.onExhausted ?? ((err) => {
69048
69153
  process.stderr.write(`telegram gateway: startup failed after ${delays.length + 1} attempts \u2014 exiting so systemd can restart: ${err}
69049
69154
  `);
@@ -73012,16 +73117,6 @@ function validateStringArray(field, value, tag = "gateway") {
73012
73117
 
73013
73118
  // format.ts
73014
73119
  var RICH_MESSAGE_MAX_CHARS2 = 32768;
73015
- function repairEscapedWhitespace2(text4) {
73016
- if (!/\\[nrt"\\]/.test(text4))
73017
- return text4;
73018
- const nonce = Math.random().toString(36).slice(2);
73019
- const BACKSLASH_PH = `\x00BK${nonce}_`;
73020
- const { masked, restore: restore2 } = maskCodeRegions2(text4, nonce);
73021
- const unescaped = masked.replace(/\\\\/g, BACKSLASH_PH).replace(/\\n/g, `
73022
- `).replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(new RegExp(BACKSLASH_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "\\");
73023
- return restore2(unescaped);
73024
- }
73025
73120
  function maskCodeRegions2(text4, nonce) {
73026
73121
  const FENCE_MASK_PH = `\x00RMF${nonce}_`;
73027
73122
  const INLINE_MASK_PH = `\x00RMI${nonce}_`;
@@ -73041,55 +73136,6 @@ function maskCodeRegions2(text4, nonce) {
73041
73136
  const stripPlaceholders = (s) => s.replace(anyMaskRe, "");
73042
73137
  return { masked, restore: restore2, placeholder: FENCE_MASK_PH, stripPlaceholders };
73043
73138
  }
73044
- function splitCollapsedInlineBullets2(text4) {
73045
- if (!/[\u2022\u00b7]/.test(text4))
73046
- return text4;
73047
- const interiorSep = /[ \t]+([\u2022\u00b7])[ \t]+/g;
73048
- return text4.split(`
73049
- `).map((line) => {
73050
- if (!/^[ \t]*[\u2022\u00b7*-] /.test(line))
73051
- return line;
73052
- if (!/[ \t][\u2022\u00b7][ \t]/.test(line))
73053
- return line;
73054
- return line.replace(interiorSep, `
73055
- $1 `);
73056
- }).join(`
73057
- `);
73058
- }
73059
- function normalizeParagraphBreaks2(text4) {
73060
- if (!text4.includes(`
73061
- `) && !/[\u2022\u00b7]/.test(text4))
73062
- return text4;
73063
- const nonce = Math.random().toString(36).slice(2);
73064
- const { masked: maskedRaw, restore: restore2, placeholder } = maskCodeRegions2(text4, nonce);
73065
- const masked = splitCollapsedInlineBullets2(maskedRaw);
73066
- let out = masked.replace(/\n[ \t\r]+\n(?:[ \t\r]*\n)*/g, `
73067
-
73068
- `).replace(/\n{3,}/g, `
73069
-
73070
- `);
73071
- const lines = out.split(`
73072
- `);
73073
- const pieces = [];
73074
- for (let i = 0;i < lines.length; i++) {
73075
- let line = lines[i];
73076
- const isLast = i === lines.length - 1;
73077
- const next = isLast ? "" : lines[i + 1];
73078
- const promote = !isLast && line.trim() !== "" && next.trim() !== "" && shouldPromoteBreak2(line, next, placeholder);
73079
- if (promote) {
73080
- line = line.replace(/[ \t\r]+$/, "");
73081
- }
73082
- pieces.push(line);
73083
- if (isLast)
73084
- break;
73085
- pieces.push(promote ? `
73086
- ` : `
73087
- `);
73088
- }
73089
- out = pieces.join("");
73090
- out = ensureBlockBoundaries2(out, placeholder);
73091
- return restore2(out);
73092
- }
73093
73139
  function hardenCardBreaks2(text4) {
73094
73140
  if (!text4.includes(`
73095
73141
  `))
@@ -73200,85 +73246,6 @@ function addParagraphSpacers2(text4) {
73200
73246
  return restore2(out.join(`
73201
73247
  `));
73202
73248
  }
73203
- function normalizePunctuation2(text4) {
73204
- if (!/[\u2014\u2013\u2022\u00b7]/.test(text4))
73205
- return text4;
73206
- const nonce = Math.random().toString(36).slice(2);
73207
- const { masked, restore: restore2 } = maskCodeRegions2(text4, nonce);
73208
- const linkMasks = [];
73209
- const LINK_MASK_PH = `\x00RML${nonce}_`;
73210
- const maskedLinks = masked.replace(/(\]\()([^)\n]*)(\))/g, (_m, open, href, close) => {
73211
- const idx = linkMasks.length;
73212
- linkMasks.push(href);
73213
- return `${open}${LINK_MASK_PH}${idx}\x00${close}`;
73214
- });
73215
- const maskedAutolinks = maskedLinks.replace(/(<)([a-zA-Z][a-zA-Z0-9+.-]*:[^>\s]*)(>)/g, (_m, lt, uri, gt) => {
73216
- const idx = linkMasks.length;
73217
- linkMasks.push(uri);
73218
- return `${lt}${LINK_MASK_PH}${idx}\x00${gt}`;
73219
- });
73220
- const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
73221
- const linkRestoreRe = new RegExp(`\x00RML${escNonce}_(\\d+)\x00`, "g");
73222
- const restoreLinks = (s) => s.replace(linkRestoreRe, (_m, idx) => linkMasks[Number(idx)] ?? _m);
73223
- let out = maskedAutolinks.replace(/(\S)[ \t][\u2014\u2013][ \t](?=(\S))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2014(?=(\w))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2013(?=\w)/g, "$1-");
73224
- out = restoreLinks(out);
73225
- out = out.split(`
73226
- `).map((line) => line.replace(/^([ \t]*)[\u2022\u00b7][ \t]+/, "$1- ")).join(`
73227
- `);
73228
- return restore2(out);
73229
- }
73230
- function listItemContent2(line) {
73231
- return line.trimStart().replace(/^(?:[-*+]|\d+[.)])\s+/, "");
73232
- }
73233
- function isFullyBolded2(fragment) {
73234
- return /^\*\*[^*]+\*\*[.,:;!?]?$/.test(fragment.trim());
73235
- }
73236
- function unbold2(fragment) {
73237
- return fragment.replace(/\*\*([^*]+)\*\*/g, "$1");
73238
- }
73239
- function stripExcessBold2(text4) {
73240
- if (!text4.includes("**"))
73241
- return text4;
73242
- const nonce = Math.random().toString(36).slice(2);
73243
- const { masked, restore: restore2, placeholder, stripPlaceholders } = maskCodeRegions2(text4, nonce);
73244
- const visible = stripPlaceholders(masked);
73245
- if (visible.length < 100)
73246
- return restore2(masked);
73247
- let boldChars = 0;
73248
- for (const m of visible.matchAll(/\*\*([^*]+)\*\*/g))
73249
- boldChars += m[1].length;
73250
- if (boldChars / visible.length > 0.3) {
73251
- return restore2(unbold2(masked));
73252
- }
73253
- const blocks = masked.split(/\n{2,}/);
73254
- const rebuilt = blocks.map((block) => {
73255
- const lines = block.split(`
73256
- `).filter((l) => l.trim() !== "");
73257
- if (lines.length === 0 || !block.includes("**"))
73258
- return block;
73259
- const listLines = lines.filter((l) => isListItemLine2(l));
73260
- if (listLines.length >= 2 && listLines.length === lines.length) {
73261
- if (lines.every((l) => isFullyBolded2(listItemContent2(l))))
73262
- return unbold2(block);
73263
- return block;
73264
- }
73265
- const isProseBlock = lines.every((l) => !isMarkerLine2(l, placeholder));
73266
- if (!isProseBlock)
73267
- return block;
73268
- if (!lines.every((l) => isFullyBolded2(l)))
73269
- return block;
73270
- if (lines.length === 1 && lines[0].trim().length <= 48)
73271
- return block;
73272
- return unbold2(block);
73273
- });
73274
- const seps = masked.match(/\n{2,}/g) ?? [];
73275
- let out = rebuilt[0] ?? "";
73276
- for (let i = 1;i < rebuilt.length; i++)
73277
- out += (seps[i - 1] ?? `
73278
-
73279
- `) + rebuilt[i];
73280
- return restore2(out);
73281
- }
73282
73249
  function isListItemLine2(line) {
73283
73250
  const t = line.trimStart();
73284
73251
  return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t);
@@ -73301,82 +73268,6 @@ function isBlockquoteLine2(line) {
73301
73268
  function isHeadingLine2(line) {
73302
73269
  return /^#{1,6}\s/.test(line.trimStart());
73303
73270
  }
73304
- function ensureBlockBoundaries2(text4, placeholder) {
73305
- if (!text4.includes(`
73306
- `))
73307
- return text4;
73308
- const lines = text4.split(`
73309
- `);
73310
- const result = [];
73311
- for (let i = 0;i < lines.length; i++) {
73312
- const line = lines[i];
73313
- const prev = result.length > 0 ? result[result.length - 1] : null;
73314
- const prevNonBlank = prev != null && prev.trim() !== "";
73315
- const curBlank = line.trim() === "";
73316
- if (prevNonBlank && !curBlank) {
73317
- const next = i + 1 < lines.length ? lines[i + 1] : "";
73318
- const prevIsTable = isTableRowLine2(prev);
73319
- const startsTableHere = !prevIsTable && (line.includes("|") && isTableDelimiterLine2(next) || isTableRowLine2(line) && isTableDelimiterLine2(next));
73320
- const startsFence = isFenceOpenLine2(line, placeholder) && !isFenceOpenLine2(prev, placeholder);
73321
- const startsQuote = isBlockquoteLine2(line) && !isBlockquoteLine2(prev);
73322
- const startsHeading = isHeadingLine2(line) && !isHeadingLine2(prev);
73323
- const startsList = isListItemLine2(line) && !isListItemLine2(prev);
73324
- if (startsTableHere || startsFence || startsQuote || startsHeading || startsList) {
73325
- result.push("");
73326
- }
73327
- }
73328
- if (prevNonBlank && !curBlank && isFenceOpenLine2(prev, placeholder)) {
73329
- const alreadySeparated = result.length > 0 && result[result.length - 1].trim() === "";
73330
- const curIsBlockStart = isFenceOpenLine2(line, placeholder) || isBlockquoteLine2(line) || isHeadingLine2(line) || isTableRowLine2(line) || isTableDelimiterLine2(line);
73331
- if (!alreadySeparated && !curIsBlockStart) {
73332
- result.push("");
73333
- }
73334
- }
73335
- if (prevNonBlank && !curBlank && isListItemLine2(prev) && !isListItemLine2(line)) {
73336
- const isIndentedContinuation = /^(\t| {4,})\S/.test(line);
73337
- const alreadySeparated = result.length > 0 && result[result.length - 1].trim() === "";
73338
- if (!isIndentedContinuation && !alreadySeparated) {
73339
- result.push("");
73340
- }
73341
- }
73342
- result.push(line);
73343
- }
73344
- return result.join(`
73345
- `);
73346
- }
73347
- function isMarkerLine2(line, placeholder) {
73348
- if (/^ {4,}\S/.test(line))
73349
- return true;
73350
- const t = line.trimStart();
73351
- if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
73352
- return true;
73353
- return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t) || t.startsWith(">") || /^#{1,6}\s/.test(t) || isTableRowLine2(line) || isTableDelimiterLine2(line) || t.startsWith("```") || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(t);
73354
- }
73355
- function shouldPromoteBreak2(prev, next, placeholder) {
73356
- if (isMarkerLine2(prev, placeholder) || isMarkerLine2(next, placeholder))
73357
- return false;
73358
- const nextTrimmed = next.trimStart();
73359
- if (nextTrimmed.length === 0)
73360
- return false;
73361
- const prevTrimmed = prev.trimEnd();
73362
- const unwrapped = prevTrimmed.replace(/[)"'\u2019\u201d\]]+$/, "");
73363
- const terminator = unwrapped.slice(-1);
73364
- if (terminator === "." || terminator === "!" || terminator === "?" || terminator === ":") {
73365
- return true;
73366
- }
73367
- const statStripped = prevTrimmed.replace(/[*_`]/g, "");
73368
- const colonIdx = statStripped.indexOf(":");
73369
- if (colonIdx > 0) {
73370
- const label = statStripped.slice(0, colonIdx).trim();
73371
- const value = statStripped.slice(colonIdx + 1);
73372
- const labelWords = label.length === 0 ? 0 : label.split(/\s+/).length;
73373
- const isStatLine = /^[ \t]+\S/.test(value) && labelWords >= 1 && labelWords <= 3 && label.length <= 24;
73374
- if (isStatLine && !/^[a-z]/.test(nextTrimmed)) {
73375
- return true;
73376
- }
73377
- }
73378
- return false;
73379
- }
73380
73271
  function hardSliceToCap2(text4, cap = RICH_MESSAGE_MAX_CHARS2) {
73381
73272
  if (cap <= 0)
73382
73273
  return [text4];
@@ -73666,121 +73557,221 @@ function decideRedeliver(input) {
73666
73557
  return { redeliver: true, framedText: frameRedelivery(text4) };
73667
73558
  }
73668
73559
 
73669
- // text-voice-scrub.ts
73670
- var NULL4 = "\x00";
73671
- var FENCE_PH2 = `${NULL4}VS_FENCE`;
73672
- var INLINE_PH4 = `${NULL4}VS_INLINE`;
73673
- var HTML_CODE_PH2 = `${NULL4}VS_HTMLCODE`;
73674
- var HTML_PRE_PH2 = `${NULL4}VS_HTMLPRE`;
73675
- var URL_PH2 = `${NULL4}VS_URL`;
73676
- var URL_RE3 = /https?:\/\/\S+/g;
73677
- function enabled4() {
73678
- const v = process.env.SWITCHROOM_DISABLE_VOICE_SCRUB;
73679
- return !(v === "1" || v === "true");
73560
+ // gateway/outbound-send-path.ts
73561
+ init_format();
73562
+ init_text_voice_scrub();
73563
+
73564
+ // temporal-normalize.ts
73565
+ function maskTemporalRegions(text4) {
73566
+ const nonce = Math.random().toString(36).slice(2);
73567
+ const PH = `\x00TN${nonce}_`;
73568
+ const masks = [];
73569
+ const push2 = (s) => {
73570
+ masks.push(s);
73571
+ return `${PH}${masks.length - 1}\x00`;
73572
+ };
73573
+ const masked = text4.replace(/```[\s\S]*?```/g, (m) => push2(m)).replace(/`[^`\n]+`/g, (m) => push2(m)).replace(/(\]\()([^)\n]*)(\))/g, (_m, open, href, close) => `${open}${push2(href)}${close}`).replace(/(<)([a-zA-Z][a-zA-Z0-9+.-]*:[^>\s]*)(>)/g, (_m, lt, uri, gt) => `${lt}${push2(uri)}${gt}`);
73574
+ const esc = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
73575
+ const re = new RegExp(`\x00TN${esc}_(\\d+)\x00`, "g");
73576
+ const restore2 = (s) => s.replace(re, (_m, idx) => masks[Number(idx)] ?? _m);
73577
+ return { masked, restore: restore2 };
73578
+ }
73579
+ function isBlockquoteLine3(line) {
73580
+ return line.trimStart().startsWith(">");
73680
73581
  }
73681
- function openerStripEnabled2() {
73682
- const v = process.env.SWITCHROOM_VOICE_STRIP_OPENERS;
73683
- return v === "1" || v === "true";
73582
+ function perNonQuoteLine(masked, fn) {
73583
+ return masked.split(`
73584
+ `).map((line) => isBlockquoteLine3(line) ? line : fn(line)).join(`
73585
+ `);
73684
73586
  }
73685
- var LEADING_AFFIRMATION_RE2 = /^(\s*)(you(?:['\u2019]| a)re absolutely right|you(?:['\u2019]| a)re so right|you(?:['\u2019]| a)re absolutely correct|absolutely right|exactly right|great catch|good catch|nice catch|spot on)\b(?:\s*$|\s*[!.,;:\u2014\u2013-][\s!.,;:\u2014\u2013-]*)/i;
73686
- function stripLeadingAffirmation2(text4) {
73687
- const m = LEADING_AFFIRMATION_RE2.exec(text4);
73688
- if (!m)
73689
- return { out: text4, count: 0 };
73690
- const leadingWs = m[1] ?? "";
73691
- const rest = text4.slice(m[0].length);
73692
- if (rest.trim().length === 0)
73693
- return { out: text4, count: 0 };
73694
- const recapped = rest.replace(/^(\s*)([a-z])/, (_m, ws, ch) => ws + ch.toUpperCase());
73695
- return { out: leadingWs + recapped, count: 1 };
73587
+ function renderLocalDate(ms, tz) {
73588
+ const parts = new Intl.DateTimeFormat("en-US", {
73589
+ timeZone: tz,
73590
+ weekday: "short",
73591
+ day: "numeric",
73592
+ month: "short"
73593
+ }).formatToParts(new Date(ms));
73594
+ const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
73595
+ return `${get("weekday")} ${get("day")} ${get("month")}`;
73696
73596
  }
73697
- function park2(text4) {
73698
- const parts = [];
73699
- let parked = text4;
73700
- parked = parked.replace(/```[\s\S]*?```/g, (m) => {
73701
- const idx = parts.length;
73702
- parts.push({ prefix: FENCE_PH2, idx, raw: m });
73703
- return `${FENCE_PH2}${idx}${NULL4}`;
73704
- });
73705
- parked = parked.replace(/<pre>[\s\S]*?<\/pre>/gi, (m) => {
73706
- const idx = parts.length;
73707
- parts.push({ prefix: HTML_PRE_PH2, idx, raw: m });
73708
- return `${HTML_PRE_PH2}${idx}${NULL4}`;
73709
- });
73710
- parked = parked.replace(/<code[^>]*>[\s\S]*?<\/code>/gi, (m) => {
73711
- const idx = parts.length;
73712
- parts.push({ prefix: HTML_CODE_PH2, idx, raw: m });
73713
- return `${HTML_CODE_PH2}${idx}${NULL4}`;
73714
- });
73715
- parked = parked.replace(/`[^`\n]+`/g, (m) => {
73716
- const idx = parts.length;
73717
- parts.push({ prefix: INLINE_PH4, idx, raw: m });
73718
- return `${INLINE_PH4}${idx}${NULL4}`;
73719
- });
73720
- parked = parked.replace(URL_RE3, (m) => {
73721
- const idx = parts.length;
73722
- parts.push({ prefix: URL_PH2, idx, raw: m });
73723
- return `${URL_PH2}${idx}${NULL4}`;
73724
- });
73725
- return { parked, parts };
73597
+ function renderLocalClock(ms, tz) {
73598
+ return new Intl.DateTimeFormat("en-US", {
73599
+ timeZone: tz,
73600
+ hour: "numeric",
73601
+ minute: "2-digit",
73602
+ hour12: true
73603
+ }).format(new Date(ms)).replace(/\s([AP])M$/, (_m, p) => ` ${p.toLowerCase()}m`);
73726
73604
  }
73727
- function restore2(text4, parts) {
73728
- let restored = text4;
73729
- for (let i = parts.length - 1;i >= 0; i--) {
73730
- const p = parts[i];
73731
- restored = restored.replace(`${p.prefix}${p.idx}${NULL4}`, () => p.raw);
73732
- }
73733
- return restored;
73605
+ function renderLocalDatetime(ms, tz) {
73606
+ return `${renderLocalDate(ms, tz)} ${renderLocalClock(ms, tz)} ${tzAbbrev(ms, tz)}`;
73734
73607
  }
73735
- function replaceDashes2(text4) {
73736
- let replaced = 0;
73737
- let out = text4;
73738
- const capFirst = (after) => {
73739
- if (/^[a-z][A-Z]/.test(after))
73740
- return after;
73741
- return after.replace(/^([a-z])/, (_m, ch) => ch.toUpperCase());
73742
- };
73743
- out = out.replace(/(\S) [\u2014\u2013] (\S)(\S?)/g, (m, before, after, peek) => {
73744
- if (/\d/.test(before) && /\d/.test(after))
73608
+ var ISO_UTC_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|\+00:00)/g;
73609
+ var LABELLED_UTC_RE = /\b(\d{1,2})(?::(\d{2}))?\s?(am|pm)?\s?(UTC|GMT)\b/gi;
73610
+ function convertUtcInLine(line, tz, nowMs2) {
73611
+ let out = line.replace(ISO_UTC_RE, (m) => {
73612
+ const ms = Date.parse(m);
73613
+ if (Number.isNaN(ms))
73745
73614
  return m;
73746
- replaced++;
73747
- return `${before}. ${capFirst(after + peek)}`;
73748
- });
73749
- out = out.replace(/ [\u2014\u2013](\s*\n)/g, (_m, ws) => {
73750
- replaced++;
73751
- return `.${ws}`;
73615
+ return renderLocalDatetime(ms, tz);
73752
73616
  });
73753
- out = out.replace(/(\w)[\u2014\u2013](\w)(\w?)/g, (m, before, after, peek) => {
73754
- if (/\d/.test(before) && /\d/.test(after))
73617
+ out = out.replace(LABELLED_UTC_RE, (m, hh, mm, ampm) => {
73618
+ if (mm == null && ampm == null)
73755
73619
  return m;
73756
- replaced++;
73757
- return `${before}. ${capFirst(after + peek)}`;
73758
- });
73759
- out = out.replace(/[\u2014\u2013]/g, () => {
73760
- replaced++;
73761
- return "-";
73620
+ let h = Number(hh);
73621
+ const pm = ampm != null && /pm/i.test(ampm);
73622
+ if (ampm != null) {
73623
+ if (h > 12)
73624
+ return m;
73625
+ if (h === 12)
73626
+ h = pm ? 12 : 0;
73627
+ else if (pm)
73628
+ h += 12;
73629
+ }
73630
+ if (h > 23)
73631
+ return m;
73632
+ const minute = mm != null ? Number(mm) : 0;
73633
+ if (minute > 59)
73634
+ return m;
73635
+ const now = new Date(nowMs2);
73636
+ const ms = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), h, minute);
73637
+ return `${renderLocalClock(ms, tz)} ${tzAbbrev(ms, tz)}`;
73762
73638
  });
73763
- return { out, replaced };
73639
+ return out;
73764
73640
  }
73765
- function scrubVoice2(text4) {
73766
- if (!enabled4() || text4.length === 0) {
73767
- return { scrubbed: text4, replaced: 0, openersStripped: 0 };
73641
+ function normalizeUtcDatetimes(text4, tz, nowMs2) {
73642
+ if (!text4)
73643
+ return text4;
73644
+ try {
73645
+ if (!/\d{4}-\d{2}-\d{2}T|\b(?:UTC|GMT)\b/i.test(text4))
73646
+ return text4;
73647
+ const { masked, restore: restore2 } = maskTemporalRegions(text4);
73648
+ const out = perNonQuoteLine(masked, (line) => convertUtcInLine(line, tz, nowMs2));
73649
+ return restore2(out);
73650
+ } catch {
73651
+ return text4;
73768
73652
  }
73769
- const { parked, parts } = park2(text4);
73770
- const opener = openerStripEnabled2() ? stripLeadingAffirmation2(parked) : { out: parked, count: 0 };
73771
- const { out, replaced } = replaceDashes2(opener.out);
73772
- const total = replaced + opener.count;
73773
- if (total === 0) {
73774
- return { scrubbed: text4, replaced: 0, openersStripped: 0 };
73653
+ }
73654
+ var REL = "(today|tonight|this\\s+morning|this\\s+afternoon|this\\s+evening|tomorrow|yesterday)";
73655
+ var SEP = "([\\s,]*(?:on\\s+)?\\(?\\s*)";
73656
+ var WD = "(?:mon|tue|wed|thu|fri|sat|sun)[a-z]*";
73657
+ var MON = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*";
73658
+ var WD_GROUP = `((?:${WD})[\\s,]+)?`;
73659
+ var DATE_CORE = `((?:\\d{1,2}\\s+${MON})|(?:${MON}\\s+\\d{1,2})|(?:\\d{4}-\\d{2}-\\d{2})|(?:\\d{1,2}\\/\\d{2}))`;
73660
+ var MONTHS4 = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
73661
+ var WEEKDAY_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
73662
+ function buildRelRegex() {
73663
+ return new RegExp(REL + SEP + WD_GROUP + DATE_CORE, "gi");
73664
+ }
73665
+ function parseDateCore(core) {
73666
+ let m;
73667
+ if (m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(core)) {
73668
+ return { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) };
73669
+ }
73670
+ if (m = /^(\d{1,2})\/(\d{2})$/.exec(core)) {
73671
+ return { year: null, month: Number(m[2]), day: Number(m[1]) };
73672
+ }
73673
+ if (m = /^(\d{1,2})\s+([a-z]+)$/i.exec(core)) {
73674
+ const mon = MONTHS4.indexOf(m[2].slice(0, 3).toLowerCase());
73675
+ if (mon < 0)
73676
+ return null;
73677
+ return { year: null, month: mon + 1, day: Number(m[1]) };
73678
+ }
73679
+ if (m = /^([a-z]+)\s+(\d{1,2})$/i.exec(core)) {
73680
+ const mon = MONTHS4.indexOf(m[1].slice(0, 3).toLowerCase());
73681
+ if (mon < 0)
73682
+ return null;
73683
+ return { year: null, month: mon + 1, day: Number(m[2]) };
73684
+ }
73685
+ return null;
73686
+ }
73687
+ function dayIndex(y, mo1, d) {
73688
+ return Math.floor(Date.UTC(y, mo1 - 1, d) / 86400000);
73689
+ }
73690
+ function resolveYear(parsed, todayY, todayIdx) {
73691
+ if (parsed.year != null)
73692
+ return parsed.year;
73693
+ let best = todayY;
73694
+ let bestAbs = Infinity;
73695
+ for (const y of [todayY - 1, todayY, todayY + 1]) {
73696
+ const diff = Math.abs(dayIndex(y, parsed.month, parsed.day) - todayIdx);
73697
+ if (diff < bestAbs) {
73698
+ bestAbs = diff;
73699
+ best = y;
73700
+ }
73701
+ }
73702
+ return best;
73703
+ }
73704
+ function matchCase(sample, replacement) {
73705
+ if (sample.length > 0 && sample[0] === sample[0].toUpperCase() && sample[0] !== sample[0].toLowerCase()) {
73706
+ return replacement.charAt(0).toUpperCase() + replacement.slice(1);
73707
+ }
73708
+ return replacement;
73709
+ }
73710
+ var TIME_OF_DAY_RE = /^(tonight|this\s+)/i;
73711
+ function rewriteRelativeInLine(line, tz, nowMs2) {
73712
+ const today = localDay(nowMs2, tz);
73713
+ const [ty, tm, td] = today.split("-").map(Number);
73714
+ const todayIdx = dayIndex(ty, tm, td);
73715
+ return line.replace(buildRelRegex(), (full, rel, sep4, wdGroup, core) => {
73716
+ const parsed = parseDateCore(core);
73717
+ if (parsed == null)
73718
+ return full;
73719
+ const year = resolveYear(parsed, ty, todayIdx);
73720
+ const targetIdx = dayIndex(year, parsed.month, parsed.day);
73721
+ const diff = targetIdx - todayIdx;
73722
+ if (diff < -1 || diff > 1)
73723
+ return full;
73724
+ const isTimeOfDay = TIME_OF_DAY_RE.test(rel);
73725
+ let newRel = rel;
73726
+ if (isTimeOfDay) {
73727
+ if (diff !== 0)
73728
+ return full;
73729
+ } else {
73730
+ const want = diff === 1 ? "tomorrow" : diff === 0 ? "today" : "yesterday";
73731
+ newRel = matchCase(rel, want);
73732
+ }
73733
+ let newWdGroup = wdGroup;
73734
+ if (wdGroup != null) {
73735
+ const trueDow = new Date(Date.UTC(year, parsed.month - 1, parsed.day)).getUTCDay();
73736
+ const trueShort = WEEKDAY_SHORT[trueDow];
73737
+ const wdMatch = /^([a-z]+)([\s,]+)$/i.exec(wdGroup);
73738
+ if (wdMatch != null) {
73739
+ const writtenShort = wdMatch[1].slice(0, 3).toLowerCase();
73740
+ if (writtenShort !== trueShort.toLowerCase()) {
73741
+ const wasLong = wdMatch[1].length > 3;
73742
+ const corrected = wasLong ? longWeekday(trueDow) : trueShort;
73743
+ newWdGroup = matchCase(wdMatch[1], corrected) + wdMatch[2];
73744
+ }
73745
+ }
73746
+ }
73747
+ return `${newRel}${sep4}${newWdGroup ?? ""}${core}`;
73748
+ });
73749
+ }
73750
+ var WEEKDAY_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
73751
+ function longWeekday(dow) {
73752
+ return WEEKDAY_LONG[dow];
73753
+ }
73754
+ function normalizeRelativeDays(text4, tz, nowMs2) {
73755
+ if (!text4)
73756
+ return text4;
73757
+ try {
73758
+ if (!/\b(today|tonight|this\s+(?:morning|afternoon|evening)|tomorrow|yesterday)\b/i.test(text4)) {
73759
+ return text4;
73760
+ }
73761
+ const { masked, restore: restore2 } = maskTemporalRegions(text4);
73762
+ const out = perNonQuoteLine(masked, (line) => rewriteRelativeInLine(line, tz, nowMs2));
73763
+ return restore2(out);
73764
+ } catch {
73765
+ return text4;
73775
73766
  }
73776
- return { scrubbed: restore2(out, parts), replaced: total, openersStripped: opener.count };
73767
+ }
73768
+ function normalizeTemporal(text4, tz, nowMs2) {
73769
+ return normalizeRelativeDays(normalizeUtcDatetimes(text4, tz, nowMs2), tz, nowMs2);
73777
73770
  }
73778
73771
 
73779
73772
  // gateway/outbound-send-path.ts
73780
- init_format();
73781
- init_text_voice_scrub();
73782
73773
  var import_grammy10 = __toESM(require_mod2(), 1);
73783
- import { statSync as statSync9 } from "fs";
73774
+ import { statSync as statSync10 } from "fs";
73784
73775
  import { extname } from "path";
73785
73776
 
73786
73777
  // gateway/backstop-delivery.ts
@@ -73911,6 +73902,321 @@ function combineReadBackResults(perId) {
73911
73902
  init_format();
73912
73903
  init_rich_send();
73913
73904
 
73905
+ // outbox.ts
73906
+ import { createHash as createHash3 } from "node:crypto";
73907
+ import {
73908
+ existsSync as existsSync24,
73909
+ mkdirSync as mkdirSync25,
73910
+ readFileSync as readFileSync24,
73911
+ readdirSync as readdirSync6,
73912
+ renameSync as renameSync8,
73913
+ statSync as statSync9,
73914
+ unlinkSync as unlinkSync12,
73915
+ writeFileSync as writeFileSync22,
73916
+ appendFileSync as appendFileSync4
73917
+ } from "node:fs";
73918
+ import { join as join28 } from "node:path";
73919
+ import { homedir as homedir10 } from "node:os";
73920
+ function sha256Hex(s) {
73921
+ return createHash3("sha256").update(s, "utf8").digest("hex");
73922
+ }
73923
+ function resolveStateDir2(explicit) {
73924
+ if (explicit != null && explicit !== "")
73925
+ return explicit;
73926
+ const env = process.env.TELEGRAM_STATE_DIR;
73927
+ if (env != null && env !== "")
73928
+ return env;
73929
+ const home2 = process.env.HOME ?? homedir10();
73930
+ return join28(home2, ".claude", "channels", "telegram");
73931
+ }
73932
+ function resolveOutboxDir(stateDir) {
73933
+ return join28(resolveStateDir2(stateDir), "outbox");
73934
+ }
73935
+ var JOURNAL_FILE = "delivered.jsonl";
73936
+ var OUTBOX_MAX_AGE_MS = 30 * 60000;
73937
+ var OUTBOX_QUIET_MS = 5000;
73938
+ var OUTBOX_SENDING_TIMEOUT_MS = 60000;
73939
+ function listPendingRecords(stateDir) {
73940
+ const dir = resolveOutboxDir(stateDir);
73941
+ try {
73942
+ return readdirSync6(dir).filter((f) => f.endsWith(".json") && f !== JOURNAL_FILE && !f.startsWith("."));
73943
+ } catch {
73944
+ return [];
73945
+ }
73946
+ }
73947
+ function readOutboxRecord(fileName, stateDir) {
73948
+ try {
73949
+ const raw = readFileSync24(join28(resolveOutboxDir(stateDir), fileName), "utf8");
73950
+ return JSON.parse(raw);
73951
+ } catch {
73952
+ return null;
73953
+ }
73954
+ }
73955
+ function claimRecord(nonce, stateDir) {
73956
+ const dir = resolveOutboxDir(stateDir);
73957
+ const from = join28(dir, `${nonce}.json`);
73958
+ const to = join28(dir, `${nonce}.sending`);
73959
+ try {
73960
+ renameSync8(from, to);
73961
+ return to;
73962
+ } catch {
73963
+ return null;
73964
+ }
73965
+ }
73966
+ function releaseClaim(nonce, stateDir) {
73967
+ const dir = resolveOutboxDir(stateDir);
73968
+ try {
73969
+ renameSync8(join28(dir, `${nonce}.sending`), join28(dir, `${nonce}.json`));
73970
+ } catch {}
73971
+ }
73972
+ function removeClaimed(nonce, stateDir) {
73973
+ try {
73974
+ unlinkSync12(join28(resolveOutboxDir(stateDir), `${nonce}.sending`));
73975
+ } catch {}
73976
+ }
73977
+ function reclaimStaleSending(stateDir, now = Date.now()) {
73978
+ const dir = resolveOutboxDir(stateDir);
73979
+ let files;
73980
+ try {
73981
+ files = readdirSync6(dir).filter((f) => f.endsWith(".sending"));
73982
+ } catch {
73983
+ return;
73984
+ }
73985
+ for (const f of files) {
73986
+ try {
73987
+ const st = statSync9(join28(dir, f));
73988
+ if (now - st.mtimeMs > OUTBOX_SENDING_TIMEOUT_MS) {
73989
+ renameSync8(join28(dir, f), join28(dir, f.replace(/\.sending$/, ".json")));
73990
+ }
73991
+ } catch {}
73992
+ }
73993
+ }
73994
+ function readDeliveredNonces(stateDir) {
73995
+ const set = new Set;
73996
+ const path2 = join28(resolveOutboxDir(stateDir), JOURNAL_FILE);
73997
+ if (!existsSync24(path2))
73998
+ return set;
73999
+ try {
74000
+ for (const line of readFileSync24(path2, "utf8").split(`
74001
+ `)) {
74002
+ if (!line)
74003
+ continue;
74004
+ try {
74005
+ const e = JSON.parse(line);
74006
+ if (e.turnNonce)
74007
+ set.add(e.turnNonce);
74008
+ } catch {}
74009
+ }
74010
+ } catch {}
74011
+ return set;
74012
+ }
74013
+ var JOURNAL_KEEP = 2000;
74014
+ var JOURNAL_ROTATE_AT = 4000;
74015
+ function appendDelivered(entry, stateDir) {
74016
+ const dir = resolveOutboxDir(stateDir);
74017
+ const path2 = join28(dir, JOURNAL_FILE);
74018
+ try {
74019
+ mkdirSync25(dir, { recursive: true });
74020
+ appendFileSync4(path2, JSON.stringify(entry) + `
74021
+ `, { mode: 384 });
74022
+ compactJournalIfLarge(path2);
74023
+ } catch {}
74024
+ }
74025
+ function compactJournalIfLarge(path2) {
74026
+ try {
74027
+ const lines = readFileSync24(path2, "utf8").split(`
74028
+ `).filter((l) => l.length > 0);
74029
+ if (lines.length <= JOURNAL_ROTATE_AT)
74030
+ return;
74031
+ const kept = lines.slice(lines.length - JOURNAL_KEEP);
74032
+ const tmp = `${path2}.${process.pid}.compact`;
74033
+ writeFileSync22(tmp, kept.join(`
74034
+ `) + `
74035
+ `, { mode: 384 });
74036
+ renameSync8(tmp, path2);
74037
+ } catch {}
74038
+ }
74039
+ function clearOutboxRecord(nonce, stateDir) {
74040
+ const dir = resolveOutboxDir(stateDir);
74041
+ for (const suffix of [".json", ".sending"]) {
74042
+ try {
74043
+ unlinkSync12(join28(dir, `${nonce}${suffix}`));
74044
+ } catch {}
74045
+ }
74046
+ }
74047
+ function decideOutboxSweep(input) {
74048
+ const {
74049
+ record,
74050
+ now,
74051
+ deliveredNonces,
74052
+ textAlreadyDelivered,
74053
+ routable,
74054
+ routePrefix = "",
74055
+ quietMs = OUTBOX_QUIET_MS,
74056
+ maxAgeMs = OUTBOX_MAX_AGE_MS
74057
+ } = input;
74058
+ if (deliveredNonces.has(record.turnNonce))
74059
+ return { action: "skip-journaled" };
74060
+ const age = now - record.createdAt;
74061
+ if (age < quietMs)
74062
+ return { action: "skip-quiet" };
74063
+ if (textAlreadyDelivered)
74064
+ return { action: "skip-dedup" };
74065
+ if (!routable)
74066
+ return { action: "skip-unroutable" };
74067
+ const delayed = age > maxAgeMs;
74068
+ const prefix = (delayed ? "(delayed) " : "") + routePrefix;
74069
+ return { action: delayed ? "send-delayed" : "send", text: prefix + record.text };
74070
+ }
74071
+ function resolveOutboxChat(record, deps) {
74072
+ if (record.chatId != null && record.chatId !== "") {
74073
+ return { chatId: record.chatId, threadId: record.threadId ?? null, via: "anchor" };
74074
+ }
74075
+ if (record.anchorContent && deps.registryChainLookup) {
74076
+ const hit = deps.registryChainLookup(record.anchorContent);
74077
+ if (hit != null)
74078
+ return { chatId: hit.chatId, threadId: hit.threadId, via: "registry" };
74079
+ }
74080
+ if (record.originChatId != null && record.originChatId !== "") {
74081
+ return { chatId: record.originChatId, threadId: record.originThreadId ?? null, via: "origin" };
74082
+ }
74083
+ return null;
74084
+ }
74085
+ function extractTaskId(anchorContent) {
74086
+ if (typeof anchorContent !== "string")
74087
+ return null;
74088
+ const m = anchorContent.match(/<task-id>\s*([^<\s]+)\s*<\/task-id>/) ?? anchorContent.match(/task[_-]?id="([^"]+)"/i) ?? anchorContent.match(/task[_-]?id:\s*([^\s,}"']+)/i);
74089
+ return m ? m[1] : null;
74090
+ }
74091
+
74092
+ // registry/subagents-schema.ts
74093
+ function countRunningBackgroundSubagents(db2) {
74094
+ const row = db2.prepare("SELECT count(*) AS n FROM subagents WHERE background = 1 AND status = 'running'").get();
74095
+ return row?.n ?? 0;
74096
+ }
74097
+ function recordSubagentEnd(db2, args) {
74098
+ db2.prepare(`
74099
+ UPDATE subagents
74100
+ SET
74101
+ ended_at = ?,
74102
+ status = ?,
74103
+ result_summary = COALESCE(?, result_summary)
74104
+ WHERE id = ?
74105
+ AND status NOT IN ('completed', 'failed')
74106
+ `).run(args.endedAt, args.status, args.resultSummary ?? null, args.id);
74107
+ }
74108
+ function recordSubagentStall(db2, args) {
74109
+ args.stalledAt;
74110
+ if (args.reason !== undefined) {
74111
+ db2.prepare(`
74112
+ UPDATE subagents
74113
+ SET status = 'stalled',
74114
+ result_summary = ?
74115
+ WHERE id = ?
74116
+ AND status NOT IN ('completed', 'failed')
74117
+ `).run(args.reason, args.id);
74118
+ return;
74119
+ }
74120
+ db2.prepare(`
74121
+ UPDATE subagents
74122
+ SET status = 'stalled'
74123
+ WHERE id = ?
74124
+ AND status NOT IN ('completed', 'failed')
74125
+ `).run(args.id);
74126
+ }
74127
+ function reapStuckRunningRows(db2, args) {
74128
+ const cutoff = args.now - args.ttlMs;
74129
+ const rows = db2.prepare(`
74130
+ SELECT id, jsonl_agent_id FROM subagents
74131
+ WHERE status = 'running'
74132
+ AND background = 1
74133
+ AND COALESCE(last_activity_at, started_at) < ?
74134
+ `).all(cutoff);
74135
+ const candidates = args.isLive ? rows.filter((r) => !args.isLive(r.jsonl_agent_id)) : rows;
74136
+ for (const row of candidates) {
74137
+ recordSubagentStall(db2, {
74138
+ id: row.id,
74139
+ stalledAt: args.now,
74140
+ reason: `reaped: stuck in running past ${Math.round(args.ttlMs / 60000)}min ttl (jsonl linkage likely missing)`
74141
+ });
74142
+ }
74143
+ return { reaped: candidates.length, ids: candidates.map((r) => r.id) };
74144
+ }
74145
+ function recordSubagentResume(db2, args) {
74146
+ args.resumedAt;
74147
+ db2.prepare(`
74148
+ UPDATE subagents
74149
+ SET status = 'running'
74150
+ WHERE id = ?
74151
+ AND status = 'stalled'
74152
+ `).run(args.id);
74153
+ }
74154
+ function bumpSubagentActivity(db2, args) {
74155
+ db2.prepare(`
74156
+ UPDATE subagents
74157
+ SET last_activity_at = ?
74158
+ WHERE id = ?
74159
+ `).run(args.ts, args.id);
74160
+ }
74161
+ function recordSubagentModel(db2, args) {
74162
+ db2.prepare(`
74163
+ UPDATE subagents
74164
+ SET model = ?
74165
+ WHERE id = ?
74166
+ `).run(args.model, args.id);
74167
+ }
74168
+ function recordNestedSubagentDispatch(db2, args) {
74169
+ db2.prepare(`
74170
+ INSERT OR IGNORE INTO subagents
74171
+ (id, parent_session_id, parent_turn_key, agent_type, description,
74172
+ background, started_at, last_activity_at, status, jsonl_agent_id,
74173
+ parent_agent_id)
74174
+ VALUES (?, NULL, NULL, ?, ?, ?, ?, ?, 'running', NULL, ?)
74175
+ `).run(args.toolUseId, args.agentType ?? null, args.description ?? null, args.background ? 1 : 0, args.now, args.now, args.parentJsonlAgentId);
74176
+ db2.prepare(`
74177
+ UPDATE subagents
74178
+ SET parent_agent_id = COALESCE(parent_agent_id, ?),
74179
+ parent_turn_key = COALESCE(
74180
+ parent_turn_key,
74181
+ (SELECT p.parent_turn_key FROM subagents p
74182
+ WHERE p.jsonl_agent_id = ? LIMIT 1)
74183
+ )
74184
+ WHERE id = ?
74185
+ `).run(args.parentJsonlAgentId, args.parentJsonlAgentId, args.toolUseId);
74186
+ }
74187
+ function resolveSubagentOriginTurnKey(db2, jsonlAgentId, maxHops = 5) {
74188
+ const seen = new Set;
74189
+ let currentJsonlId = jsonlAgentId;
74190
+ for (let hop = 0;hop <= maxHops && currentJsonlId != null; hop++) {
74191
+ if (seen.has(currentJsonlId))
74192
+ return null;
74193
+ seen.add(currentJsonlId);
74194
+ const row = db2.prepare("SELECT parent_turn_key, parent_agent_id FROM subagents WHERE jsonl_agent_id = ? LIMIT 1").get(currentJsonlId);
74195
+ if (row == null)
74196
+ return null;
74197
+ if (row.parent_turn_key != null && row.parent_turn_key.length > 0)
74198
+ return row.parent_turn_key;
74199
+ currentJsonlId = row.parent_agent_id ?? null;
74200
+ }
74201
+ return null;
74202
+ }
74203
+
74204
+ // gateway/outbox-sweep.ts
74205
+ function journalExternalDelivery(args, stateDir, now = Date.now()) {
74206
+ const nonce = args.turnNonce;
74207
+ if (nonce == null || nonce === "")
74208
+ return;
74209
+ appendDelivered({
74210
+ turnNonce: nonce,
74211
+ textSha256: sha256Hex(args.text),
74212
+ tgMessageId: args.tgMessageId,
74213
+ ts: now,
74214
+ deliverySource: "reply-tool",
74215
+ ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn }
74216
+ }, stateDir);
74217
+ clearOutboxRecord(nonce, stateDir);
74218
+ }
74219
+
73914
74220
  // gateway/chat-id-fallback.ts
73915
74221
  function resolveChatIdFallback(rawChatId, access, turnSessionChatId, lastKnownChatId, hasLiveTurn) {
73916
74222
  if (access.allowFrom.includes(rawChatId) || rawChatId in access.groups) {
@@ -73951,12 +74257,12 @@ function decideOverPing(input) {
73951
74257
  // silent-reply-anchor.ts
73952
74258
  init_format();
73953
74259
  var TELEGRAM_MSG_CAP2 = RICH_MESSAGE_MAX_CHARS;
73954
- function enabled5() {
74260
+ function enabled4() {
73955
74261
  const v = process.env.SWITCHROOM_DISABLE_SILENT_REPLY_AUTOEDIT;
73956
74262
  return !(v === "1" || v === "true");
73957
74263
  }
73958
74264
  function decideSilentReplyAnchor(input) {
73959
- if (!enabled5()) {
74265
+ if (!enabled4()) {
73960
74266
  return { kind: "fresh", becomesAnchor: false };
73961
74267
  }
73962
74268
  if (!input.effectivelySilent) {
@@ -74279,11 +74585,11 @@ function classifyPhotoFile(path2) {
74279
74585
  }
74280
74586
 
74281
74587
  // streaming-metrics.ts
74282
- function enabled6() {
74588
+ function enabled5() {
74283
74589
  return process.env.SWITCHROOM_STREAMING_METRICS === "1";
74284
74590
  }
74285
74591
  function logStreamingEvent2(ev) {
74286
- if (!enabled6())
74592
+ if (!enabled5())
74287
74593
  return;
74288
74594
  const line = JSON.stringify({ ts: monotonicNowMs2(), ...ev });
74289
74595
  try {
@@ -74300,26 +74606,26 @@ function monotonicNowMs2() {
74300
74606
  }
74301
74607
 
74302
74608
  // silent-end.ts
74303
- import { existsSync as existsSync24, readFileSync as readFileSync24, writeFileSync as writeFileSync22, unlinkSync as unlinkSync12, mkdirSync as mkdirSync25 } from "node:fs";
74304
- import { dirname as dirname11, join as join28 } from "node:path";
74305
- import { homedir as homedir10 } from "node:os";
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";
74611
+ import { homedir as homedir11 } from "node:os";
74306
74612
  var SILENT_END_MAX_RETRIES2 = 2;
74307
74613
  var SILENT_END_STALE_RECORD_MAX_AGE_MS2 = 30 * 60000;
74308
74614
  function silentEndFallbackText(turnDurationMs) {
74309
74615
  const elapsed = typeof turnDurationMs === "number" && Number.isFinite(turnDurationMs) && turnDurationMs > 0 ? ` (waited ${Math.round(turnDurationMs / 1000)}s)` : "";
74310
74616
  return "\u26a0\ufe0f The agent finished working but didn\u2019t send a reply" + elapsed + " \u2014 your last message may not have been answered. Please try asking again.";
74311
74617
  }
74312
- function resolveStateDir2(deps) {
74618
+ function resolveStateDir3(deps) {
74313
74619
  if (deps?.stateDir != null)
74314
74620
  return deps.stateDir;
74315
74621
  const env = process.env.TELEGRAM_STATE_DIR;
74316
74622
  if (env != null && env !== "")
74317
74623
  return env;
74318
- const home2 = process.env.HOME ?? homedir10();
74319
- return join28(home2, ".claude", "channels", "telegram");
74624
+ const home2 = process.env.HOME ?? homedir11();
74625
+ return join29(home2, ".claude", "channels", "telegram");
74320
74626
  }
74321
74627
  function resolveStatePath3(deps) {
74322
- return join28(resolveStateDir2(deps), "silent-end-pending.json");
74628
+ return join29(resolveStateDir3(deps), "silent-end-pending.json");
74323
74629
  }
74324
74630
  function emitLog2(deps, line) {
74325
74631
  if (deps?.log != null)
@@ -74331,8 +74637,8 @@ function writeSilentEndState2(args, deps) {
74331
74637
  const statePath = resolveStatePath3(deps);
74332
74638
  let retryCount = 0;
74333
74639
  try {
74334
- if (existsSync24(statePath)) {
74335
- const prev = JSON.parse(readFileSync24(statePath, "utf8"));
74640
+ if (existsSync25(statePath)) {
74641
+ const prev = JSON.parse(readFileSync25(statePath, "utf8"));
74336
74642
  if (prev.turnKey === args.turnKey && typeof prev.retryCount === "number") {
74337
74643
  retryCount = prev.retryCount;
74338
74644
  }
@@ -74348,8 +74654,8 @@ function writeSilentEndState2(args, deps) {
74348
74654
  timestamp: Date.now()
74349
74655
  };
74350
74656
  try {
74351
- mkdirSync25(dirname11(statePath), { recursive: true });
74352
- writeFileSync22(statePath, JSON.stringify(state3), "utf8");
74657
+ mkdirSync26(dirname11(statePath), { recursive: true });
74658
+ writeFileSync23(statePath, JSON.stringify(state3), "utf8");
74353
74659
  emitLog2(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
74354
74660
  `);
74355
74661
  } catch (err) {
@@ -74359,13 +74665,13 @@ function writeSilentEndState2(args, deps) {
74359
74665
  }
74360
74666
  function clearSilentEndState2(turnKey3, deps) {
74361
74667
  const statePath = resolveStatePath3(deps);
74362
- if (!existsSync24(statePath))
74668
+ if (!existsSync25(statePath))
74363
74669
  return;
74364
74670
  try {
74365
- const prev = JSON.parse(readFileSync24(statePath, "utf8"));
74671
+ const prev = JSON.parse(readFileSync25(statePath, "utf8"));
74366
74672
  if (prev.turnKey != null && prev.turnKey !== turnKey3)
74367
74673
  return;
74368
- unlinkSync12(statePath);
74674
+ unlinkSync13(statePath);
74369
74675
  emitLog2(deps, `silent-end: cleared state file turnKey=${turnKey3}
74370
74676
  `);
74371
74677
  } catch {}
@@ -74396,10 +74702,10 @@ function settleCapturedProseDelivery(outcome, effects) {
74396
74702
  }
74397
74703
  function readSilentEndState2(deps) {
74398
74704
  const statePath = resolveStatePath3(deps);
74399
- if (!existsSync24(statePath))
74705
+ if (!existsSync25(statePath))
74400
74706
  return null;
74401
74707
  try {
74402
- return JSON.parse(readFileSync24(statePath, "utf8"));
74708
+ return JSON.parse(readFileSync25(statePath, "utf8"));
74403
74709
  } catch {
74404
74710
  return null;
74405
74711
  }
@@ -74428,10 +74734,21 @@ function recordSilentTurnEnd2(args, deps) {
74428
74734
  var recordUndeliveredTurnEnd2 = recordSilentTurnEnd2;
74429
74735
 
74430
74736
  // gateway/outbound-send-path.ts
74431
- function normalizeOutboundBody(rawText, site, redact2) {
74432
- let text4 = normalizeParagraphBreaks(repairEscapedWhitespace(rawText));
74737
+ function normalizeOutboundBody(rawText, site, redact2, opts = {}) {
74738
+ const { literalText = false, addSpacers = false, tz, nowMs: nowMs2 } = opts;
74739
+ let text4 = repairEscapedWhitespace(rawText);
74740
+ if (!literalText)
74741
+ text4 = normalizeParagraphBreaks(text4);
74433
74742
  text4 = redact2(text4, site);
74434
- text4 = stripExcessBold(normalizePunctuation(text4));
74743
+ if (!literalText) {
74744
+ let formatted = stripExcessBold(normalizePunctuation(text4));
74745
+ if (addSpacers)
74746
+ formatted = addParagraphSpacers(formatted);
74747
+ text4 = formatted;
74748
+ if (tz != null && nowMs2 != null) {
74749
+ text4 = normalizeTemporal(text4, tz, nowMs2);
74750
+ }
74751
+ }
74435
74752
  let voiceReplaced = 0;
74436
74753
  const scrub = scrubVoice(text4);
74437
74754
  if (scrub.replaced > 0) {
@@ -74683,7 +75000,10 @@ async function sendReply(deps, req) {
74683
75000
  const rawText = args.text;
74684
75001
  if (rawText == null || rawText === "")
74685
75002
  throw new Error("reply: text is required and cannot be empty");
74686
- const _normalized = normalizeOutboundBody(rawText, "reply", redactOutboundText);
75003
+ const _normalized = normalizeOutboundBody(rawText, "reply", redactOutboundText, {
75004
+ tz: resolveEnvTimezone(),
75005
+ nowMs: Date.now()
75006
+ });
74687
75007
  let text4 = _normalized.text;
74688
75008
  if (_normalized.voiceReplaced > 0) {
74689
75009
  emitRuntimeMetric2({
@@ -74862,7 +75182,7 @@ ${url}`;
74862
75182
  }
74863
75183
  for (const f of files) {
74864
75184
  assertSendable(f);
74865
- const st = statSync9(f);
75185
+ const st = statSync10(f);
74866
75186
  if (st.size > MAX_ATTACHMENT_BYTES) {
74867
75187
  throw new Error(`file too large: ${f} (${(st.size / 1024 / 1024).toFixed(1)}MB, max 50MB)`);
74868
75188
  }
@@ -75063,6 +75383,9 @@ ${url}`;
75063
75383
  closeObligationOnSubstantiveReply(args, turn2, replyRoutedOriginTurn);
75064
75384
  }
75065
75385
  outboundDedup.record(chat_id, threadId, decision.mergedText, Date.now(), turn2?.registryKey ?? null);
75386
+ if (isFinalAnswerReply({ text: decision.mergedText, disableNotification: modelDisableNotification })) {
75387
+ journalExternalDelivery({ turnNonce: turn2?.turnId ?? null, text: decision.mergedText, tgMessageId: decision.messageId, replyAlreadyDeliveredThisTurn: true });
75388
+ }
75066
75389
  silentAnchorEditDone = true;
75067
75390
  } catch (err) {
75068
75391
  process.stderr.write(`telegram gateway: silent-reply auto-edit failed, ` + `falling back to fresh send: ${err instanceof Error ? err.message : String(err)}
@@ -75336,7 +75659,11 @@ ${url}`;
75336
75659
  process.stderr.write(`telegram channel: reply: finalized chatId=${chat_id} messageIds=[${sentIds.join(",")}] chunks=${chunks.length}
75337
75660
  `);
75338
75661
  if (sentIds.length > 0) {
75339
- outboundDedup.record(chat_id, threadId, text4, Date.now(), getCurrentTurn()?.registryKey ?? null);
75662
+ const t = getCurrentTurn();
75663
+ outboundDedup.record(chat_id, threadId, text4, Date.now(), t?.registryKey ?? null);
75664
+ if (shouldJournalReplySiteDelivery({ text: rawText, disableNotification: modelDisableNotification })) {
75665
+ journalExternalDelivery({ turnNonce: t?.turnId ?? null, text: text4, tgMessageId: sentIds[sentIds.length - 1], replyAlreadyDeliveredThisTurn: true });
75666
+ }
75340
75667
  }
75341
75668
  return { content: [{ type: "text", text: result }] };
75342
75669
  }
@@ -75389,6 +75716,7 @@ async function deliverCapturedProse(deps, args) {
75389
75716
  } catch {}
75390
75717
  }
75391
75718
  outboundDedup.record(chatId, threadId, text4, now, registryKey);
75719
+ journalExternalDelivery({ turnNonce: originTurnId, text: text4, tgMessageId: sentIds[sentIds.length - 1], replyAlreadyDeliveredThisTurn: false });
75392
75720
  process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from ` + `transcript scan (chat=${chatId} origin=${originTurnId})
75393
75721
  `);
75394
75722
  outcome = "sent";
@@ -75841,8 +76169,37 @@ class LivenessTracker {
75841
76169
  }
75842
76170
  }
75843
76171
 
75844
- // gateway/stream-render.ts
76172
+ // gateway/outbound-send-path.ts
76173
+ init_format();
76174
+ init_text_voice_scrub();
76175
+ var import_grammy11 = __toESM(require_mod2(), 1);
75845
76176
  init_format();
76177
+ init_rich_send();
76178
+ function normalizeOutboundBody2(rawText, site, redact2, opts = {}) {
76179
+ const { literalText = false, addSpacers = false, tz, nowMs: nowMs2 } = opts;
76180
+ let text4 = repairEscapedWhitespace(rawText);
76181
+ if (!literalText)
76182
+ text4 = normalizeParagraphBreaks(text4);
76183
+ text4 = redact2(text4, site);
76184
+ if (!literalText) {
76185
+ let formatted = stripExcessBold(normalizePunctuation(text4));
76186
+ if (addSpacers)
76187
+ formatted = addParagraphSpacers(formatted);
76188
+ text4 = formatted;
76189
+ if (tz != null && nowMs2 != null) {
76190
+ text4 = normalizeTemporal(text4, tz, nowMs2);
76191
+ }
76192
+ }
76193
+ let voiceReplaced = 0;
76194
+ const scrub = scrubVoice(text4);
76195
+ if (scrub.replaced > 0) {
76196
+ text4 = scrub.scrubbed;
76197
+ voiceReplaced = scrub.replaced;
76198
+ }
76199
+ return { text: text4, voiceReplaced };
76200
+ }
76201
+
76202
+ // gateway/stream-render.ts
75846
76203
  init_history();
75847
76204
 
75848
76205
  // narrative-dedup.ts
@@ -75908,20 +76265,20 @@ var INTERRUPTED_VIA = new Set([
75908
76265
  init_rich_send();
75909
76266
 
75910
76267
  // runtime-metrics.ts
75911
- import { mkdirSync as mkdirSync27, appendFileSync as appendFileSync4 } from "node:fs";
75912
- import { dirname as dirname12, join as join30 } from "node:path";
76268
+ import { mkdirSync as mkdirSync28, appendFileSync as appendFileSync5 } from "node:fs";
76269
+ import { dirname as dirname12, join as join31 } from "node:path";
75913
76270
  function resolveJsonlPath2() {
75914
76271
  const override = process.env.SWITCHROOM_RUNTIME_METRICS_PATH;
75915
76272
  if (override && override.trim() !== "")
75916
76273
  return override.trim();
75917
76274
  const base = process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent";
75918
- return join30(base, "runtime-metrics.jsonl");
76275
+ return join31(base, "runtime-metrics.jsonl");
75919
76276
  }
75920
76277
  function appendJsonl2(line) {
75921
76278
  const path2 = resolveJsonlPath2();
75922
76279
  try {
75923
- mkdirSync27(dirname12(path2), { recursive: true });
75924
- appendFileSync4(path2, line + `
76280
+ mkdirSync28(dirname12(path2), { recursive: true });
76281
+ appendFileSync5(path2, line + `
75925
76282
  `, "utf-8");
75926
76283
  } catch (err) {
75927
76284
  process.stderr.write(`runtime-metrics: jsonl write failed: ${err.message}
@@ -75942,9 +76299,6 @@ function emitRuntimeMetric2(event) {
75942
76299
  captureEvent(event.kind, { ...event, ts: wrapped.ts });
75943
76300
  }
75944
76301
 
75945
- // gateway/stream-render.ts
75946
- init_text_voice_scrub();
75947
-
75948
76302
  // gateway/feed-open-gate.ts
75949
76303
  function shouldEarlyOpenLiveness(input) {
75950
76304
  if (!input.enabled)
@@ -76414,28 +76768,28 @@ function detectStatusSurfaceDegraded(t) {
76414
76768
  // gateway/turn-active-marker.ts
76415
76769
  import {
76416
76770
  closeSync as closeSync5,
76417
- existsSync as existsSync27,
76418
- mkdirSync as mkdirSync28,
76771
+ existsSync as existsSync28,
76772
+ mkdirSync as mkdirSync29,
76419
76773
  openSync as openSync5,
76420
- readFileSync as readFileSync25,
76421
- statSync as statSync10,
76422
- unlinkSync as unlinkSync13,
76774
+ readFileSync as readFileSync26,
76775
+ statSync as statSync11,
76776
+ unlinkSync as unlinkSync14,
76423
76777
  utimesSync,
76424
- writeFileSync as writeFileSync23
76778
+ writeFileSync as writeFileSync24
76425
76779
  } from "node:fs";
76426
- import { join as join31 } from "node:path";
76780
+ import { join as join32 } from "node:path";
76427
76781
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
76428
76782
  var TURN_ACTIVE_HARD_TTL_MS = 10 * 60000;
76429
76783
  function writeTurnActiveMarker(stateDir, marker) {
76430
76784
  try {
76431
- mkdirSync28(stateDir, { recursive: true });
76432
- writeFileSync23(join31(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
76785
+ mkdirSync29(stateDir, { recursive: true });
76786
+ writeFileSync24(join32(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
76433
76787
  `, { mode: 384 });
76434
76788
  } catch {}
76435
76789
  }
76436
76790
  function touchTurnActiveMarker(stateDir) {
76437
- const path2 = join31(stateDir, TURN_ACTIVE_MARKER_FILE);
76438
- if (!existsSync27(path2))
76791
+ const path2 = join32(stateDir, TURN_ACTIVE_MARKER_FILE);
76792
+ if (!existsSync28(path2))
76439
76793
  return;
76440
76794
  const now = new Date;
76441
76795
  try {
@@ -76449,13 +76803,13 @@ function touchTurnActiveMarker(stateDir) {
76449
76803
  }
76450
76804
  function removeTurnActiveMarker(stateDir) {
76451
76805
  try {
76452
- unlinkSync13(join31(stateDir, TURN_ACTIVE_MARKER_FILE));
76806
+ unlinkSync14(join32(stateDir, TURN_ACTIVE_MARKER_FILE));
76453
76807
  } catch {}
76454
76808
  }
76455
76809
  function readTurnActiveMarkerAgeMs(stateDir, now) {
76456
- const path2 = join31(stateDir, TURN_ACTIVE_MARKER_FILE);
76810
+ const path2 = join32(stateDir, TURN_ACTIVE_MARKER_FILE);
76457
76811
  try {
76458
- const st = statSync10(path2);
76812
+ const st = statSync11(path2);
76459
76813
  return (now ?? Date.now()) - st.mtimeMs;
76460
76814
  } catch {
76461
76815
  return null;
@@ -76512,7 +76866,7 @@ function finalizeBackstopSendGated(turn, send) {
76512
76866
  var MAX_LIFETIME_MS2 = 30 * 60000;
76513
76867
  var stateByKey2 = new Map;
76514
76868
  var activeDeps3 = null;
76515
- function enabled7() {
76869
+ function enabled6() {
76516
76870
  const v = process.env.SWITCHROOM_DISABLE_PENDING_PROGRESS;
76517
76871
  return !(v === "1" || v === "true");
76518
76872
  }
@@ -76520,7 +76874,7 @@ function nowMs2() {
76520
76874
  return activeDeps3?.nowMs ? activeDeps3.nowMs() : Date.now();
76521
76875
  }
76522
76876
  function noteTurnEnd2(key) {
76523
- if (!enabled7())
76877
+ if (!enabled6())
76524
76878
  return;
76525
76879
  const s = stateByKey2.get(key);
76526
76880
  if (s == null)
@@ -77231,24 +77585,18 @@ function handleSessionEvent(deps, ev) {
77231
77585
  return;
77232
77586
  }
77233
77587
  if (turnEndDecision === "flush" && flushDecision.kind === "flush") {
77234
- let capturedText = flushDecision.text;
77235
- capturedText = normalizeParagraphBreaks(repairEscapedWhitespace(capturedText));
77236
77588
  const backstopChatId = chatId;
77237
77589
  const backstopThreadId = threadId;
77238
77590
  const backstopCtrl = ctrl;
77239
- capturedText = redactOutboundText(capturedText, "turn_flush");
77240
- capturedText = stripExcessBold(normalizePunctuation(capturedText));
77241
- {
77242
- const scrub = scrubVoice(capturedText);
77243
- if (scrub.replaced > 0) {
77244
- capturedText = scrub.scrubbed;
77245
- emitRuntimeMetric2({
77246
- kind: "voice_scrub_applied",
77247
- chatKey: statusKey(backstopChatId, backstopThreadId),
77248
- replaced: scrub.replaced,
77249
- site: "turn_flush"
77250
- });
77251
- }
77591
+ const _flushNorm = normalizeOutboundBody2(flushDecision.text, "turn_flush", redactOutboundText, { tz: resolveEnvTimezone(), nowMs: Date.now() });
77592
+ let capturedText = _flushNorm.text;
77593
+ if (_flushNorm.voiceReplaced > 0) {
77594
+ emitRuntimeMetric2({
77595
+ kind: "voice_scrub_applied",
77596
+ chatKey: statusKey(backstopChatId, backstopThreadId),
77597
+ replaced: _flushNorm.voiceReplaced,
77598
+ site: "turn_flush"
77599
+ });
77252
77600
  }
77253
77601
  turn.finalAnswerDelivered = true;
77254
77602
  turn.finalAnswerSubstantive = true;
@@ -78346,19 +78694,19 @@ function resolveAgentDirFromEnv() {
78346
78694
  }
78347
78695
 
78348
78696
  // active-reactions.ts
78349
- import { readFileSync as readFileSync26, writeFileSync as writeFileSync24, renameSync as renameSync8, existsSync as existsSync28, unlinkSync as unlinkSync14 } from "node:fs";
78350
- import { join as join32 } from "node:path";
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";
78351
78699
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
78352
78700
  function reactionsPath(agentDir) {
78353
- return join32(agentDir, ACTIVE_REACTIONS_FILENAME);
78701
+ return join33(agentDir, ACTIVE_REACTIONS_FILENAME);
78354
78702
  }
78355
78703
  function readActiveReactions(agentDir) {
78356
78704
  const p = reactionsPath(agentDir);
78357
- if (!existsSync28(p))
78705
+ if (!existsSync29(p))
78358
78706
  return [];
78359
78707
  let raw;
78360
78708
  try {
78361
- raw = readFileSync26(p, "utf-8");
78709
+ raw = readFileSync27(p, "utf-8");
78362
78710
  } catch {
78363
78711
  return [];
78364
78712
  }
@@ -78384,15 +78732,15 @@ function writeActiveReactions(agentDir, reactions) {
78384
78732
  const p = reactionsPath(agentDir);
78385
78733
  if (reactions.length === 0) {
78386
78734
  try {
78387
- unlinkSync14(p);
78735
+ unlinkSync15(p);
78388
78736
  } catch {}
78389
78737
  return;
78390
78738
  }
78391
78739
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
78392
78740
  try {
78393
- writeFileSync24(tmp, JSON.stringify(reactions) + `
78741
+ writeFileSync25(tmp, JSON.stringify(reactions) + `
78394
78742
  `, "utf-8");
78395
- renameSync8(tmp, p);
78743
+ renameSync9(tmp, p);
78396
78744
  } catch {}
78397
78745
  }
78398
78746
  function addActiveReaction(agentDir, reaction) {
@@ -78409,24 +78757,24 @@ function removeActiveReaction(agentDir, chatId, messageId) {
78409
78757
  }
78410
78758
  function clearActiveReactions(agentDir) {
78411
78759
  try {
78412
- unlinkSync14(reactionsPath(agentDir));
78760
+ unlinkSync15(reactionsPath(agentDir));
78413
78761
  } catch {}
78414
78762
  }
78415
78763
 
78416
78764
  // active-reactions.ts
78417
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync25, renameSync as renameSync9, existsSync as existsSync29, unlinkSync as unlinkSync15 } from "node:fs";
78418
- import { join as join33 } from "node:path";
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";
78419
78767
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
78420
78768
  function reactionsPath2(agentDir) {
78421
- return join33(agentDir, ACTIVE_REACTIONS_FILENAME2);
78769
+ return join34(agentDir, ACTIVE_REACTIONS_FILENAME2);
78422
78770
  }
78423
78771
  function readActiveReactions2(agentDir) {
78424
78772
  const p = reactionsPath2(agentDir);
78425
- if (!existsSync29(p))
78773
+ if (!existsSync30(p))
78426
78774
  return [];
78427
78775
  let raw;
78428
78776
  try {
78429
- raw = readFileSync27(p, "utf-8");
78777
+ raw = readFileSync28(p, "utf-8");
78430
78778
  } catch {
78431
78779
  return [];
78432
78780
  }
@@ -78450,7 +78798,7 @@ function readActiveReactions2(agentDir) {
78450
78798
  }
78451
78799
  function clearActiveReactions2(agentDir) {
78452
78800
  try {
78453
- unlinkSync15(reactionsPath2(agentDir));
78801
+ unlinkSync16(reactionsPath2(agentDir));
78454
78802
  } catch {}
78455
78803
  }
78456
78804
 
@@ -78871,7 +79219,7 @@ async function loadFromAuthBroker(options = {}) {
78871
79219
  }
78872
79220
 
78873
79221
  // gateway/folder-picker-handler.ts
78874
- var import_grammy11 = __toESM(require_mod2(), 1);
79222
+ var import_grammy12 = __toESM(require_mod2(), 1);
78875
79223
 
78876
79224
  // ../src/drive/folder-picker.ts
78877
79225
  var DRIVE_ID_RE2 = /^[A-Za-z0-9_-]+$/;
@@ -79190,7 +79538,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
79190
79538
  const confirmText = `\u2705 Granted ${deps.agentName} access to folder ${parsed.folder_id}
79191
79539
  ` + `Scope: \`${scope}\`
79192
79540
  ` + `Revoke with: \`/approvals revoke ${decisionId}\``;
79193
- const kb = new import_grammy11.InlineKeyboard().url("\uD83D\uDCD6 Open in Drive", `https://drive.google.com/drive/folders/${parsed.folder_id}`);
79541
+ const kb = new import_grammy12.InlineKeyboard().url("\uD83D\uDCD6 Open in Drive", `https://drive.google.com/drive/folders/${parsed.folder_id}`);
79194
79542
  try {
79195
79543
  await ctx.editMessageText({ markdown: confirmText }, {
79196
79544
  reply_markup: kb
@@ -79199,7 +79547,7 @@ async function recordGrantAndConfirm(ctx, deps, parsed) {
79199
79547
  await ctx.answerCallbackQuery({ text: "Allowed" });
79200
79548
  }
79201
79549
  function toKeyboard(spec) {
79202
- const kb = new import_grammy11.InlineKeyboard;
79550
+ const kb = new import_grammy12.InlineKeyboard;
79203
79551
  for (let r = 0;r < spec.rows.length; r++) {
79204
79552
  const row = spec.rows[r];
79205
79553
  for (const btn of row) {
@@ -79294,17 +79642,17 @@ async function approvalRecord(args, opts) {
79294
79642
  }
79295
79643
 
79296
79644
  // quota-check.ts
79297
- import { readFileSync as readFileSync28, existsSync as existsSync30 } from "fs";
79298
- import { join as join34 } from "path";
79645
+ import { readFileSync as readFileSync29, existsSync as existsSync31 } from "fs";
79646
+ import { join as join35 } from "path";
79299
79647
  var OAUTH_BETA2 = "oauth-2025-04-20";
79300
79648
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
79301
79649
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
79302
79650
  function readOauthToken2(claudeConfigDir) {
79303
- const tokenFile = join34(claudeConfigDir, ".oauth-token");
79304
- if (!existsSync30(tokenFile))
79651
+ const tokenFile = join35(claudeConfigDir, ".oauth-token");
79652
+ if (!existsSync31(tokenFile))
79305
79653
  return null;
79306
79654
  try {
79307
- const raw = readFileSync28(tokenFile, "utf-8").trim();
79655
+ const raw = readFileSync29(tokenFile, "utf-8").trim();
79308
79656
  return raw.length > 0 ? raw : null;
79309
79657
  } catch {
79310
79658
  return null;
@@ -80640,15 +80988,15 @@ async function menuWithBannerStatic(deps, banner) {
80640
80988
  }
80641
80989
 
80642
80990
  // gateway/session-model-file.ts
80643
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync26, renameSync as renameSync10, rmSync as rmSync4 } from "node:fs";
80644
- import { join as join35 } from "node:path";
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";
80645
80993
  var SESSION_MODEL_FILE = ".session-model";
80646
80994
  var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
80647
80995
  var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
80648
80996
  function atomicWrite(path2, content3) {
80649
80997
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
80650
- writeFileSync26(tmp, content3, "utf8");
80651
- renameSync10(tmp, path2);
80998
+ writeFileSync27(tmp, content3, "utf8");
80999
+ renameSync11(tmp, path2);
80652
81000
  }
80653
81001
  function serializeSessionModel(rec) {
80654
81002
  return `${JSON.stringify({
@@ -80663,28 +81011,28 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
80663
81011
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
80664
81012
  }
80665
81013
  try {
80666
- rmSync4(join35(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81014
+ rmSync4(join36(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
80667
81015
  } catch {}
80668
- atomicWrite(join35(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
81016
+ atomicWrite(join36(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
80669
81017
  }
80670
81018
  function readSessionModelFileRaw(agentDir) {
80671
81019
  try {
80672
- return readFileSync29(join35(agentDir, SESSION_MODEL_FILE), "utf8");
81020
+ return readFileSync30(join36(agentDir, SESSION_MODEL_FILE), "utf8");
80673
81021
  } catch {
80674
81022
  return null;
80675
81023
  }
80676
81024
  }
80677
81025
  function clearSessionModelFile(agentDir) {
80678
81026
  try {
80679
- rmSync4(join35(agentDir, SESSION_MODEL_FILE), { force: true });
81027
+ rmSync4(join36(agentDir, SESSION_MODEL_FILE), { force: true });
80680
81028
  } catch {}
80681
81029
  }
80682
81030
  function consumeSessionModelCarrierOnHealthyBoot(agentDir) {
80683
81031
  try {
80684
- rmSync4(join35(agentDir, SESSION_MODEL_FILE), { force: true });
81032
+ rmSync4(join36(agentDir, SESSION_MODEL_FILE), { force: true });
80685
81033
  } catch {}
80686
81034
  try {
80687
- rmSync4(join35(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
81035
+ rmSync4(join36(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
80688
81036
  } catch {}
80689
81037
  }
80690
81038
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -80693,12 +81041,12 @@ function restoreSessionModelFileRaw(agentDir, raw) {
80693
81041
  return;
80694
81042
  }
80695
81043
  try {
80696
- atomicWrite(join35(agentDir, SESSION_MODEL_FILE), raw);
81044
+ atomicWrite(join36(agentDir, SESSION_MODEL_FILE), raw);
80697
81045
  } catch {}
80698
81046
  }
80699
81047
  function readConfiguredDefaultModel(agentDir) {
80700
81048
  try {
80701
- const v = readFileSync29(join35(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
81049
+ const v = readFileSync30(join36(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
80702
81050
  return v.length > 0 ? v : null;
80703
81051
  } catch {
80704
81052
  return null;
@@ -80710,12 +81058,12 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
80710
81058
  if (!EFFORT_LEVEL_RE.test(level)) {
80711
81059
  throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
80712
81060
  }
80713
- atomicWrite(join35(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
81061
+ atomicWrite(join36(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
80714
81062
  `);
80715
81063
  }
80716
81064
  function clearSessionEffortFile(agentDir) {
80717
81065
  try {
80718
- rmSync4(join35(agentDir, SESSION_EFFORT_FILE), { force: true });
81066
+ rmSync4(join36(agentDir, SESSION_EFFORT_FILE), { force: true });
80719
81067
  } catch {}
80720
81068
  }
80721
81069
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -80738,13 +81086,13 @@ function writePremiumRecoveryFile(agentDir, premiumModel, chats) {
80738
81086
  if (clean.length === 0) {
80739
81087
  throw new Error("refusing to persist premium-recovery marker with no chats to notify");
80740
81088
  }
80741
- atomicWrite(join35(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
81089
+ atomicWrite(join36(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
80742
81090
  `);
80743
81091
  }
80744
81092
  function readPremiumRecoveryFile(agentDir) {
80745
81093
  let raw;
80746
81094
  try {
80747
- raw = readFileSync29(join35(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
81095
+ raw = readFileSync30(join36(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
80748
81096
  } catch {
80749
81097
  return null;
80750
81098
  }
@@ -80757,7 +81105,7 @@ function readPremiumRecoveryFile(agentDir) {
80757
81105
  }
80758
81106
  function clearPremiumRecoveryFile(agentDir) {
80759
81107
  try {
80760
- rmSync4(join35(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
81108
+ rmSync4(join36(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
80761
81109
  } catch {}
80762
81110
  }
80763
81111
 
@@ -81046,7 +81394,7 @@ async function discoverModels(agentName3, opts = {}) {
81046
81394
  }
81047
81395
 
81048
81396
  // ../src/agents/scaffold.ts
81049
- import { dirname as dirname14, isAbsolute, join as join38, relative, resolve as resolve8 } from "node:path";
81397
+ import { dirname as dirname14, isAbsolute, join as join39, relative, resolve as resolve8 } from "node:path";
81050
81398
  init_atomic();
81051
81399
 
81052
81400
  // ../src/agents/agent-uid.ts
@@ -81063,8 +81411,8 @@ init_merge();
81063
81411
  init_timezone();
81064
81412
 
81065
81413
  // ../src/cli/agent-config.ts
81066
- import { join as join36 } from "node:path";
81067
- import { homedir as homedir11 } from "node:os";
81414
+ import { join as join37 } from "node:path";
81415
+ import { homedir as homedir12 } from "node:os";
81068
81416
 
81069
81417
  // ../src/cli/helpers.ts
81070
81418
  init_loader();
@@ -81084,12 +81432,12 @@ var WEBKITE_VAULT_KEYS = new Set([
81084
81432
  init_overlay_loader();
81085
81433
 
81086
81434
  // ../src/cli/agent-config.ts
81087
- var AUDIT_ROOT = join36(homedir11(), ".switchroom", "audit");
81435
+ var AUDIT_ROOT = join37(homedir12(), ".switchroom", "audit");
81088
81436
 
81089
81437
  // ../src/agents/profiles.ts
81090
81438
  var import_handlebars = __toESM(require_lib(), 1);
81091
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync27, existsSync as existsSync31, readdirSync as readdirSync6, statSync as statSync11, copyFileSync, mkdirSync as mkdirSync29, realpathSync as realpathSync2 } from "node:fs";
81092
- import { resolve as resolve7, join as join37, sep as pathSep } from "node:path";
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";
81093
81441
  function resolveProfilesRoot() {
81094
81442
  const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
81095
81443
  if (envOverride) {
@@ -81100,7 +81448,7 @@ function resolveProfilesRoot() {
81100
81448
  resolve7(import.meta.dirname, "profiles")
81101
81449
  ];
81102
81450
  for (const candidate of candidates) {
81103
- if (existsSync31(candidate)) {
81451
+ if (existsSync32(candidate)) {
81104
81452
  return candidate;
81105
81453
  }
81106
81454
  }
@@ -81116,9 +81464,9 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
81116
81464
  var SHARED_FRAGMENTS_DIR = resolve7(PROFILES_ROOT, "_shared");
81117
81465
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
81118
81466
  for (const name of SHARED_FRAGMENTS) {
81119
- const fragPath = join37(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
81120
- if (existsSync31(fragPath)) {
81121
- import_handlebars.default.registerPartial(name, readFileSync30(fragPath, "utf-8"));
81467
+ const fragPath = join38(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
81468
+ if (existsSync32(fragPath)) {
81469
+ import_handlebars.default.registerPartial(name, readFileSync31(fragPath, "utf-8"));
81122
81470
  }
81123
81471
  }
81124
81472
 
@@ -81725,8 +82073,8 @@ init_overlay_loader();
81725
82073
  init_merge();
81726
82074
  init_timezone();
81727
82075
  var import_yaml4 = __toESM(require_dist(), 1);
81728
- import { readFileSync as readFileSync31, existsSync as existsSync32 } from "node:fs";
81729
- import { homedir as homedir12 } from "node:os";
82076
+ import { readFileSync as readFileSync32, existsSync as existsSync33 } from "node:fs";
82077
+ import { homedir as homedir13 } from "node:os";
81730
82078
  import { resolve as resolve9 } from "node:path";
81731
82079
 
81732
82080
  class ConfigError2 extends Error {
@@ -81783,7 +82131,7 @@ function coerceLegacyGoogleWorkspaceKeys2(parsed, filePath) {
81783
82131
  }
81784
82132
  function findConfigFile2(startDir) {
81785
82133
  const envPath = process.env.SWITCHROOM_CONFIG;
81786
- const home2 = homedir12();
82134
+ const home2 = homedir13();
81787
82135
  const userDir = resolve9(home2, ".switchroom");
81788
82136
  const searchPaths = [
81789
82137
  envPath ? resolve9(envPath) : null,
@@ -81801,7 +82149,7 @@ function findConfigFile2(startDir) {
81801
82149
  resolve9(userDir, "clerk.yml")
81802
82150
  ].filter(Boolean);
81803
82151
  for (const path2 of searchPaths) {
81804
- if (existsSync32(path2)) {
82152
+ if (existsSync33(path2)) {
81805
82153
  return path2;
81806
82154
  }
81807
82155
  }
@@ -81809,12 +82157,12 @@ function findConfigFile2(startDir) {
81809
82157
  }
81810
82158
  function loadConfig2(configPath) {
81811
82159
  const filePath = configPath ?? findConfigFile2();
81812
- if (!existsSync32(filePath)) {
82160
+ if (!existsSync33(filePath)) {
81813
82161
  throw new ConfigError2(`Config file not found: ${filePath}`);
81814
82162
  }
81815
82163
  let raw;
81816
82164
  try {
81817
- raw = readFileSync31(filePath, "utf-8");
82165
+ raw = readFileSync32(filePath, "utf-8");
81818
82166
  } catch (err) {
81819
82167
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
81820
82168
  ` ${err.message}`
@@ -82344,15 +82692,15 @@ function topicForRecipient(args) {
82344
82692
  }
82345
82693
 
82346
82694
  // ../src/agents/perf.ts
82347
- import { existsSync as existsSync33, readFileSync as readFileSync32 } from "node:fs";
82695
+ import { existsSync as existsSync34, readFileSync as readFileSync33 } from "node:fs";
82348
82696
  function readTurnUsages(jsonlPath, lastN) {
82349
- if (!existsSync33(jsonlPath))
82697
+ if (!existsSync34(jsonlPath))
82350
82698
  return [];
82351
82699
  if (lastN <= 0)
82352
82700
  return [];
82353
82701
  let raw;
82354
82702
  try {
82355
- raw = readFileSync32(jsonlPath, "utf-8");
82703
+ raw = readFileSync33(jsonlPath, "utf-8");
82356
82704
  } catch {
82357
82705
  return [];
82358
82706
  }
@@ -82443,8 +82791,8 @@ function numField(obj, key) {
82443
82791
  }
82444
82792
 
82445
82793
  // gateway/context-occupancy.ts
82446
- import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync28 } from "node:fs";
82447
- import { join as join39 } from "node:path";
82794
+ import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync29 } from "node:fs";
82795
+ import { join as join40 } from "node:path";
82448
82796
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
82449
82797
  var TIGHT_FRACTION = 0.8;
82450
82798
  function buildContextOccupancy(occupancy, cap, now) {
@@ -82467,9 +82815,9 @@ function buildContextOccupancy(occupancy, cap, now) {
82467
82815
  }
82468
82816
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
82469
82817
  try {
82470
- const path2 = join39(stateDir, CONTEXT_OCCUPANCY_FILENAME);
82471
- (deps?.mkdir ?? ((p, o) => mkdirSync30(p, o)))(stateDir, { recursive: true });
82472
- (deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
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) + `
82473
82821
  `);
82474
82822
  } catch {}
82475
82823
  }
@@ -82623,7 +82971,7 @@ function nextCompactNotify(state6, ev) {
82623
82971
  }
82624
82972
 
82625
82973
  // gateway/hostd-dispatch.ts
82626
- import { existsSync as existsSync34 } from "node:fs";
82974
+ import { existsSync as existsSync35 } from "node:fs";
82627
82975
  import { randomBytes as randomBytes7 } from "node:crypto";
82628
82976
  init_loader();
82629
82977
  var _hostdEnabled2;
@@ -82647,13 +82995,13 @@ function hostdSocketPath2(agentName3) {
82647
82995
  function hostdWillBeUsed2(agentName3) {
82648
82996
  if (!isHostdEnabled2())
82649
82997
  return false;
82650
- return existsSync34(hostdSocketPath2(agentName3));
82998
+ return existsSync35(hostdSocketPath2(agentName3));
82651
82999
  }
82652
83000
  async function tryHostdDispatch2(agentName3, req, timeoutMs = 5000) {
82653
83001
  if (!isHostdEnabled2())
82654
83002
  return "not-configured";
82655
83003
  const sockPath = hostdSocketPath2(agentName3);
82656
- if (!existsSync34(sockPath))
83004
+ if (!existsSync35(sockPath))
82657
83005
  return "not-configured";
82658
83006
  try {
82659
83007
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -82682,7 +83030,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
82682
83030
  if (!isHostdEnabled2())
82683
83031
  return "not-configured";
82684
83032
  const sockPath = hostdSocketPath2(agentName3);
82685
- if (!existsSync34(sockPath))
83033
+ if (!existsSync35(sockPath))
82686
83034
  return "not-configured";
82687
83035
  const now = opts.now ?? Date.now;
82688
83036
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -82801,7 +83149,7 @@ function createDmPinSweeper(deps) {
82801
83149
 
82802
83150
  // gateway/webhook-ingest-server.ts
82803
83151
  import net4 from "node:net";
82804
- import { chmodSync as chmodSync10, existsSync as existsSync35, unlinkSync as unlinkSync16 } from "node:fs";
83152
+ import { chmodSync as chmodSync10, existsSync as existsSync36, unlinkSync as unlinkSync17 } from "node:fs";
82805
83153
  var MAX_REQUEST_BYTES = 1024 * 1024;
82806
83154
  function fdOf(conn) {
82807
83155
  const handle = conn._handle;
@@ -82813,8 +83161,8 @@ function startWebhookIngestServer(opts) {
82813
83161
  const log = opts.log ?? ((s) => process.stderr.write(s));
82814
83162
  const allowed = new Set(opts.allowedUids);
82815
83163
  try {
82816
- if (existsSync35(opts.socketPath))
82817
- unlinkSync16(opts.socketPath);
83164
+ if (existsSync36(opts.socketPath))
83165
+ unlinkSync17(opts.socketPath);
82818
83166
  } catch (err) {
82819
83167
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
82820
83168
  `);
@@ -82906,28 +83254,28 @@ function startWebhookIngestServer(opts) {
82906
83254
  server.close();
82907
83255
  } catch {}
82908
83256
  try {
82909
- if (existsSync35(opts.socketPath))
82910
- unlinkSync16(opts.socketPath);
83257
+ if (existsSync36(opts.socketPath))
83258
+ unlinkSync17(opts.socketPath);
82911
83259
  } catch {}
82912
83260
  }
82913
83261
  };
82914
83262
  }
82915
83263
 
82916
83264
  // ../src/web/webhook-gateway-record.ts
82917
- import { appendFileSync as appendFileSync6, mkdirSync as mkdirSync33 } from "fs";
82918
- import { join as join42 } from "path";
82919
- import { homedir as homedir14 } from "os";
83265
+ import { appendFileSync as appendFileSync7, mkdirSync as mkdirSync34 } from "fs";
83266
+ import { join as join43 } from "path";
83267
+ import { homedir as homedir15 } from "os";
82920
83268
 
82921
83269
  // ../src/web/webhook-handler.ts
82922
- import { appendFileSync as appendFileSync5, existsSync as existsSync36, mkdirSync as mkdirSync31, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
82923
- import { join as join40 } from "path";
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";
82924
83272
  var DEDUP_MAX = 1000;
82925
83273
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
82926
83274
  function loadDedupFile(path2) {
82927
83275
  try {
82928
- if (!existsSync36(path2))
83276
+ if (!existsSync37(path2))
82929
83277
  return {};
82930
- const raw = JSON.parse(readFileSync33(path2, "utf-8"));
83278
+ const raw = JSON.parse(readFileSync34(path2, "utf-8"));
82931
83279
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
82932
83280
  } catch {
82933
83281
  return {};
@@ -82941,7 +83289,7 @@ function saveDedupFile(path2, deliveries, now) {
82941
83289
  }
82942
83290
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
82943
83291
  const final = Object.fromEntries(sorted);
82944
- writeFileSync29(path2, JSON.stringify({ deliveries: final }), {
83292
+ writeFileSync30(path2, JSON.stringify({ deliveries: final }), {
82945
83293
  mode: 384
82946
83294
  });
82947
83295
  }
@@ -82949,8 +83297,8 @@ var agentDedupCache = new Map;
82949
83297
  function createFileDedupStore(resolveAgentDir) {
82950
83298
  return {
82951
83299
  check(agent, deliveryId, now) {
82952
- const telegramDir = join40(resolveAgentDir(agent), "telegram");
82953
- const filePath = join40(telegramDir, "webhook-dedup.json");
83300
+ const telegramDir = join41(resolveAgentDir(agent), "telegram");
83301
+ const filePath = join41(telegramDir, "webhook-dedup.json");
82954
83302
  if (!agentDedupCache.has(agent)) {
82955
83303
  agentDedupCache.set(agent, loadDedupFile(filePath));
82956
83304
  }
@@ -82960,7 +83308,7 @@ function createFileDedupStore(resolveAgentDir) {
82960
83308
  }
82961
83309
  deliveries[deliveryId] = now;
82962
83310
  try {
82963
- mkdirSync31(telegramDir, { recursive: true });
83311
+ mkdirSync32(telegramDir, { recursive: true });
82964
83312
  saveDedupFile(filePath, deliveries, now);
82965
83313
  } catch {}
82966
83314
  return;
@@ -82971,9 +83319,9 @@ var tokenBuckets = new Map;
82971
83319
  var throttleIssueWindow = new Map;
82972
83320
 
82973
83321
  // ../src/web/webhook-dispatch.ts
82974
- import { existsSync as existsSync37, mkdirSync as mkdirSync32, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "fs";
82975
- import { join as join41 } from "path";
82976
- import { homedir as homedir13 } from "os";
83322
+ import { existsSync as existsSync38, mkdirSync as mkdirSync33, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
83323
+ import { join as join42 } from "path";
83324
+ import { homedir as homedir14 } from "os";
82977
83325
 
82978
83326
  // ../src/agent-scheduler/ipc-client.ts
82979
83327
  import { createConnection as createConnection2 } from "node:net";
@@ -83271,9 +83619,9 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
83271
83619
  }
83272
83620
  function loadCooldownFile(path2) {
83273
83621
  try {
83274
- if (!existsSync37(path2))
83622
+ if (!existsSync38(path2))
83275
83623
  return {};
83276
- const raw = JSON.parse(readFileSync34(path2, "utf-8"));
83624
+ const raw = JSON.parse(readFileSync35(path2, "utf-8"));
83277
83625
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
83278
83626
  } catch {
83279
83627
  return {};
@@ -83281,7 +83629,7 @@ function loadCooldownFile(path2) {
83281
83629
  }
83282
83630
  function saveCooldownFile(path2, dispatches) {
83283
83631
  try {
83284
- writeFileSync30(path2, JSON.stringify({ dispatches }), {
83632
+ writeFileSync31(path2, JSON.stringify({ dispatches }), {
83285
83633
  mode: 384
83286
83634
  });
83287
83635
  } catch {}
@@ -83292,8 +83640,8 @@ function createFileCooldownStore(resolveAgentDir) {
83292
83640
  isCoolingDown(agent, key, cooldownMs, now) {
83293
83641
  if (cooldownMs <= 0)
83294
83642
  return false;
83295
- const telegramDir = join41(resolveAgentDir(agent), "telegram");
83296
- const filePath = join41(telegramDir, "webhook-cooldown.json");
83643
+ const telegramDir = join42(resolveAgentDir(agent), "telegram");
83644
+ const filePath = join42(telegramDir, "webhook-cooldown.json");
83297
83645
  if (!cache.has(agent)) {
83298
83646
  cache.set(agent, loadCooldownFile(filePath));
83299
83647
  }
@@ -83304,7 +83652,7 @@ function createFileCooldownStore(resolveAgentDir) {
83304
83652
  }
83305
83653
  dispatches[key] = now;
83306
83654
  try {
83307
- mkdirSync32(telegramDir, { recursive: true });
83655
+ mkdirSync33(telegramDir, { recursive: true });
83308
83656
  saveCooldownFile(filePath, dispatches);
83309
83657
  } catch {}
83310
83658
  return false;
@@ -83350,9 +83698,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
83350
83698
  }
83351
83699
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
83352
83700
  const log = deps.log ?? ((s) => process.stderr.write(s));
83353
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join41(homedir13(), ".switchroom", "agents", a));
83701
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir14(), ".switchroom", "agents", a));
83354
83702
  const now = (deps.now ?? Date.now)();
83355
- const socketPath = join41(resolveAgentDir(agent), "telegram", "gateway.sock");
83703
+ const socketPath = join42(resolveAgentDir(agent), "telegram", "gateway.sock");
83356
83704
  const inbound = {
83357
83705
  type: "inbound",
83358
83706
  chatId: ctx.chatId,
@@ -83424,7 +83772,7 @@ function evaluateDispatch(args, deps = {}) {
83424
83772
  const log = deps.log ?? ((s) => process.stderr.write(s));
83425
83773
  const now = (deps.now ?? Date.now)();
83426
83774
  const nowDate = deps.nowDate ?? (() => new Date(now));
83427
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join41(homedir13(), ".switchroom", "agents", a));
83775
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir14(), ".switchroom", "agents", a));
83428
83776
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
83429
83777
  if (!DISPATCH_SOURCES.includes(args.source))
83430
83778
  return 0;
@@ -83502,10 +83850,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
83502
83850
  function recordWebhookEvent(rec, deps = {}) {
83503
83851
  const log = deps.log ?? ((s) => process.stderr.write(s));
83504
83852
  const now = rec.ts || (deps.now ?? Date.now)();
83505
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir14(), ".switchroom", "agents", a));
83853
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir15(), ".switchroom", "agents", a));
83506
83854
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
83507
83855
  const agent = rec.agent;
83508
- const telegramDir = join42(resolveAgentDir(agent), "telegram");
83856
+ const telegramDir = join43(resolveAgentDir(agent), "telegram");
83509
83857
  if (rec.source === "github" && rec.delivery_id) {
83510
83858
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
83511
83859
  if (originalTs !== undefined) {
@@ -83514,9 +83862,9 @@ function recordWebhookEvent(rec, deps = {}) {
83514
83862
  return { status: "deduped", ts: originalTs };
83515
83863
  }
83516
83864
  }
83517
- const logPath = join42(telegramDir, "webhook-events.jsonl");
83865
+ const logPath = join43(telegramDir, "webhook-events.jsonl");
83518
83866
  try {
83519
- mkdirSync33(telegramDir, { recursive: true });
83867
+ mkdirSync34(telegramDir, { recursive: true });
83520
83868
  const record = {
83521
83869
  ts: now,
83522
83870
  source: rec.source,
@@ -83524,7 +83872,7 @@ function recordWebhookEvent(rec, deps = {}) {
83524
83872
  rendered_text: rec.rendered_text,
83525
83873
  payload: rec.payload
83526
83874
  };
83527
- appendFileSync6(logPath, JSON.stringify(record) + `
83875
+ appendFileSync7(logPath, JSON.stringify(record) + `
83528
83876
  `, { mode: 384 });
83529
83877
  } catch (err) {
83530
83878
  log(`webhook-gateway: agent='${agent}' source='${rec.source}' write failed: ${err.message}
@@ -83626,7 +83974,7 @@ function recordWebhookEvent(rec, deps = {}) {
83626
83974
 
83627
83975
  // gateway/ipc-server.ts
83628
83976
  init_format();
83629
- import { renameSync as renameSync12, unlinkSync as unlinkSync17, chmodSync as chmodSync11 } from "fs";
83977
+ import { renameSync as renameSync13, unlinkSync as unlinkSync18, chmodSync as chmodSync11 } from "fs";
83630
83978
  var MAX_BUFFER_SIZE = 1024 * 1024;
83631
83979
  var VALID_OPERATOR_KINDS = new Set([
83632
83980
  "credentials-expired",
@@ -83835,10 +84183,10 @@ function createIpcServer(options) {
83835
84183
  heartbeatTimeoutMs = 30000
83836
84184
  } = options;
83837
84185
  try {
83838
- renameSync12(socketPath, socketPath + ".bak");
84186
+ renameSync13(socketPath, socketPath + ".bak");
83839
84187
  } catch {}
83840
84188
  try {
83841
- unlinkSync17(socketPath + ".bak");
84189
+ unlinkSync18(socketPath + ".bak");
83842
84190
  } catch {}
83843
84191
  const clients = new Set;
83844
84192
  const agentIndex = new Map;
@@ -84225,7 +84573,7 @@ function createIpcServer(options) {
84225
84573
  clientBySocketId.clear();
84226
84574
  server.stop(true);
84227
84575
  try {
84228
- renameSync12(socketPath, socketPath + ".bak");
84576
+ renameSync13(socketPath, socketPath + ".bak");
84229
84577
  } catch {}
84230
84578
  }
84231
84579
  };
@@ -84679,7 +85027,7 @@ async function handleRequestMs365Approval(client3, msg, deps) {
84679
85027
 
84680
85028
  // gateway/diff-preview-card.ts
84681
85029
  init_format();
84682
- var import_grammy12 = __toESM(require_mod2(), 1);
85030
+ var import_grammy13 = __toESM(require_mod2(), 1);
84683
85031
  var REQUEST_ID_RE = /^[0-9a-f]{32}$/;
84684
85032
  var PENDING_FILE_ID_SENTINEL = "pending-create";
84685
85033
  function buildDiffPreviewCard(input) {
@@ -84697,7 +85045,7 @@ function buildDiffPreviewCard(input) {
84697
85045
  }
84698
85046
  const text4 = bodyLines.join(`
84699
85047
  `);
84700
- const kb = new import_grammy12.InlineKeyboard;
85048
+ const kb = new import_grammy13.InlineKeyboard;
84701
85049
  const ROW_BREAK_AFTER = [
84702
85050
  "apply_suggestion"
84703
85051
  ];
@@ -88160,27 +88508,27 @@ function skillProposalKeyboard(id) {
88160
88508
  // ../src/self-improve/skill-proposals.ts
88161
88509
  import {
88162
88510
  closeSync as closeSync7,
88163
- existsSync as existsSync38,
88164
- mkdirSync as mkdirSync34,
88511
+ existsSync as existsSync39,
88512
+ mkdirSync as mkdirSync35,
88165
88513
  openSync as openSync7,
88166
- readFileSync as readFileSync35,
88514
+ readFileSync as readFileSync36,
88167
88515
  writeSync as writeSync5
88168
88516
  } from "node:fs";
88169
- import { join as join43 } from "node:path";
88517
+ import { join as join44 } from "node:path";
88170
88518
  import { randomUUID as randomUUID5 } from "node:crypto";
88171
88519
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
88172
88520
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
88173
88521
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
88174
88522
  var PROPOSAL_SIM_THRESHOLD = 0.5;
88175
88523
  function proposalsPath2(stateDir) {
88176
- return join43(stateDir, PROPOSALS_FILE2);
88524
+ return join44(stateDir, PROPOSALS_FILE2);
88177
88525
  }
88178
88526
  function rejectedPath2(stateDir) {
88179
- return join43(stateDir, REJECTED_FILE2);
88527
+ return join44(stateDir, REJECTED_FILE2);
88180
88528
  }
88181
88529
  function ensureDir3(stateDir) {
88182
- if (!existsSync38(stateDir)) {
88183
- mkdirSync34(stateDir, { recursive: true, mode: 493 });
88530
+ if (!existsSync39(stateDir)) {
88531
+ mkdirSync35(stateDir, { recursive: true, mode: 493 });
88184
88532
  }
88185
88533
  }
88186
88534
  function appendLine2(path2, obj) {
@@ -88193,11 +88541,11 @@ function appendLine2(path2, obj) {
88193
88541
  }
88194
88542
  }
88195
88543
  function readLines2(path2, isValid2) {
88196
- if (!existsSync38(path2))
88544
+ if (!existsSync39(path2))
88197
88545
  return [];
88198
88546
  let raw;
88199
88547
  try {
88200
- raw = readFileSync35(path2, "utf-8");
88548
+ raw = readFileSync36(path2, "utf-8");
88201
88549
  } catch {
88202
88550
  return [];
88203
88551
  }
@@ -88794,15 +89142,15 @@ function escapeBody2(s) {
88794
89142
  }
88795
89143
 
88796
89144
  // gateway/pid-file.ts
88797
- import { writeFileSync as writeFileSync31, readFileSync as readFileSync36, unlinkSync as unlinkSync18, renameSync as renameSync13 } from "node:fs";
89145
+ import { writeFileSync as writeFileSync32, readFileSync as readFileSync37, unlinkSync as unlinkSync19, renameSync as renameSync14 } from "node:fs";
88798
89146
  function writePidFile(path2, record) {
88799
89147
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
88800
- writeFileSync31(tmp, JSON.stringify(record), "utf-8");
88801
- renameSync13(tmp, path2);
89148
+ writeFileSync32(tmp, JSON.stringify(record), "utf-8");
89149
+ renameSync14(tmp, path2);
88802
89150
  }
88803
89151
  function clearPidFile(path2) {
88804
89152
  try {
88805
- unlinkSync18(path2);
89153
+ unlinkSync19(path2);
88806
89154
  } catch {}
88807
89155
  }
88808
89156
 
@@ -88813,10 +89161,10 @@ import {
88813
89161
  writeFile as writeFileAsync,
88814
89162
  readFile as readFileAsync
88815
89163
  } from "node:fs/promises";
88816
- import { readFileSync as readFileSync37 } from "node:fs";
89164
+ import { readFileSync as readFileSync38 } from "node:fs";
88817
89165
  function readCurrentBootId() {
88818
89166
  try {
88819
- const stat = readFileSync37("/proc/1/stat", "utf-8");
89167
+ const stat = readFileSync38("/proc/1/stat", "utf-8");
88820
89168
  const lastParen = stat.lastIndexOf(")");
88821
89169
  if (lastParen < 0)
88822
89170
  return null;
@@ -88964,14 +89312,14 @@ var DEFAULT_LOG3 = (line) => {
88964
89312
  `) ? line : line + `
88965
89313
  `);
88966
89314
  };
88967
- var DEFAULT_SLEEP3 = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
89315
+ var DEFAULT_SLEEP4 = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
88968
89316
  var DEFAULT_NOW = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
88969
89317
  function fmtAgent2(agentName3) {
88970
89318
  return agentName3 ? ` agent=${agentName3}` : "";
88971
89319
  }
88972
89320
  async function drainShutdown(opts) {
88973
89321
  const log = opts.log ?? DEFAULT_LOG3;
88974
- const sleep2 = opts.sleep ?? DEFAULT_SLEEP3;
89322
+ const sleep2 = opts.sleep ?? DEFAULT_SLEEP4;
88975
89323
  const now = opts.now ?? DEFAULT_NOW;
88976
89324
  const budgetMs = opts.budgetMs ?? 35000;
88977
89325
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -89019,15 +89367,15 @@ function safeCount(fn) {
89019
89367
  }
89020
89368
 
89021
89369
  // gateway/session-marker.ts
89022
- import { writeFileSync as writeFileSync32, readFileSync as readFileSync38, renameSync as renameSync14, unlinkSync as unlinkSync19 } from "node:fs";
89370
+ import { writeFileSync as writeFileSync33, readFileSync as readFileSync39, renameSync as renameSync15, unlinkSync as unlinkSync20 } from "node:fs";
89023
89371
  function writeSessionMarker(path2, marker) {
89024
89372
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89025
- writeFileSync32(tmp, JSON.stringify(marker), "utf-8");
89026
- renameSync14(tmp, path2);
89373
+ writeFileSync33(tmp, JSON.stringify(marker), "utf-8");
89374
+ renameSync15(tmp, path2);
89027
89375
  }
89028
89376
  function readSessionMarker(path2) {
89029
89377
  try {
89030
- const raw = readFileSync38(path2, "utf-8");
89378
+ const raw = readFileSync39(path2, "utf-8");
89031
89379
  const parsed = JSON.parse(raw);
89032
89380
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
89033
89381
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -89049,16 +89397,16 @@ function shouldFireRestartBanner(input) {
89049
89397
  }
89050
89398
 
89051
89399
  // gateway/clean-shutdown-marker.ts
89052
- import { writeFileSync as writeFileSync33, readFileSync as readFileSync39, renameSync as renameSync15, unlinkSync as unlinkSync20 } from "node:fs";
89400
+ import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync16, unlinkSync as unlinkSync21 } from "node:fs";
89053
89401
  var DEFAULT_MAX_AGE_MS = 60000;
89054
89402
  function writeCleanShutdownMarker(path2, marker) {
89055
89403
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89056
- writeFileSync33(tmp, JSON.stringify(marker), "utf-8");
89057
- renameSync15(tmp, path2);
89404
+ writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
89405
+ renameSync16(tmp, path2);
89058
89406
  }
89059
89407
  function readCleanShutdownMarker(path2) {
89060
89408
  try {
89061
- const raw = readFileSync39(path2, "utf-8");
89409
+ const raw = readFileSync40(path2, "utf-8");
89062
89410
  const parsed = JSON.parse(raw);
89063
89411
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
89064
89412
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -89073,7 +89421,7 @@ function readCleanShutdownMarker(path2) {
89073
89421
  }
89074
89422
  function clearCleanShutdownMarker(path2) {
89075
89423
  try {
89076
- unlinkSync20(path2);
89424
+ unlinkSync21(path2);
89077
89425
  } catch {}
89078
89426
  }
89079
89427
  function shouldSuppressRecoveryBanner(marker, now, maxAgeMs = DEFAULT_MAX_AGE_MS) {
@@ -89430,16 +89778,16 @@ function classifyAdminGate(text4, myAgentName) {
89430
89778
 
89431
89779
  // subagent-watcher.ts
89432
89780
  import {
89433
- existsSync as existsSync39,
89781
+ existsSync as existsSync40,
89434
89782
  openSync as openSync8,
89435
89783
  readSync as readSync2,
89436
- statSync as statSync12,
89784
+ statSync as statSync13,
89437
89785
  closeSync as closeSync8,
89438
89786
  watch,
89439
- readdirSync as readdirSync7,
89440
- readFileSync as readFileSync40
89787
+ readdirSync as readdirSync8,
89788
+ readFileSync as readFileSync41
89441
89789
  } from "fs";
89442
- import { join as join44 } from "path";
89790
+ import { join as join45 } from "path";
89443
89791
 
89444
89792
  // session-tail.ts
89445
89793
  function sanitizeCwdToProjectName2(cwd) {
@@ -89753,104 +90101,6 @@ function redactSecrets(text4) {
89753
90101
 
89754
90102
  // subagent-watcher.ts
89755
90103
  init_card_format();
89756
-
89757
- // registry/subagents-schema.ts
89758
- function countRunningBackgroundSubagents(db3) {
89759
- const row = db3.prepare("SELECT count(*) AS n FROM subagents WHERE background = 1 AND status = 'running'").get();
89760
- return row?.n ?? 0;
89761
- }
89762
- function recordSubagentEnd(db3, args) {
89763
- db3.prepare(`
89764
- UPDATE subagents
89765
- SET
89766
- ended_at = ?,
89767
- status = ?,
89768
- result_summary = COALESCE(?, result_summary)
89769
- WHERE id = ?
89770
- AND status NOT IN ('completed', 'failed')
89771
- `).run(args.endedAt, args.status, args.resultSummary ?? null, args.id);
89772
- }
89773
- function recordSubagentStall(db3, args) {
89774
- args.stalledAt;
89775
- if (args.reason !== undefined) {
89776
- db3.prepare(`
89777
- UPDATE subagents
89778
- SET status = 'stalled',
89779
- result_summary = ?
89780
- WHERE id = ?
89781
- AND status NOT IN ('completed', 'failed')
89782
- `).run(args.reason, args.id);
89783
- return;
89784
- }
89785
- db3.prepare(`
89786
- UPDATE subagents
89787
- SET status = 'stalled'
89788
- WHERE id = ?
89789
- AND status NOT IN ('completed', 'failed')
89790
- `).run(args.id);
89791
- }
89792
- function reapStuckRunningRows(db3, args) {
89793
- const cutoff = args.now - args.ttlMs;
89794
- const rows = db3.prepare(`
89795
- SELECT id, jsonl_agent_id FROM subagents
89796
- WHERE status = 'running'
89797
- AND background = 1
89798
- AND COALESCE(last_activity_at, started_at) < ?
89799
- `).all(cutoff);
89800
- const candidates = args.isLive ? rows.filter((r) => !args.isLive(r.jsonl_agent_id)) : rows;
89801
- for (const row of candidates) {
89802
- recordSubagentStall(db3, {
89803
- id: row.id,
89804
- stalledAt: args.now,
89805
- reason: `reaped: stuck in running past ${Math.round(args.ttlMs / 60000)}min ttl (jsonl linkage likely missing)`
89806
- });
89807
- }
89808
- return { reaped: candidates.length, ids: candidates.map((r) => r.id) };
89809
- }
89810
- function recordSubagentResume(db3, args) {
89811
- args.resumedAt;
89812
- db3.prepare(`
89813
- UPDATE subagents
89814
- SET status = 'running'
89815
- WHERE id = ?
89816
- AND status = 'stalled'
89817
- `).run(args.id);
89818
- }
89819
- function bumpSubagentActivity(db3, args) {
89820
- db3.prepare(`
89821
- UPDATE subagents
89822
- SET last_activity_at = ?
89823
- WHERE id = ?
89824
- `).run(args.ts, args.id);
89825
- }
89826
- function recordSubagentModel(db3, args) {
89827
- db3.prepare(`
89828
- UPDATE subagents
89829
- SET model = ?
89830
- WHERE id = ?
89831
- `).run(args.model, args.id);
89832
- }
89833
- function recordNestedSubagentDispatch(db3, args) {
89834
- db3.prepare(`
89835
- INSERT OR IGNORE INTO subagents
89836
- (id, parent_session_id, parent_turn_key, agent_type, description,
89837
- background, started_at, last_activity_at, status, jsonl_agent_id,
89838
- parent_agent_id)
89839
- VALUES (?, NULL, NULL, ?, ?, ?, ?, ?, 'running', NULL, ?)
89840
- `).run(args.toolUseId, args.agentType ?? null, args.description ?? null, args.background ? 1 : 0, args.now, args.now, args.parentJsonlAgentId);
89841
- db3.prepare(`
89842
- UPDATE subagents
89843
- SET parent_agent_id = COALESCE(parent_agent_id, ?),
89844
- parent_turn_key = COALESCE(
89845
- parent_turn_key,
89846
- (SELECT p.parent_turn_key FROM subagents p
89847
- WHERE p.jsonl_agent_id = ? LIMIT 1)
89848
- )
89849
- WHERE id = ?
89850
- `).run(args.parentJsonlAgentId, args.parentJsonlAgentId, args.toolUseId);
89851
- }
89852
-
89853
- // subagent-watcher.ts
89854
90104
  var DEFAULT_RESCAN_MS = 1000;
89855
90105
  var DEFAULT_STALL_THRESHOLD_MS = 60000;
89856
90106
  var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
@@ -89887,7 +90137,7 @@ function backfillJsonlAgentId(db3, jsonlPath, agentId, log) {
89887
90137
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
89888
90138
  let meta;
89889
90139
  try {
89890
- const raw = readFileSync40(metaPath, "utf8");
90140
+ const raw = readFileSync41(metaPath, "utf8");
89891
90141
  meta = JSON.parse(raw);
89892
90142
  } catch {
89893
90143
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -90342,9 +90592,9 @@ function startSubagentWatcher(config) {
90342
90592
  clearTimeout(ref.ref);
90343
90593
  });
90344
90594
  const fs2 = config.fs ?? {
90345
- existsSync: existsSync39,
90346
- readdirSync: readdirSync7,
90347
- statSync: statSync12,
90595
+ existsSync: existsSync40,
90596
+ readdirSync: readdirSync8,
90597
+ statSync: statSync13,
90348
90598
  openSync: openSync8,
90349
90599
  closeSync: closeSync8,
90350
90600
  readSync: readSync2,
@@ -90860,8 +91110,8 @@ function startSubagentWatcher(config) {
90860
91110
  if (stopped)
90861
91111
  return;
90862
91112
  pruneVanishedDirWatchers();
90863
- const claudeHome = join44(agentDir, ".claude");
90864
- const projectsRoot = join44(claudeHome, "projects");
91113
+ const claudeHome = join45(agentDir, ".claude");
91114
+ const projectsRoot = join45(claudeHome, "projects");
90865
91115
  if (!fs2.existsSync(projectsRoot))
90866
91116
  return;
90867
91117
  let projectDirs;
@@ -90895,7 +91145,7 @@ function startSubagentWatcher(config) {
90895
91145
  continue;
90896
91146
  }
90897
91147
  warnedForeignSlugs.delete(pDir);
90898
- const projectPath = join44(projectsRoot, pDir);
91148
+ const projectPath = join45(projectsRoot, pDir);
90899
91149
  let sessionDirs;
90900
91150
  try {
90901
91151
  sessionDirs = fs2.readdirSync(projectPath);
@@ -90905,7 +91155,7 @@ function startSubagentWatcher(config) {
90905
91155
  for (const sDir of sessionDirs) {
90906
91156
  if (sDir.endsWith(".jsonl"))
90907
91157
  continue;
90908
- const subagentsPath = join44(projectPath, sDir, "subagents");
91158
+ const subagentsPath = join45(projectPath, sDir, "subagents");
90909
91159
  if (!fs2.existsSync(subagentsPath))
90910
91160
  continue;
90911
91161
  const watchAndScan = (dirPath) => {
@@ -90914,7 +91164,7 @@ function startSubagentWatcher(config) {
90914
91164
  const w = fs2.watch(dirPath, (_event, filename) => {
90915
91165
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
90916
91166
  return;
90917
- const filePath = join44(dirPath, filename.toString());
91167
+ const filePath = join45(dirPath, filename.toString());
90918
91168
  if (!knownFiles.has(filePath)) {
90919
91169
  scanSubagentsDir(dirPath);
90920
91170
  }
@@ -90928,7 +91178,7 @@ function startSubagentWatcher(config) {
90928
91178
  scanSubagentsDir(dirPath);
90929
91179
  };
90930
91180
  watchAndScan(subagentsPath);
90931
- const workflowsPath = join44(subagentsPath, "workflows");
91181
+ const workflowsPath = join45(subagentsPath, "workflows");
90932
91182
  if (fs2.existsSync(workflowsPath)) {
90933
91183
  let wfDirs;
90934
91184
  try {
@@ -90938,7 +91188,7 @@ function startSubagentWatcher(config) {
90938
91188
  }
90939
91189
  for (const wfDir of wfDirs) {
90940
91190
  try {
90941
- const wfPath = join44(workflowsPath, wfDir);
91191
+ const wfPath = join45(workflowsPath, wfDir);
90942
91192
  if (!fs2.statSync(wfPath).isDirectory())
90943
91193
  continue;
90944
91194
  watchAndScan(wfPath);
@@ -90958,7 +91208,7 @@ function startSubagentWatcher(config) {
90958
91208
  for (const e of entries) {
90959
91209
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
90960
91210
  continue;
90961
- const filePath = join44(subagentsPath, e);
91211
+ const filePath = join45(subagentsPath, e);
90962
91212
  if (knownFiles.has(filePath))
90963
91213
  continue;
90964
91214
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -91086,37 +91336,37 @@ function startSubagentWatcher(config) {
91086
91336
 
91087
91337
  // ../src/worktree/registry.ts
91088
91338
  import {
91089
- mkdirSync as mkdirSync35,
91090
- writeFileSync as writeFileSync34,
91091
- readFileSync as readFileSync41,
91092
- readdirSync as readdirSync8,
91093
- unlinkSync as unlinkSync21,
91094
- existsSync as existsSync40,
91095
- renameSync as renameSync16
91339
+ mkdirSync as mkdirSync36,
91340
+ writeFileSync as writeFileSync35,
91341
+ readFileSync as readFileSync42,
91342
+ readdirSync as readdirSync9,
91343
+ unlinkSync as unlinkSync22,
91344
+ existsSync as existsSync41,
91345
+ renameSync as renameSync17
91096
91346
  } from "node:fs";
91097
- import { join as join45, resolve as resolve10 } from "node:path";
91098
- import { homedir as homedir15 } from "node:os";
91347
+ import { join as join46, resolve as resolve10 } from "node:path";
91348
+ import { homedir as homedir16 } from "node:os";
91099
91349
  function registryDir() {
91100
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join45(homedir15(), ".switchroom", "worktrees"));
91350
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join46(homedir16(), ".switchroom", "worktrees"));
91101
91351
  }
91102
91352
  function recordPath(id) {
91103
- return join45(registryDir(), `${id}.json`);
91353
+ return join46(registryDir(), `${id}.json`);
91104
91354
  }
91105
91355
  function ensureDir4() {
91106
- mkdirSync35(registryDir(), { recursive: true });
91356
+ mkdirSync36(registryDir(), { recursive: true });
91107
91357
  }
91108
91358
  function writeRecord(record) {
91109
91359
  ensureDir4();
91110
91360
  const target = recordPath(record.id);
91111
91361
  const tmp = `${target}.tmp${process.pid}`;
91112
- writeFileSync34(tmp, JSON.stringify(record, null, 2) + `
91362
+ writeFileSync35(tmp, JSON.stringify(record, null, 2) + `
91113
91363
  `, { mode: 384 });
91114
- renameSync16(tmp, target);
91364
+ renameSync17(tmp, target);
91115
91365
  }
91116
91366
  function readRecord(id) {
91117
91367
  const path2 = recordPath(id);
91118
91368
  try {
91119
- const raw = readFileSync41(path2, "utf8");
91369
+ const raw = readFileSync42(path2, "utf8");
91120
91370
  return JSON.parse(raw);
91121
91371
  } catch {
91122
91372
  return null;
@@ -91126,7 +91376,7 @@ function listRecords() {
91126
91376
  ensureDir4();
91127
91377
  const dir = registryDir();
91128
91378
  const records = [];
91129
- for (const entry of readdirSync8(dir)) {
91379
+ for (const entry of readdirSync9(dir)) {
91130
91380
  if (!entry.endsWith(".json"))
91131
91381
  continue;
91132
91382
  const id = entry.slice(0, -5);
@@ -91146,7 +91396,7 @@ function touchHeartbeat(id, onAfterRead) {
91146
91396
  writeRecord({ ...rec, heartbeatAt: new Date().toISOString() });
91147
91397
  }
91148
91398
  function recordExists(id) {
91149
- return existsSync40(recordPath(id));
91399
+ return existsSync41(recordPath(id));
91150
91400
  }
91151
91401
 
91152
91402
  // worktree-watch-cwds.ts
@@ -91278,15 +91528,15 @@ function determineRestartReason(opts) {
91278
91528
  init_boot_card();
91279
91529
 
91280
91530
  // gateway/update-announce.ts
91281
- import { existsSync as existsSync45, mkdirSync as mkdirSync39, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync48 } from "node:fs";
91282
- import { join as join50 } from "node:path";
91283
- import { homedir as homedir17 } from "node:os";
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";
91533
+ import { homedir as homedir18 } from "node:os";
91284
91534
 
91285
91535
  // ../src/host-control/audit-reader.ts
91286
- import { homedir as homedir16 } from "node:os";
91287
- import { join as join49 } from "node:path";
91288
- function defaultAuditLogPath(home2 = homedir16()) {
91289
- return join49(home2, ".switchroom", "host-control-audit.log");
91536
+ import { homedir as homedir17 } from "node:os";
91537
+ import { join as join50 } from "node:path";
91538
+ function defaultAuditLogPath(home2 = homedir17()) {
91539
+ return join50(home2, ".switchroom", "host-control-audit.log");
91290
91540
  }
91291
91541
  function parseAuditLine(line) {
91292
91542
  const trimmed = line.trim();
@@ -91409,8 +91659,8 @@ function readAndFilter(raw, filters, limit) {
91409
91659
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
91410
91660
  function readLastTerminalUpdateAudit(opts = {}) {
91411
91661
  const path2 = opts.auditLogPath ?? defaultAuditLogPath();
91412
- const exists = opts.exists ?? existsSync45;
91413
- const readFile = opts.readFile ?? ((p) => readFileSync48(p, "utf-8"));
91662
+ const exists = opts.exists ?? existsSync46;
91663
+ const readFile = opts.readFile ?? ((p) => readFileSync49(p, "utf-8"));
91414
91664
  if (!exists(path2))
91415
91665
  return null;
91416
91666
  let raw;
@@ -91471,15 +91721,15 @@ function renderUpdateOutcomeLine(entry) {
91471
91721
  `);
91472
91722
  }
91473
91723
  function claimUpdateAnnouncement(requestId, opts = {}) {
91474
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join50(homedir17(), ".switchroom");
91475
- const dir = join50(stateDir, "update-announced");
91724
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join51(homedir18(), ".switchroom");
91725
+ const dir = join51(stateDir, "update-announced");
91476
91726
  try {
91477
- mkdirSync39(dir, { recursive: true });
91727
+ mkdirSync40(dir, { recursive: true });
91478
91728
  } catch {
91479
91729
  return false;
91480
91730
  }
91481
91731
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
91482
- const path2 = join50(dir, safeId);
91732
+ const path2 = join51(dir, safeId);
91483
91733
  try {
91484
91734
  const fd = openSync9(path2, "wx");
91485
91735
  closeSync9(fd);
@@ -91499,7 +91749,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
91499
91749
 
91500
91750
  // issues-card.ts
91501
91751
  init_card_format();
91502
- import { readFileSync as readFileSync49, writeFileSync as writeFileSync39 } from "node:fs";
91752
+ import { readFileSync as readFileSync50, writeFileSync as writeFileSync40 } from "node:fs";
91503
91753
  var SEVERITY_EMOJI = {
91504
91754
  info: "\u2139\ufe0f",
91505
91755
  warn: "\u26a0\ufe0f",
@@ -91591,7 +91841,7 @@ function extractRetryAfterSecs2(err) {
91591
91841
  var COOLDOWN_JITTER_MS2 = 500;
91592
91842
  function readPersistedMessageId(path2, log) {
91593
91843
  try {
91594
- const raw = readFileSync49(path2, "utf8");
91844
+ const raw = readFileSync50(path2, "utf8");
91595
91845
  const parsed = JSON.parse(raw);
91596
91846
  const v = parsed.messageId;
91597
91847
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -91607,7 +91857,7 @@ function readPersistedMessageId(path2, log) {
91607
91857
  }
91608
91858
  function writePersistedMessageId(path2, messageId, log) {
91609
91859
  try {
91610
- writeFileSync39(path2, JSON.stringify({ messageId }) + `
91860
+ writeFileSync40(path2, JSON.stringify({ messageId }) + `
91611
91861
  `, { mode: 384 });
91612
91862
  } catch (err) {
91613
91863
  log(`issues-card: persist write failed (${err.message})`);
@@ -91700,24 +91950,24 @@ function createIssuesCardHandle(opts) {
91700
91950
  }
91701
91951
 
91702
91952
  // issues-watcher.ts
91703
- import { existsSync as existsSync47, statSync as statSync15 } from "node:fs";
91704
- import { join as join52 } from "node:path";
91953
+ import { existsSync as existsSync48, statSync as statSync16 } from "node:fs";
91954
+ import { join as join53 } from "node:path";
91705
91955
 
91706
91956
  // ../src/issues/store.ts
91707
91957
  import {
91708
91958
  closeSync as closeSync10,
91709
- existsSync as existsSync46,
91710
- mkdirSync as mkdirSync40,
91959
+ existsSync as existsSync47,
91960
+ mkdirSync as mkdirSync41,
91711
91961
  openSync as openSync10,
91712
- readdirSync as readdirSync10,
91713
- readFileSync as readFileSync50,
91714
- renameSync as renameSync19,
91715
- statSync as statSync14,
91716
- unlinkSync as unlinkSync22,
91717
- writeFileSync as writeFileSync40,
91962
+ readdirSync as readdirSync11,
91963
+ readFileSync as readFileSync51,
91964
+ renameSync as renameSync20,
91965
+ statSync as statSync15,
91966
+ unlinkSync as unlinkSync23,
91967
+ writeFileSync as writeFileSync41,
91718
91968
  writeSync as writeSync6
91719
91969
  } from "node:fs";
91720
- import { join as join51 } from "node:path";
91970
+ import { join as join52 } from "node:path";
91721
91971
  import { randomBytes as randomBytes8 } from "node:crypto";
91722
91972
  import { execSync } from "node:child_process";
91723
91973
 
@@ -91736,12 +91986,12 @@ init_redact();
91736
91986
  var ISSUES_FILE = "issues.jsonl";
91737
91987
  var ISSUES_LOCK = "issues.lock";
91738
91988
  function readAll(stateDir) {
91739
- const path2 = join51(stateDir, ISSUES_FILE);
91740
- if (!existsSync46(path2))
91989
+ const path2 = join52(stateDir, ISSUES_FILE);
91990
+ if (!existsSync47(path2))
91741
91991
  return [];
91742
91992
  let raw;
91743
91993
  try {
91744
- raw = readFileSync50(path2, "utf-8");
91994
+ raw = readFileSync51(path2, "utf-8");
91745
91995
  } catch {
91746
91996
  return [];
91747
91997
  }
@@ -91773,7 +92023,7 @@ function list2(stateDir, opts = {}) {
91773
92023
  });
91774
92024
  }
91775
92025
  function resolve11(stateDir, fingerprint, nowFn = Date.now) {
91776
- if (!existsSync46(join51(stateDir, ISSUES_FILE)))
92026
+ if (!existsSync47(join52(stateDir, ISSUES_FILE)))
91777
92027
  return 0;
91778
92028
  return withLock(stateDir, () => {
91779
92029
  const all2 = readAll(stateDir);
@@ -91791,21 +92041,21 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
91791
92041
  });
91792
92042
  }
91793
92043
  function writeAll(stateDir, events) {
91794
- const path2 = join51(stateDir, ISSUES_FILE);
92044
+ const path2 = join52(stateDir, ISSUES_FILE);
91795
92045
  sweepOrphanTmpFiles(stateDir);
91796
92046
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes8(4).toString("hex")}`;
91797
92047
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
91798
92048
  `) + `
91799
92049
  `;
91800
- writeFileSync40(tmp, body, "utf-8");
91801
- renameSync19(tmp, path2);
92050
+ writeFileSync41(tmp, body, "utf-8");
92051
+ renameSync20(tmp, path2);
91802
92052
  }
91803
92053
  var ORPHAN_TMP_TTL_MS = 60000;
91804
92054
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
91805
92055
  function sweepOrphanTmpFiles(stateDir) {
91806
92056
  let entries;
91807
92057
  try {
91808
- entries = readdirSync10(stateDir);
92058
+ entries = readdirSync11(stateDir);
91809
92059
  } catch {
91810
92060
  return;
91811
92061
  }
@@ -91813,11 +92063,11 @@ function sweepOrphanTmpFiles(stateDir) {
91813
92063
  for (const entry of entries) {
91814
92064
  if (!entry.startsWith(TMP_PREFIX))
91815
92065
  continue;
91816
- const tmpPath2 = join51(stateDir, entry);
92066
+ const tmpPath2 = join52(stateDir, entry);
91817
92067
  try {
91818
- const stat = statSync14(tmpPath2);
92068
+ const stat = statSync15(tmpPath2);
91819
92069
  if (stat.mtimeMs < cutoff) {
91820
- unlinkSync22(tmpPath2);
92070
+ unlinkSync23(tmpPath2);
91821
92071
  }
91822
92072
  } catch {}
91823
92073
  }
@@ -91825,7 +92075,7 @@ function sweepOrphanTmpFiles(stateDir) {
91825
92075
  var LOCK_RETRY_MS = 25;
91826
92076
  var LOCK_TIMEOUT_MS = 1e4;
91827
92077
  function withLock(stateDir, fn) {
91828
- const lockPath = join51(stateDir, ISSUES_LOCK);
92078
+ const lockPath = join52(stateDir, ISSUES_LOCK);
91829
92079
  const startedAt = Date.now();
91830
92080
  let fd = null;
91831
92081
  while (fd === null) {
@@ -91853,27 +92103,27 @@ function withLock(stateDir, fn) {
91853
92103
  closeSync10(fd);
91854
92104
  } catch {}
91855
92105
  try {
91856
- unlinkSync22(lockPath);
92106
+ unlinkSync23(lockPath);
91857
92107
  } catch {}
91858
92108
  }
91859
92109
  }
91860
92110
  function tryStealStaleLock(lockPath) {
91861
92111
  let pidStr;
91862
92112
  try {
91863
- pidStr = readFileSync50(lockPath, "utf-8").trim();
92113
+ pidStr = readFileSync51(lockPath, "utf-8").trim();
91864
92114
  } catch {
91865
92115
  return true;
91866
92116
  }
91867
92117
  const pid = Number(pidStr);
91868
92118
  if (!Number.isFinite(pid) || pid <= 0) {
91869
92119
  try {
91870
- unlinkSync22(lockPath);
92120
+ unlinkSync23(lockPath);
91871
92121
  } catch {}
91872
92122
  return true;
91873
92123
  }
91874
92124
  if (pid === process.pid) {
91875
92125
  try {
91876
- unlinkSync22(lockPath);
92126
+ unlinkSync23(lockPath);
91877
92127
  } catch {}
91878
92128
  return true;
91879
92129
  }
@@ -91888,7 +92138,7 @@ function tryStealStaleLock(lockPath) {
91888
92138
  return false;
91889
92139
  }
91890
92140
  try {
91891
- unlinkSync22(lockPath);
92141
+ unlinkSync23(lockPath);
91892
92142
  } catch {}
91893
92143
  return true;
91894
92144
  }
@@ -91910,7 +92160,7 @@ function isIssueEvent(v) {
91910
92160
  // issues-watcher.ts
91911
92161
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
91912
92162
  function startIssuesWatcher(opts) {
91913
- const path2 = join52(opts.stateDir, ISSUES_FILE);
92163
+ const path2 = join53(opts.stateDir, ISSUES_FILE);
91914
92164
  const log = opts.log ?? (() => {});
91915
92165
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
91916
92166
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -91958,10 +92208,10 @@ function startIssuesWatcher(opts) {
91958
92208
  };
91959
92209
  }
91960
92210
  function defaultSignatureProvider(path2) {
91961
- if (!existsSync47(path2))
92211
+ if (!existsSync48(path2))
91962
92212
  return null;
91963
92213
  try {
91964
- const stat = statSync15(path2);
92214
+ const stat = statSync16(path2);
91965
92215
  return `${stat.mtimeMs}:${stat.size}`;
91966
92216
  } catch {
91967
92217
  return null;
@@ -92669,8 +92919,8 @@ function readBashCommand(inputPreview) {
92669
92919
  }
92670
92920
 
92671
92921
  // gateway/scoped-grant-store.ts
92672
- import { readFileSync as readFileSync51, writeFileSync as writeFileSync41 } from "node:fs";
92673
- import { join as join53 } from "node:path";
92922
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync42 } from "node:fs";
92923
+ import { join as join54 } from "node:path";
92674
92924
 
92675
92925
  // scoped-approval.ts
92676
92926
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -92712,11 +92962,11 @@ function scopedGrantPersistEnabled(env = process.env) {
92712
92962
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
92713
92963
  }
92714
92964
  function createScopedGrantStore(stateDir, env = process.env) {
92715
- const filePath = join53(stateDir, "scoped-grants.json");
92716
- const enabled8 = scopedGrantPersistEnabled(env);
92965
+ const filePath = join54(stateDir, "scoped-grants.json");
92966
+ const enabled7 = scopedGrantPersistEnabled(env);
92717
92967
  function read() {
92718
92968
  try {
92719
- const raw = readFileSync51(filePath, "utf-8");
92969
+ const raw = readFileSync52(filePath, "utf-8");
92720
92970
  const parsed = JSON.parse(raw);
92721
92971
  return Array.isArray(parsed) ? parsed : [];
92722
92972
  } catch {
@@ -92724,17 +92974,17 @@ function createScopedGrantStore(stateDir, env = process.env) {
92724
92974
  }
92725
92975
  }
92726
92976
  return {
92727
- enabled: enabled8,
92977
+ enabled: enabled7,
92728
92978
  load(now) {
92729
- if (!enabled8)
92979
+ if (!enabled7)
92730
92980
  return new Map;
92731
92981
  return deserializeScopedGrants(read(), now);
92732
92982
  },
92733
92983
  save(store2) {
92734
- if (!enabled8)
92984
+ if (!enabled7)
92735
92985
  return;
92736
92986
  try {
92737
- writeFileSync41(filePath, JSON.stringify(serializeScopedGrants(store2)), {
92987
+ writeFileSync42(filePath, JSON.stringify(serializeScopedGrants(store2)), {
92738
92988
  encoding: "utf-8",
92739
92989
  mode: 384
92740
92990
  });
@@ -93085,8 +93335,8 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
93085
93335
 
93086
93336
  // credits-watch.ts
93087
93337
  init_card_format();
93088
- import { readFileSync as readFileSync52, writeFileSync as writeFileSync42, existsSync as existsSync48, mkdirSync as mkdirSync41 } from "fs";
93089
- import { join as join54 } from "path";
93338
+ import { readFileSync as readFileSync53, writeFileSync as writeFileSync43, existsSync as existsSync49, mkdirSync as mkdirSync42 } from "fs";
93339
+ import { join as join55 } from "path";
93090
93340
  var STATE_FILE = "credits-watch.json";
93091
93341
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
93092
93342
  var KNOWN_CREDIT_REASONS = [
@@ -93108,12 +93358,12 @@ function emptyCreditState() {
93108
93358
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
93109
93359
  }
93110
93360
  function readClaudeJsonOverage(claudeConfigDir) {
93111
- const path2 = join54(claudeConfigDir, ".claude.json");
93112
- if (!existsSync48(path2))
93361
+ const path2 = join55(claudeConfigDir, ".claude.json");
93362
+ if (!existsSync49(path2))
93113
93363
  return null;
93114
93364
  let raw;
93115
93365
  try {
93116
- raw = readFileSync52(path2, "utf-8");
93366
+ raw = readFileSync53(path2, "utf-8");
93117
93367
  } catch {
93118
93368
  return null;
93119
93369
  }
@@ -93191,11 +93441,11 @@ function humanizeReason(reason) {
93191
93441
  }
93192
93442
  }
93193
93443
  function loadCreditState(stateDir) {
93194
- const path2 = join54(stateDir, STATE_FILE);
93195
- if (!existsSync48(path2))
93444
+ const path2 = join55(stateDir, STATE_FILE);
93445
+ if (!existsSync49(path2))
93196
93446
  return emptyCreditState();
93197
93447
  try {
93198
- const raw = readFileSync52(path2, "utf-8");
93448
+ const raw = readFileSync53(path2, "utf-8");
93199
93449
  const parsed = JSON.parse(raw);
93200
93450
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
93201
93451
  return {
@@ -93207,17 +93457,17 @@ function loadCreditState(stateDir) {
93207
93457
  return emptyCreditState();
93208
93458
  }
93209
93459
  function saveCreditState(stateDir, state7) {
93210
- mkdirSync41(stateDir, { recursive: true });
93211
- const path2 = join54(stateDir, STATE_FILE);
93212
- writeFileSync42(path2, JSON.stringify(state7, null, 2) + `
93460
+ mkdirSync42(stateDir, { recursive: true });
93461
+ const path2 = join55(stateDir, STATE_FILE);
93462
+ writeFileSync43(path2, JSON.stringify(state7, null, 2) + `
93213
93463
  `, { mode: 384 });
93214
93464
  }
93215
93465
 
93216
93466
  // quota-watch.ts
93217
93467
  init_auth_snapshot_format();
93218
93468
  init_card_format();
93219
- import { readFileSync as readFileSync53, writeFileSync as writeFileSync43, existsSync as existsSync49, mkdirSync as mkdirSync42 } from "fs";
93220
- import { join as join55 } from "path";
93469
+ import { readFileSync as readFileSync54, writeFileSync as writeFileSync44, existsSync as existsSync50, mkdirSync as mkdirSync43 } from "fs";
93470
+ import { join as join56 } from "path";
93221
93471
  var STATE_FILE2 = "quota-watch.json";
93222
93472
  function emptyQuotaWatchState() {
93223
93473
  return {};
@@ -93455,11 +93705,11 @@ function buildRecoveryMessage(agentName3, snap) {
93455
93705
  `);
93456
93706
  }
93457
93707
  function loadQuotaWatchState(stateDir) {
93458
- const path2 = join55(stateDir, STATE_FILE2);
93459
- if (!existsSync49(path2))
93708
+ const path2 = join56(stateDir, STATE_FILE2);
93709
+ if (!existsSync50(path2))
93460
93710
  return emptyQuotaWatchState();
93461
93711
  try {
93462
- const raw = readFileSync53(path2, "utf-8");
93712
+ const raw = readFileSync54(path2, "utf-8");
93463
93713
  const parsed = JSON.parse(raw);
93464
93714
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
93465
93715
  return emptyQuotaWatchState();
@@ -93476,9 +93726,9 @@ function loadQuotaWatchState(stateDir) {
93476
93726
  }
93477
93727
  }
93478
93728
  function saveQuotaWatchState(stateDir, state7) {
93479
- mkdirSync42(stateDir, { recursive: true });
93480
- const path2 = join55(stateDir, STATE_FILE2);
93481
- writeFileSync43(path2, JSON.stringify(state7, null, 2) + `
93729
+ mkdirSync43(stateDir, { recursive: true });
93730
+ const path2 = join56(stateDir, STATE_FILE2);
93731
+ writeFileSync44(path2, JSON.stringify(state7, null, 2) + `
93482
93732
  `, { mode: 384 });
93483
93733
  }
93484
93734
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -93526,31 +93776,31 @@ init_auth_snapshot_format2();
93526
93776
  // gateway/turn-active-marker.ts
93527
93777
  import {
93528
93778
  closeSync as closeSync11,
93529
- existsSync as existsSync50,
93530
- mkdirSync as mkdirSync43,
93779
+ existsSync as existsSync51,
93780
+ mkdirSync as mkdirSync44,
93531
93781
  openSync as openSync11,
93532
- readFileSync as readFileSync54,
93533
- statSync as statSync16,
93534
- unlinkSync as unlinkSync23,
93782
+ readFileSync as readFileSync55,
93783
+ statSync as statSync17,
93784
+ unlinkSync as unlinkSync24,
93535
93785
  utimesSync as utimesSync2,
93536
- writeFileSync as writeFileSync44
93786
+ writeFileSync as writeFileSync45
93537
93787
  } from "node:fs";
93538
- import { join as join56 } from "node:path";
93788
+ import { join as join57 } from "node:path";
93539
93789
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
93540
93790
  var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
93541
93791
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
93542
93792
  function removeTurnActiveMarker2(stateDir) {
93543
93793
  try {
93544
- unlinkSync23(join56(stateDir, TURN_ACTIVE_MARKER_FILE2));
93794
+ unlinkSync24(join57(stateDir, TURN_ACTIVE_MARKER_FILE2));
93545
93795
  } catch {}
93546
93796
  }
93547
93797
  function sweepStaleTurnActiveMarker(stateDir, opts) {
93548
- const path2 = join56(stateDir, TURN_ACTIVE_MARKER_FILE2);
93549
- if (!existsSync50(path2))
93798
+ const path2 = join57(stateDir, TURN_ACTIVE_MARKER_FILE2);
93799
+ if (!existsSync51(path2))
93550
93800
  return false;
93551
93801
  const now = opts.now ?? Date.now();
93552
93802
  try {
93553
- const st = statSync16(path2);
93803
+ const st = statSync17(path2);
93554
93804
  const ageMs = now - st.mtimeMs;
93555
93805
  const hardExpired = ageMs > opts.hardTtlMs;
93556
93806
  const idleExpired = !opts.turnInFlight && ageMs > opts.idleSweepMs;
@@ -93558,9 +93808,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
93558
93808
  return false;
93559
93809
  let payload = null;
93560
93810
  try {
93561
- payload = readFileSync54(path2, "utf8");
93811
+ payload = readFileSync55(path2, "utf8");
93562
93812
  } catch {}
93563
- unlinkSync23(path2);
93813
+ unlinkSync24(path2);
93564
93814
  if (opts.onRemove) {
93565
93815
  try {
93566
93816
  opts.onRemove({
@@ -93576,9 +93826,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
93576
93826
  }
93577
93827
  }
93578
93828
  function readTurnActiveMarkerAgeMs2(stateDir, now) {
93579
- const path2 = join56(stateDir, TURN_ACTIVE_MARKER_FILE2);
93829
+ const path2 = join57(stateDir, TURN_ACTIVE_MARKER_FILE2);
93580
93830
  try {
93581
- const st = statSync16(path2);
93831
+ const st = statSync17(path2);
93582
93832
  return (now ?? Date.now()) - st.mtimeMs;
93583
93833
  } catch {
93584
93834
  return null;
@@ -93589,19 +93839,19 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
93589
93839
  }
93590
93840
 
93591
93841
  // gateway/gateway-heartbeat.ts
93592
- import { mkdirSync as mkdirSync44, utimesSync as utimesSync3, writeFileSync as writeFileSync45 } from "node:fs";
93593
- import { join as join57 } from "node:path";
93842
+ import { mkdirSync as mkdirSync45, utimesSync as utimesSync3, writeFileSync as writeFileSync46 } from "node:fs";
93843
+ import { join as join58 } from "node:path";
93594
93844
  var GATEWAY_HEARTBEAT_FILE = "gateway-heartbeat";
93595
93845
  var GATEWAY_HEARTBEAT_INTERVAL_MS = 15000;
93596
93846
  function touchGatewayHeartbeat(stateDir) {
93597
- const path2 = join57(stateDir, GATEWAY_HEARTBEAT_FILE);
93847
+ const path2 = join58(stateDir, GATEWAY_HEARTBEAT_FILE);
93598
93848
  const now = new Date;
93599
93849
  try {
93600
93850
  utimesSync3(path2, now, now);
93601
93851
  } catch {
93602
93852
  try {
93603
- mkdirSync44(stateDir, { recursive: true });
93604
- writeFileSync45(path2, `${Date.now()}
93853
+ mkdirSync45(stateDir, { recursive: true });
93854
+ writeFileSync46(path2, `${Date.now()}
93605
93855
  `, { mode: 384 });
93606
93856
  } catch {}
93607
93857
  }
@@ -93613,11 +93863,133 @@ function startGatewayHeartbeat(stateDir, intervalMs = GATEWAY_HEARTBEAT_INTERVAL
93613
93863
  return timer3;
93614
93864
  }
93615
93865
 
93866
+ // gateway/outbox-sweep.ts
93867
+ async function sweepOutbox(deps) {
93868
+ const now = deps.now?.() ?? Date.now();
93869
+ const log = deps.log ?? (() => {});
93870
+ const summary = { scanned: 0, delivered: 0, skipped: 0 };
93871
+ reclaimStaleSending(deps.stateDir, now);
93872
+ const pending = listPendingRecords(deps.stateDir);
93873
+ if (pending.length === 0)
93874
+ return summary;
93875
+ const deliveredNonces = readDeliveredNonces(deps.stateDir);
93876
+ for (const fileName of pending) {
93877
+ const record2 = readOutboxRecord(fileName, deps.stateDir);
93878
+ if (record2 == null)
93879
+ continue;
93880
+ summary.scanned++;
93881
+ const resolved = resolveOutboxChat(record2, {
93882
+ registryChainLookup: (anchorContent) => {
93883
+ const taskId = extractTaskId(anchorContent);
93884
+ if (taskId == null || deps.registryChainLookup == null)
93885
+ return null;
93886
+ return deps.registryChainLookup(taskId);
93887
+ }
93888
+ });
93889
+ const routePrefix = resolved?.via === "origin" ? "(from background task) " : "";
93890
+ const decision = decideOutboxSweep({
93891
+ record: record2,
93892
+ now,
93893
+ deliveredNonces,
93894
+ textAlreadyDelivered: resolved != null && deps.textAlreadyDelivered(resolved.chatId, resolved.threadId, record2.text),
93895
+ routable: resolved != null,
93896
+ routePrefix,
93897
+ quietMs: deps.quietMs ?? OUTBOX_QUIET_MS
93898
+ });
93899
+ if (decision.action !== "send" && decision.action !== "send-delayed") {
93900
+ summary.skipped++;
93901
+ if (decision.action === "skip-journaled") {
93902
+ clearOutboxRecord(record2.turnNonce, deps.stateDir);
93903
+ } else if (decision.action === "skip-dedup") {
93904
+ appendDelivered({
93905
+ turnNonce: record2.turnNonce,
93906
+ textSha256: record2.textSha256,
93907
+ ts: now,
93908
+ deliverySource: "sweep",
93909
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true
93910
+ }, deps.stateDir);
93911
+ clearOutboxRecord(record2.turnNonce, deps.stateDir);
93912
+ }
93913
+ continue;
93914
+ }
93915
+ const claimed = claimRecord(record2.turnNonce, deps.stateDir);
93916
+ if (claimed == null) {
93917
+ summary.skipped++;
93918
+ continue;
93919
+ }
93920
+ const resolvedChat = resolved;
93921
+ try {
93922
+ const messageId = await deps.send(resolvedChat.chatId, resolvedChat.threadId, decision.text ?? record2.text);
93923
+ appendDelivered({
93924
+ turnNonce: record2.turnNonce,
93925
+ textSha256: record2.textSha256,
93926
+ tgMessageId: messageId,
93927
+ ts: now,
93928
+ deliverySource: "sweep",
93929
+ replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true
93930
+ }, deps.stateDir);
93931
+ removeClaimed(record2.turnNonce, deps.stateDir);
93932
+ summary.delivered++;
93933
+ log(`outbox-sweep: delivered nonce=${record2.turnNonce} via=${resolvedChat.via} ` + `source=${record2.source} chars=${record2.text.length}${decision.action === "send-delayed" ? " (delayed)" : ""}
93934
+ `);
93935
+ } catch (err) {
93936
+ releaseClaim(record2.turnNonce, deps.stateDir);
93937
+ summary.skipped++;
93938
+ log(`outbox-sweep: send failed nonce=${record2.turnNonce}: ${err.message} \u2014 will retry
93939
+ `);
93940
+ }
93941
+ }
93942
+ return summary;
93943
+ }
93944
+ function chatFromTurnKey(turnKey3) {
93945
+ const idx = turnKey3.indexOf(":");
93946
+ const chatId = idx === -1 ? turnKey3 : turnKey3.slice(0, idx);
93947
+ const tRaw = idx === -1 ? "_" : turnKey3.slice(idx + 1);
93948
+ const t = tRaw === "_" || tRaw === "" ? null : Number(tRaw);
93949
+ return { chatId, threadId: Number.isFinite(t) ? t : null };
93950
+ }
93951
+ var OUTBOX_SWEEP_INTERVAL_MS = 5000;
93952
+ function startOutboxSweep(deps) {
93953
+ if (!deps.isGatewayMain || process.env.SWITCHROOM_TG_OUTBOX_DELIVERY === "0")
93954
+ return;
93955
+ const retry = createRetryApiCall({ log: deps.log });
93956
+ const tick3 = () => {
93957
+ const bot = deps.getBot();
93958
+ if (bot == null)
93959
+ return;
93960
+ sweepOutbox({
93961
+ stateDir: deps.stateDir,
93962
+ 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
+ },
93972
+ textAlreadyDelivered: (chatId, threadId, text4) => deps.dedupCheck(chatId, threadId ?? undefined, text4),
93973
+ registryChainLookup: (taskId) => {
93974
+ const db3 = deps.getTurnsDb();
93975
+ if (db3 == null)
93976
+ return null;
93977
+ const turnKey3 = resolveSubagentOriginTurnKey(db3, taskId);
93978
+ return turnKey3 == null ? null : chatFromTurnKey(turnKey3);
93979
+ }
93980
+ }).catch((err) => deps.log?.(`outbox-sweep: tick failed: ${err.message}
93981
+ `));
93982
+ };
93983
+ const timer3 = setInterval(tick3, OUTBOX_SWEEP_INTERVAL_MS);
93984
+ timer3.unref?.();
93985
+ return timer3;
93986
+ }
93987
+
93616
93988
  // ../src/build-info.ts
93617
- var VERSION = "0.19.12";
93618
- var COMMIT_SHA = "c2de527a";
93619
- var COMMIT_DATE = "2026-07-21T22:14:51Z";
93620
- var LATEST_PR = 3499;
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;
93621
93993
  var COMMITS_AHEAD_OF_TAG = 0;
93622
93994
 
93623
93995
  // gateway/boot-version.ts
@@ -93662,10 +94034,10 @@ function composeBootVersionString(inputs) {
93662
94034
  }
93663
94035
 
93664
94036
  // gateway/unhandled-rejection-policy.ts
93665
- var import_grammy13 = __toESM(require_mod2(), 1);
94037
+ var import_grammy14 = __toESM(require_mod2(), 1);
93666
94038
  function classifyRejection(err, opts = {}) {
93667
- const isGrammy = opts.isGrammyError != null ? opts.isGrammyError(err) : err instanceof import_grammy13.GrammyError;
93668
- const isHttp = opts.isHttpError != null ? opts.isHttpError(err) : err instanceof import_grammy13.HttpError;
94039
+ const isGrammy = opts.isGrammyError != null ? opts.isGrammyError(err) : err instanceof import_grammy14.GrammyError;
94040
+ const isHttp = opts.isHttpError != null ? opts.isHttpError(err) : err instanceof import_grammy14.HttpError;
93669
94041
  if (isHttp)
93670
94042
  return "log_only";
93671
94043
  if (err instanceof Error && err.message === FLOOD_WAIT_ACTIVE)
@@ -93694,12 +94066,12 @@ init_protocol();
93694
94066
  init_peercred();
93695
94067
  import * as net5 from "node:net";
93696
94068
  import * as fs2 from "node:fs";
93697
- import { homedir as homedir18 } from "node:os";
93698
- import { join as join58 } from "node:path";
94069
+ import { homedir as homedir19 } from "node:os";
94070
+ import { join as join59 } from "node:path";
93699
94071
  var DEFAULT_TIMEOUT_MS4 = 2000;
93700
94072
  var UNLOCK_TIMEOUT_MS = 30000;
93701
- var LEGACY_SOCKET_PATH2 = join58(homedir18(), ".switchroom", "vault-broker.sock");
93702
- var OPERATOR_SOCKET_PATH2 = join58(homedir18(), ".switchroom", "broker-operator", "sock");
94073
+ var LEGACY_SOCKET_PATH2 = join59(homedir19(), ".switchroom", "vault-broker.sock");
94074
+ var OPERATOR_SOCKET_PATH2 = join59(homedir19(), ".switchroom", "broker-operator", "sock");
93703
94075
  function defaultBrokerSocketPath2() {
93704
94076
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
93705
94077
  return OPERATOR_SOCKET_PATH2;
@@ -94581,8 +94953,8 @@ function resolveVaultApprovalPosture(broker) {
94581
94953
  }
94582
94954
 
94583
94955
  // registry/turns-schema.ts
94584
- import { chmodSync as chmodSync12, mkdirSync as mkdirSync45 } from "fs";
94585
- import { join as join59 } from "path";
94956
+ import { chmodSync as chmodSync12, mkdirSync as mkdirSync46 } from "fs";
94957
+ import { join as join60 } from "path";
94586
94958
  var DatabaseClass3 = null;
94587
94959
  function loadDatabaseClass3() {
94588
94960
  if (DatabaseClass3 != null)
@@ -94654,9 +95026,9 @@ function applySchema(db3) {
94654
95026
  }
94655
95027
  function openTurnsDb(agentDir) {
94656
95028
  const Database = loadDatabaseClass3();
94657
- const dir = join59(agentDir, "telegram");
94658
- mkdirSync45(dir, { recursive: true, mode: 448 });
94659
- const path2 = join59(dir, "registry.db");
95029
+ const dir = join60(agentDir, "telegram");
95030
+ mkdirSync46(dir, { recursive: true, mode: 448 });
95031
+ const path2 = join60(dir, "registry.db");
94660
95032
  const db3 = new Database(path2, { create: true });
94661
95033
  applySchema(db3);
94662
95034
  try {
@@ -95000,7 +95372,7 @@ function selectResumeBuilder(endedVia, opts) {
95000
95372
  }
95001
95373
 
95002
95374
  // gateway/bridge-dead-watchdog.ts
95003
- import { readFileSync as readFileSync56, writeFileSync as writeFileSync46, renameSync as renameSync20, unlinkSync as unlinkSync24 } from "node:fs";
95375
+ import { readFileSync as readFileSync57, writeFileSync as writeFileSync47, renameSync as renameSync21, unlinkSync as unlinkSync25 } from "node:fs";
95004
95376
 
95005
95377
  // gateway/cron-session.ts
95006
95378
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -95033,7 +95405,7 @@ function readFreshCrashLogTail(path2, opts = {}) {
95033
95405
  const nowMs3 = opts.nowMs ?? Date.now();
95034
95406
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
95035
95407
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
95036
- const readFile = opts.readFile ?? ((p) => readFileSync56(p, "utf8"));
95408
+ const readFile = opts.readFile ?? ((p) => readFileSync57(p, "utf8"));
95037
95409
  let raw;
95038
95410
  try {
95039
95411
  raw = readFile(path2);
@@ -95054,13 +95426,13 @@ function readFreshCrashLogTail(path2, opts = {}) {
95054
95426
  }
95055
95427
  function writeBridgeDeadEscalationMarker(path2, marker) {
95056
95428
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
95057
- writeFileSync46(tmp, JSON.stringify(marker), "utf8");
95058
- renameSync20(tmp, path2);
95429
+ writeFileSync47(tmp, JSON.stringify(marker), "utf8");
95430
+ renameSync21(tmp, path2);
95059
95431
  }
95060
95432
  function consumeBridgeDeadEscalationMarker(path2, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
95061
95433
  let marker = null;
95062
95434
  try {
95063
- const parsed = JSON.parse(readFileSync56(path2, "utf8"));
95435
+ const parsed = JSON.parse(readFileSync57(path2, "utf8"));
95064
95436
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
95065
95437
  const age = nowMs3 - parsed.ts;
95066
95438
  if (age >= 0 && age < maxAgeMs) {
@@ -95072,7 +95444,7 @@ function consumeBridgeDeadEscalationMarker(path2, nowMs3 = Date.now(), maxAgeMs
95072
95444
  }
95073
95445
  } catch {}
95074
95446
  try {
95075
- unlinkSync24(path2);
95447
+ unlinkSync25(path2);
95076
95448
  } catch {}
95077
95449
  return marker;
95078
95450
  }
@@ -95213,15 +95585,15 @@ function createBridgeDeadWatchdog(opts) {
95213
95585
  }
95214
95586
 
95215
95587
  // gateway/boot-probes.ts
95216
- import { readFileSync as readFileSync57, readdirSync as readdirSync11, existsSync as existsSync52 } from "fs";
95588
+ import { readFileSync as readFileSync58, readdirSync as readdirSync12, existsSync as existsSync53 } from "fs";
95217
95589
  init_quota_cache();
95218
95590
  init_quota_check();
95219
95591
  import { execFile as execFileCb2 } from "child_process";
95220
95592
  import { promisify as promisify2 } from "util";
95221
95593
  var execFile2 = promisify2(execFileCb2);
95222
95594
  var realProcFs2 = {
95223
- readdir: (p) => readdirSync11(p),
95224
- readFile: (p) => readFileSync57(p, "utf-8")
95595
+ readdir: (p) => readdirSync12(p),
95596
+ readFile: (p) => readFileSync58(p, "utf-8")
95225
95597
  };
95226
95598
  function findAgentProcessInContainer2(fs3 = realProcFs2) {
95227
95599
  let entries;
@@ -95353,7 +95725,7 @@ function listNonTerminalSubagentsForTurn(db3, parentTurnKey) {
95353
95725
  `).all(parentTurnKey);
95354
95726
  return rows.map(mapSubagentRow);
95355
95727
  }
95356
- function resolveSubagentOriginTurnKey(db3, jsonlAgentId, maxHops = 5) {
95728
+ function resolveSubagentOriginTurnKey2(db3, jsonlAgentId, maxHops = 5) {
95357
95729
  const seen = new Set;
95358
95730
  let currentJsonlId = jsonlAgentId;
95359
95731
  for (let hop = 0;hop <= maxHops && currentJsonlId != null; hop++) {
@@ -95471,7 +95843,7 @@ if (isGatewayMain) {
95471
95843
  shutdownAnalytics();
95472
95844
  });
95473
95845
  }
95474
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join60(homedir19(), ".claude", "channels", "telegram");
95846
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join61(homedir20(), ".claude", "channels", "telegram");
95475
95847
  var permCardStore = createPermissionCardStore(STATE_DIR);
95476
95848
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
95477
95849
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -95510,7 +95882,7 @@ function alwaysAllowDrainDeps() {
95510
95882
  return {
95511
95883
  readConfigText: () => {
95512
95884
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
95513
- return readFileSync58(cfgPath, "utf8");
95885
+ return readFileSync59(cfgPath, "utf8");
95514
95886
  },
95515
95887
  resolveAllowList: (_configText, agentName3) => {
95516
95888
  const cfg = loadConfig2();
@@ -95571,11 +95943,11 @@ function scheduleAlwaysAllowPersistDrain() {
95571
95943
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
95572
95944
  timer3.unref?.();
95573
95945
  }
95574
- var ACCESS_FILE = join60(STATE_DIR, "access.json");
95575
- var APPROVED_DIR = join60(STATE_DIR, "approved");
95576
- var ENV_FILE = join60(STATE_DIR, ".env");
95577
- var INBOX_DIR = join60(STATE_DIR, "inbox");
95578
- var PEOPLE_FILE = join60(STATE_DIR, "people.json");
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");
95579
95951
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
95580
95952
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
95581
95953
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -95640,7 +96012,7 @@ function formatBootVersion() {
95640
96012
  }
95641
96013
  try {
95642
96014
  chmodSync14(ENV_FILE, 384);
95643
- for (const line of readFileSync58(ENV_FILE, "utf8").split(`
96015
+ for (const line of readFileSync59(ENV_FILE, "utf8").split(`
95644
96016
  `)) {
95645
96017
  const m = line.match(/^(\w+)=(.*)$/);
95646
96018
  if (m && process.env[m[1]] === undefined)
@@ -95661,7 +96033,7 @@ var bot;
95661
96033
  var lastGetUpdatesHeartbeatMs = Date.now();
95662
96034
  var GRAMMY_VERSION = (() => {
95663
96035
  try {
95664
- const raw = readFileSync58(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
96036
+ const raw = readFileSync59(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
95665
96037
  return JSON.parse(raw).version ?? "unknown";
95666
96038
  } catch {
95667
96039
  return "unknown";
@@ -95724,7 +96096,7 @@ function assertSendable(f) {
95724
96096
  } catch {
95725
96097
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
95726
96098
  }
95727
- const inbox = join60(stateReal, "inbox");
96099
+ const inbox = join61(stateReal, "inbox");
95728
96100
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
95729
96101
  throw new Error(`refusing to send channel state: ${f}`);
95730
96102
  }
@@ -95743,7 +96115,7 @@ function assertSendable(f) {
95743
96115
  }
95744
96116
  function readAccessFile() {
95745
96117
  try {
95746
- const raw = readFileSync58(ACCESS_FILE, "utf8");
96118
+ const raw = readFileSync59(ACCESS_FILE, "utf8");
95747
96119
  const parsed = JSON.parse(raw);
95748
96120
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
95749
96121
  const groups = {};
@@ -95783,7 +96155,7 @@ function readAccessFile() {
95783
96155
  if (err.code === "ENOENT")
95784
96156
  return defaultAccess();
95785
96157
  try {
95786
- renameSync21(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
96158
+ renameSync22(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
95787
96159
  } catch {}
95788
96160
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
95789
96161
  `);
@@ -95805,7 +96177,7 @@ function loadAccess() {
95805
96177
  }
95806
96178
  function readPeopleFile() {
95807
96179
  try {
95808
- const raw = readFileSync58(PEOPLE_FILE, "utf8");
96180
+ const raw = readFileSync59(PEOPLE_FILE, "utf8");
95809
96181
  const parsed = JSON.parse(raw);
95810
96182
  if (!Array.isArray(parsed.entries))
95811
96183
  return [];
@@ -95827,11 +96199,11 @@ function assertAllowedChat(chat_id) {
95827
96199
  function saveAccess(a) {
95828
96200
  if (STATIC)
95829
96201
  return;
95830
- mkdirSync47(STATE_DIR, { recursive: true, mode: 448 });
96202
+ mkdirSync48(STATE_DIR, { recursive: true, mode: 448 });
95831
96203
  const tmp = ACCESS_FILE + ".tmp";
95832
- writeFileSync48(tmp, JSON.stringify(a, null, 2) + `
96204
+ writeFileSync49(tmp, JSON.stringify(a, null, 2) + `
95833
96205
  `, { mode: 384 });
95834
- renameSync21(tmp, ACCESS_FILE);
96206
+ renameSync22(tmp, ACCESS_FILE);
95835
96207
  }
95836
96208
  function pruneExpired(a) {
95837
96209
  const now = Date.now();
@@ -95849,7 +96221,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
95849
96221
  if (isGatewayMain && HISTORY_ENABLED) {
95850
96222
  try {
95851
96223
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
95852
- process.stderr.write(`telegram gateway: history capture enabled at ${join60(STATE_DIR, "history.db")}
96224
+ process.stderr.write(`telegram gateway: history capture enabled at ${join61(STATE_DIR, "history.db")}
95853
96225
  `);
95854
96226
  } catch (err) {
95855
96227
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -95868,12 +96240,12 @@ if (isGatewayMain)
95868
96240
  let markerTurnKey = null;
95869
96241
  let markerAgeMs = null;
95870
96242
  try {
95871
- const markerPath = join60(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
95872
- if (existsSync54(markerPath)) {
95873
- const st = statSync19(markerPath);
96243
+ const markerPath = join61(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
96244
+ if (existsSync55(markerPath)) {
96245
+ const st = statSync20(markerPath);
95874
96246
  markerAgeMs = Date.now() - st.mtimeMs;
95875
96247
  try {
95876
- const payload = JSON.parse(readFileSync58(markerPath, "utf8"));
96248
+ const payload = JSON.parse(readFileSync59(markerPath, "utf8"));
95877
96249
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
95878
96250
  markerTurnKey = payload.turnKey;
95879
96251
  }
@@ -95893,10 +96265,10 @@ if (isGatewayMain)
95893
96265
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
95894
96266
  `);
95895
96267
  } else {
95896
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join60(agentDir, "telegram", "registry.db")}
96268
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join61(agentDir, "telegram", "registry.db")}
95897
96269
  `);
95898
96270
  }
95899
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join60(STATE_DIR, "bridge-dead-escalation.json"));
96271
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join61(STATE_DIR, "bridge-dead-escalation.json"));
95900
96272
  if (bridgeDeadMarker != null) {
95901
96273
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
95902
96274
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -95909,7 +96281,7 @@ if (isGatewayMain)
95909
96281
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
95910
96282
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
95911
96283
  if (pending2 != null && selfAgent) {
95912
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join60(STATE_DIR, "clean-shutdown.json");
96284
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join61(STATE_DIR, "clean-shutdown.json");
95913
96285
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
95914
96286
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
95915
96287
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -96022,7 +96394,7 @@ if (isGatewayMain)
96022
96394
  `);
96023
96395
  }
96024
96396
  }
96025
- const pendingEnvPath = join60(agentDir, ".pending-turn.env");
96397
+ const pendingEnvPath = join61(agentDir, ".pending-turn.env");
96026
96398
  try {
96027
96399
  if (pending2 != null) {
96028
96400
  const lines = [
@@ -96036,13 +96408,13 @@ if (isGatewayMain)
96036
96408
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
96037
96409
  ];
96038
96410
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
96039
- writeFileSync48(pendingEnvTmp, lines.join(`
96411
+ writeFileSync49(pendingEnvTmp, lines.join(`
96040
96412
  `) + `
96041
96413
  `, { mode: 384 });
96042
- renameSync21(pendingEnvTmp, pendingEnvPath);
96414
+ renameSync22(pendingEnvTmp, pendingEnvPath);
96043
96415
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
96044
96416
  `);
96045
- } else if (existsSync54(pendingEnvPath)) {
96417
+ } else if (existsSync55(pendingEnvPath)) {
96046
96418
  rmSync6(pendingEnvPath, { force: true });
96047
96419
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
96048
96420
  `);
@@ -96060,7 +96432,7 @@ function resolveSubagentOriginChat(agentId) {
96060
96432
  if (turnsDb == null)
96061
96433
  return null;
96062
96434
  try {
96063
- const originKey = resolveSubagentOriginTurnKey(turnsDb, agentId);
96435
+ const originKey = resolveSubagentOriginTurnKey2(turnsDb, agentId);
96064
96436
  if (originKey == null)
96065
96437
  return null;
96066
96438
  const turn = getTurnByKey(turnsDb, originKey);
@@ -96145,12 +96517,12 @@ if (isGatewayMain && !STATIC) {
96145
96517
  function checkApprovals() {
96146
96518
  let files;
96147
96519
  try {
96148
- files = readdirSync13(APPROVED_DIR);
96520
+ files = readdirSync14(APPROVED_DIR);
96149
96521
  } catch {
96150
96522
  return;
96151
96523
  }
96152
96524
  for (const senderId of files) {
96153
- const file = join60(APPROVED_DIR, senderId);
96525
+ const file = join61(APPROVED_DIR, senderId);
96154
96526
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync6(file, { force: true }), (err) => {
96155
96527
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
96156
96528
  `);
@@ -96323,12 +96695,12 @@ function noteAgentOutputAt(key, ts) {
96323
96695
  lastAgentOutputAt.delete(oldest);
96324
96696
  }
96325
96697
  }
96326
- var OBLIGATION_STORE_PATH = join60(STATE_DIR, "obligations.json");
96698
+ var OBLIGATION_STORE_PATH = join61(STATE_DIR, "obligations.json");
96327
96699
  var obligationStoreFs = {
96328
- readFileSync: (p) => readFileSync58(p, "utf8"),
96329
- writeFileSync: (p, d) => writeFileSync48(p, d),
96330
- renameSync: (a, b) => renameSync21(a, b),
96331
- existsSync: (p) => existsSync54(p)
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)
96332
96704
  };
96333
96705
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
96334
96706
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -97102,14 +97474,14 @@ function emitTurnRecord(turn, endedAt) {
97102
97474
  maybeRotate(turnsPath, {
97103
97475
  statSize: (p) => {
97104
97476
  try {
97105
- return statSync19(p).size;
97477
+ return statSync20(p).size;
97106
97478
  } catch {
97107
97479
  return;
97108
97480
  }
97109
97481
  },
97110
- rename: (from, to) => renameSync21(from, to)
97482
+ rename: (from, to) => renameSync22(from, to)
97111
97483
  });
97112
- appendFileSync7(turnsPath, rec);
97484
+ appendFileSync8(turnsPath, rec);
97113
97485
  } catch {}
97114
97486
  }
97115
97487
  function maybeProactiveCompact() {
@@ -97591,7 +97963,7 @@ function wrapBootCardApi(threadId) {
97591
97963
  await lockedBot.api.editMessageText(cid, mid, richMessage2(text5), editOpts);
97592
97964
  return "edited";
97593
97965
  } catch (err) {
97594
- const desc = err instanceof import_grammy15.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
97966
+ const desc = err instanceof import_grammy16.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
97595
97967
  if (typeof desc === "string" && desc.toLowerCase().includes("not modified"))
97596
97968
  return "edited";
97597
97969
  return "gone";
@@ -97735,7 +98107,7 @@ function sweepStaleAlwaysAllowCorrelations(now = Date.now()) {
97735
98107
  var MENTAL_MODEL_CORRELATION_TTL_MS = 720000;
97736
98108
  var pendingMentalModelCorrelations = createSweepableStore((entry, now) => now - entry.createdAt > MENTAL_MODEL_CORRELATION_TTL_MS);
97737
98109
  function mentalModelCorrelationKey(agentName3, unifiedDiff) {
97738
- return `${agentName3}::${createHash4("sha256").update(unifiedDiff).digest("hex")}`;
98110
+ return `${agentName3}::${createHash5("sha256").update(unifiedDiff).digest("hex")}`;
97739
98111
  }
97740
98112
  function sweepStaleMentalModelCorrelations(now = Date.now()) {
97741
98113
  pendingMentalModelCorrelations.sweep(now);
@@ -98671,28 +99043,28 @@ var statusPinState = new Map;
98671
99043
  var statusPinChatIds = new Map;
98672
99044
  var statusPinPinnedAt = new Map;
98673
99045
  var statusPinRightsCache = new PinRightsCache2;
98674
- var STATUS_PIN_STORE_PATH = join60(STATE_DIR, "status-pins.json");
99046
+ var STATUS_PIN_STORE_PATH = join61(STATE_DIR, "status-pins.json");
98675
99047
  var statusPinStoreFs = {
98676
- readFileSync: (p) => readFileSync58(p, "utf8"),
98677
- writeFileSync: (p, d) => writeFileSync48(p, d),
98678
- renameSync: (a, b) => renameSync21(a, b),
98679
- existsSync: (p) => existsSync54(p)
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)
98680
99052
  };
98681
99053
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
98682
- var ACTIVITY_CARD_STORE_PATH = join60(STATE_DIR, "activity-cards-pending.json");
99054
+ var ACTIVITY_CARD_STORE_PATH = join61(STATE_DIR, "activity-cards-pending.json");
98683
99055
  var activityCardStoreFs = {
98684
- readFileSync: (p) => readFileSync58(p, "utf8"),
98685
- writeFileSync: (p, d) => writeFileSync48(p, d),
98686
- renameSync: (a, b) => renameSync21(a, b),
98687
- existsSync: (p) => existsSync54(p)
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)
98688
99060
  };
98689
99061
  var activityCardPersistEnabled = !STATIC;
98690
- var QUEUED_CARD_STORE_PATH = join60(STATE_DIR, "queued-cards-pending.json");
99062
+ var QUEUED_CARD_STORE_PATH = join61(STATE_DIR, "queued-cards-pending.json");
98691
99063
  var queuedCardStoreFs = {
98692
- readFileSync: (p) => readFileSync58(p, "utf8"),
98693
- writeFileSync: (p, d) => writeFileSync48(p, d),
98694
- renameSync: (a, b) => renameSync21(a, b),
98695
- existsSync: (p) => existsSync54(p)
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)
98696
99068
  };
98697
99069
  var queuedCardPersistEnabled = !STATIC;
98698
99070
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -99065,12 +99437,12 @@ var getPinnedProgressCardMessageId = null;
99065
99437
  var completeProgressCardTurn = null;
99066
99438
  var subagentWatcher = null;
99067
99439
  var workerActivityFeed = null;
99068
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join60(STATE_DIR, "gateway.sock");
99440
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join61(STATE_DIR, "gateway.sock");
99069
99441
  if (isGatewayMain)
99070
- mkdirSync47(STATE_DIR, { recursive: true, mode: 448 });
99071
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join60(STATE_DIR, "gateway.pid.json");
99072
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join60(STATE_DIR, "gateway-session.json");
99073
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join60(STATE_DIR, "clean-shutdown.json");
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");
99074
99446
  var GATEWAY_STARTED_AT_MS = Date.now();
99075
99447
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
99076
99448
  var activeBootCard = null;
@@ -99099,7 +99471,7 @@ function ensureIssuesCard(chatId, threadId) {
99099
99471
  bot: botApi,
99100
99472
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
99101
99473
  `),
99102
- persistPath: join60(stateDir, "issues-card.json")
99474
+ persistPath: join61(stateDir, "issues-card.json")
99103
99475
  });
99104
99476
  activeIssuesWatcher = startIssuesWatcher({
99105
99477
  stateDir,
@@ -99275,6 +99647,7 @@ function runDeliveryConfirmSweep() {
99275
99647
  }
99276
99648
  var _deliveryConfirmSweep = isGatewayMain ? setInterval(runDeliveryConfirmSweep, DELIVERY_CONFIRM_SWEEP_MS) : undefined;
99277
99649
  _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) });
99278
99651
  if (isGatewayMain)
99279
99652
  startTimer2({
99280
99653
  editMessage: async (ctx) => {
@@ -99296,14 +99669,14 @@ if (isGatewayMain)
99296
99669
  var inboundSpool;
99297
99670
  if (isGatewayMain)
99298
99671
  inboundSpool = STATIC ? undefined : createInboundSpool({
99299
- path: join60(STATE_DIR, "inbound-spool.jsonl"),
99672
+ path: join61(STATE_DIR, "inbound-spool.jsonl"),
99300
99673
  fs: {
99301
- appendFileSync: (p, d) => appendFileSync7(p, d),
99302
- readFileSync: (p) => readFileSync58(p, "utf8"),
99303
- writeFileSync: (p, d) => writeFileSync48(p, d),
99304
- renameSync: (a, b) => renameSync21(a, b),
99305
- existsSync: (p) => existsSync54(p),
99306
- statSizeSync: (p) => statSync19(p).size
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),
99679
+ statSizeSync: (p) => statSync20(p).size
99307
99680
  },
99308
99681
  onDegraded: (info) => {
99309
99682
  process.stderr.write(`telegram gateway: inbound-spool durability ${info.degraded ? "DEGRADED to in-memory-only" : "RECOVERED"} path=${info.path} consecutiveFailures=${info.consecutiveFailures}${info.error != null ? ` error=${info.error}` : ""}
@@ -99364,13 +99737,13 @@ async function maybeRedeliverUndeliveredAnswer() {
99364
99737
  let transcriptText;
99365
99738
  try {
99366
99739
  const projectsDir = getProjectsDirForCwd();
99367
- const path2 = join60(projectsDir, `${sessionId}.jsonl`);
99368
- if (!existsSync54(path2)) {
99740
+ const path2 = join61(projectsDir, `${sessionId}.jsonl`);
99741
+ if (!existsSync55(path2)) {
99369
99742
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path2}); skipping
99370
99743
  `);
99371
99744
  return;
99372
99745
  }
99373
- transcriptText = readFileSync58(path2, "utf8");
99746
+ transcriptText = readFileSync59(path2, "utf8");
99374
99747
  } catch (err) {
99375
99748
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
99376
99749
  `);
@@ -99467,7 +99840,7 @@ if (isGatewayMain && inboundSpool != null) {
99467
99840
  }
99468
99841
  var pendingPermissionBuffer = createPendingPermissionBuffer();
99469
99842
  function buildPermissionActionRow(requestId, showAlways) {
99470
- const kb = new import_grammy15.InlineKeyboard().text("\u274C Deny", `perm:deny:${requestId}`).text("\u2705 Allow", `perm:allow:${requestId}`);
99843
+ const kb = new import_grammy16.InlineKeyboard().text("\u274C Deny", `perm:deny:${requestId}`).text("\u2705 Allow", `perm:allow:${requestId}`);
99471
99844
  if (showAlways)
99472
99845
  kb.text("\uD83D\uDD01 Always\u2026", `perm:always:${requestId}`);
99473
99846
  return kb;
@@ -99529,8 +99902,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
99529
99902
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
99530
99903
  isShuttingDown: () => shuttingDown,
99531
99904
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
99532
- crashLogPath: join60(STATE_DIR, "bridge-crash.log"),
99533
- markerPath: join60(STATE_DIR, "bridge-dead-escalation.json"),
99905
+ crashLogPath: join61(STATE_DIR, "bridge-crash.log"),
99906
+ markerPath: join61(STATE_DIR, "bridge-dead-escalation.json"),
99534
99907
  log: (line) => process.stderr.write(`${line}
99535
99908
  `),
99536
99909
  priorStreak: bridgeDeadPriorStreak,
@@ -99636,8 +100009,8 @@ if (isGatewayMain)
99636
100009
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
99637
100010
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
99638
100011
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
99639
- configSnapshotPath: join60(resolvedAgentDirForCard, ".config-snapshot.json"),
99640
- bootCardStatePath: join60(resolvedAgentDirForCard, ".boot-card-msgid.json"),
100012
+ configSnapshotPath: join61(resolvedAgentDirForCard, ".config-snapshot.json"),
100013
+ bootCardStatePath: join61(resolvedAgentDirForCard, ".boot-card-msgid.json"),
99641
100014
  floodStatePath: FLOOD_STATE_PATH,
99642
100015
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
99643
100016
  }, ackMsgId).then((handle) => {
@@ -100041,7 +100414,7 @@ if (isGatewayMain)
100041
100414
  }
100042
100415
  },
100043
100416
  postAttachment: async (args) => {
100044
- const input = new import_grammy15.InputFile(Buffer.from(args.content, "utf8"), args.filename);
100417
+ const input = new import_grammy16.InputFile(Buffer.from(args.content, "utf8"), args.filename);
100045
100418
  await robustApiCall(() => bot.api.sendDocument(args.chatId, input, {
100046
100419
  ...args.threadId !== undefined ? { message_thread_id: args.threadId } : {}
100047
100420
  }), {
@@ -100319,7 +100692,7 @@ if (isGatewayMain)
100319
100692
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
100320
100693
  if (Number.isInteger(receiverUid))
100321
100694
  allowedUids.push(receiverUid);
100322
- const socketPath = join60(STATE_DIR, "webhook.sock");
100695
+ const socketPath = join61(STATE_DIR, "webhook.sock");
100323
100696
  const webhookInject = (agentName3, inbound) => {
100324
100697
  const msg = inbound;
100325
100698
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -100547,9 +100920,9 @@ function redactOutboundText(text5, site) {
100547
100920
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
100548
100921
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
100549
100922
  var voiceOnDemandCache = new VoiceOnDemandCache({
100550
- persistPath: join60(STATE_DIR, "voice-ondemand.json")
100923
+ persistPath: join61(STATE_DIR, "voice-ondemand.json")
100551
100924
  });
100552
- var VOICE_CACHE_DIR = join60(STATE_DIR, "voice-cache");
100925
+ var VOICE_CACHE_DIR = join61(STATE_DIR, "voice-cache");
100553
100926
  var voicePreSynthQueue = new PreSynthQueue({
100554
100927
  runJob: async (job) => {
100555
100928
  const sidecarToken = await materializeSidecarToken2();
@@ -100816,7 +101189,7 @@ async function executeAskUser(rawArgs) {
100816
101189
  }
100817
101190
  }
100818
101191
  const askId = generateAskId();
100819
- const keyboard = new import_grammy15.InlineKeyboard;
101192
+ const keyboard = new import_grammy16.InlineKeyboard;
100820
101193
  for (let i = 0;i < args.options.length; i++) {
100821
101194
  keyboard.text(args.options[i], encodeAskCallback(askId, i));
100822
101195
  if (i < args.options.length - 1)
@@ -100951,11 +101324,11 @@ async function executeSendGif(rawArgs) {
100951
101324
  };
100952
101325
  }
100953
101326
  async function publishToTelegraph(text5, shortName, authorName) {
100954
- const accountPath = join60(STATE_DIR, "telegraph-account.json");
101327
+ const accountPath = join61(STATE_DIR, "telegraph-account.json");
100955
101328
  let account = null;
100956
101329
  try {
100957
- if (existsSync54(accountPath)) {
100958
- const raw = readFileSync58(accountPath, "utf-8");
101330
+ if (existsSync55(accountPath)) {
101331
+ const raw = readFileSync59(accountPath, "utf-8");
100959
101332
  const parsed = JSON.parse(raw);
100960
101333
  if (parsed.shortName && parsed.accessToken) {
100961
101334
  account = parsed;
@@ -100974,8 +101347,8 @@ async function publishToTelegraph(text5, shortName, authorName) {
100974
101347
  }
100975
101348
  account = created.value;
100976
101349
  try {
100977
- mkdirSync47(STATE_DIR, { recursive: true, mode: 448 });
100978
- writeFileSync48(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
101350
+ mkdirSync48(STATE_DIR, { recursive: true, mode: 448 });
101351
+ writeFileSync49(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
100979
101352
  } catch (err) {
100980
101353
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
100981
101354
  `);
@@ -101091,7 +101464,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
101091
101464
  }
101092
101465
  function readLiveSwitchroomConfigText() {
101093
101466
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
101094
- return readFileSync58(cfgPath, "utf8");
101467
+ return readFileSync59(cfgPath, "utf8");
101095
101468
  }
101096
101469
  async function executeReact(args) {
101097
101470
  if (!args.chat_id)
@@ -101130,9 +101503,9 @@ async function executeDownloadAttachment(args) {
101130
101503
  fileUniqueId: file.file_unique_id,
101131
101504
  now: Date.now()
101132
101505
  });
101133
- mkdirSync47(INBOX_DIR, { recursive: true, mode: 448 });
101506
+ mkdirSync48(INBOX_DIR, { recursive: true, mode: 448 });
101134
101507
  assertInsideInbox2(INBOX_DIR, dlPath);
101135
- writeFileSync48(dlPath, buf, { mode: 384 });
101508
+ writeFileSync49(dlPath, buf, { mode: 384 });
101136
101509
  return { content: [{ type: "text", text: dlPath }] };
101137
101510
  }
101138
101511
  async function executeEditMessage(args) {
@@ -101146,23 +101519,20 @@ async function executeEditMessage(args) {
101146
101519
  const editAccess = loadAccess();
101147
101520
  const editFormat = args.format ?? (editAccess.parseMode ?? "html");
101148
101521
  const editLiteralText = editFormat === "text";
101149
- let editRawText = repairEscapedWhitespace2(args.text);
101150
- if (!editLiteralText)
101151
- editRawText = normalizeParagraphBreaks2(editRawText);
101152
- editRawText = redactOutboundText(editRawText, "edit_message");
101153
- if (!editLiteralText)
101154
- editRawText = addParagraphSpacers2(stripExcessBold2(normalizePunctuation2(editRawText)));
101155
- {
101156
- const scrub = scrubVoice2(editRawText);
101157
- if (scrub.replaced > 0) {
101158
- editRawText = scrub.scrubbed;
101159
- emitRuntimeMetric({
101160
- kind: "voice_scrub_applied",
101161
- chatKey: statusKey(String(args.chat_id ?? ""), undefined),
101162
- replaced: scrub.replaced,
101163
- site: "edit_message"
101164
- });
101165
- }
101522
+ const _editNorm = normalizeOutboundBody(args.text, "edit_message", redactOutboundText, {
101523
+ literalText: editLiteralText,
101524
+ addSpacers: !editLiteralText,
101525
+ tz: resolveEnvTimezone2(),
101526
+ nowMs: Date.now()
101527
+ });
101528
+ let editRawText = _editNorm.text;
101529
+ if (_editNorm.voiceReplaced > 0) {
101530
+ emitRuntimeMetric({
101531
+ kind: "voice_scrub_applied",
101532
+ chatKey: statusKey(String(args.chat_id ?? ""), undefined),
101533
+ replaced: _editNorm.voiceReplaced,
101534
+ site: "edit_message"
101535
+ });
101166
101536
  }
101167
101537
  const edited = await robustApiCall(() => lockedBot.api.editMessageText(String(args.chat_id ?? ""), Number(args.message_id), editLiteralText ? editRawText : richMessage2(editRawText)));
101168
101538
  const id = typeof edited === "object" && edited ? edited.message_id : args.message_id;
@@ -102495,14 +102865,14 @@ function restartMarkerPath() {
102495
102865
  const agentDir = resolveAgentDirFromEnv();
102496
102866
  if (!agentDir)
102497
102867
  return null;
102498
- return join60(agentDir, "restart-pending.json");
102868
+ return join61(agentDir, "restart-pending.json");
102499
102869
  }
102500
102870
  function writeRestartMarker(marker) {
102501
102871
  const p = restartMarkerPath();
102502
102872
  if (!p)
102503
102873
  return;
102504
102874
  try {
102505
- writeFileSync48(p, JSON.stringify(marker));
102875
+ writeFileSync49(p, JSON.stringify(marker));
102506
102876
  lastPlannedRestartAt = Date.now();
102507
102877
  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}
102508
102878
  `);
@@ -102521,7 +102891,7 @@ function readRestartMarker() {
102521
102891
  if (!p)
102522
102892
  return null;
102523
102893
  try {
102524
- return JSON.parse(readFileSync58(p, "utf8"));
102894
+ return JSON.parse(readFileSync59(p, "utf8"));
102525
102895
  } catch {
102526
102896
  return null;
102527
102897
  }
@@ -102670,7 +103040,7 @@ var _dockerReachable;
102670
103040
  function isDockerReachable() {
102671
103041
  if (_dockerReachable !== undefined)
102672
103042
  return _dockerReachable;
102673
- if (!existsSync54("/var/run/docker.sock")) {
103043
+ if (!existsSync55("/var/run/docker.sock")) {
102674
103044
  _dockerReachable = false;
102675
103045
  return _dockerReachable;
102676
103046
  }
@@ -102687,12 +103057,12 @@ function _resetDockerReachableCache() {
102687
103057
  }
102688
103058
  function spawnSwitchroomDetached(args, onFailure) {
102689
103059
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
102690
- const logPath = join60(STATE_DIR, "detached-spawn.log");
103060
+ const logPath = join61(STATE_DIR, "detached-spawn.log");
102691
103061
  let outFd = null;
102692
103062
  try {
102693
- mkdirSync47(STATE_DIR, { recursive: true });
103063
+ mkdirSync48(STATE_DIR, { recursive: true });
102694
103064
  outFd = openSync12(logPath, "a");
102695
- writeFileSync48(logPath, `
103065
+ writeFileSync49(logPath, `
102696
103066
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
102697
103067
  `, { flag: "a" });
102698
103068
  } catch {}
@@ -102718,7 +103088,7 @@ function spawnSwitchroomDetached(args, onFailure) {
102718
103088
  return;
102719
103089
  let tail = "";
102720
103090
  try {
102721
- const full = readFileSync58(logPath, "utf8");
103091
+ const full = readFileSync59(logPath, "utf8");
102722
103092
  tail = full.split(`
102723
103093
  `).slice(-30).join(`
102724
103094
  `).trim();
@@ -102980,10 +103350,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
102980
103350
  }
102981
103351
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
102982
103352
  try {
102983
- const auditPath = join60(homedir19(), ".switchroom", "vault-audit.log");
102984
- if (!existsSync54(auditPath))
103353
+ const auditPath = join61(homedir20(), ".switchroom", "vault-audit.log");
103354
+ if (!existsSync55(auditPath))
102985
103355
  return [];
102986
- const raw = readFileSync58(auditPath, "utf8");
103356
+ const raw = readFileSync59(auditPath, "utf8");
102987
103357
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
102988
103358
  } catch {
102989
103359
  return [];
@@ -103034,7 +103404,7 @@ async function buildAgentMetadata(agentName3) {
103034
103404
  try {
103035
103405
  const agentDir = resolveAgentDirFromEnv();
103036
103406
  if (agentDir) {
103037
- const raw = readFileSync58(join60(agentDir, ".claude", ".claude.json"), "utf8");
103407
+ const raw = readFileSync59(join61(agentDir, ".claude", ".claude.json"), "utf8");
103038
103408
  claudeJson = JSON.parse(raw);
103039
103409
  }
103040
103410
  } catch {}
@@ -103163,7 +103533,7 @@ function buildModelDeps(restartCtx) {
103163
103533
  try {
103164
103534
  const agentDir = resolveAgentDirFromEnv();
103165
103535
  if (agentDir) {
103166
- const local = await fetchQuota2({ claudeConfigDir: join60(agentDir, ".claude") });
103536
+ const local = await fetchQuota2({ claudeConfigDir: join61(agentDir, ".claude") });
103167
103537
  if (local.ok)
103168
103538
  return formatQuotaLine2(local.data);
103169
103539
  }
@@ -103258,7 +103628,7 @@ function buildModelDeps(restartCtx) {
103258
103628
  function modelMenuReplyMarkup(reply) {
103259
103629
  if (!reply.keyboard)
103260
103630
  return;
103261
- const kb = new import_grammy15.InlineKeyboard;
103631
+ const kb = new import_grammy16.InlineKeyboard;
103262
103632
  for (const row of reply.keyboard) {
103263
103633
  for (const btn of row)
103264
103634
  kb.text(btn.text, btn.callback_data);
@@ -103398,7 +103768,7 @@ function buildEffortDeps() {
103398
103768
  function effortMenuReplyMarkup(reply) {
103399
103769
  if (!reply.keyboard)
103400
103770
  return;
103401
- const kb = new import_grammy15.InlineKeyboard;
103771
+ const kb = new import_grammy16.InlineKeyboard;
103402
103772
  for (const row of reply.keyboard) {
103403
103773
  for (const btn of row)
103404
103774
  kb.text(btn.text, btn.callback_data);
@@ -103409,10 +103779,10 @@ function effortMenuReplyMarkup(reply) {
103409
103779
  function flushAgentHandoff(agentDir) {
103410
103780
  let removed = 0;
103411
103781
  for (const fname of [".handoff.md", ".handoff-topic"]) {
103412
- const p = join60(agentDir, fname);
103782
+ const p = join61(agentDir, fname);
103413
103783
  try {
103414
- if (existsSync54(p)) {
103415
- unlinkSync26(p);
103784
+ if (existsSync55(p)) {
103785
+ unlinkSync27(p);
103416
103786
  removed++;
103417
103787
  }
103418
103788
  } catch (err) {
@@ -103467,7 +103837,7 @@ async function handleNewCommand(ctx) {
103467
103837
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
103468
103838
  if (agentDir != null) {
103469
103839
  try {
103470
- writeFileSync48(join60(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
103840
+ writeFileSync49(join61(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
103471
103841
  `, "utf8");
103472
103842
  } catch (err) {
103473
103843
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -103580,16 +103950,16 @@ function buildFolderPickerDeps() {
103580
103950
  };
103581
103951
  }
103582
103952
  var lockoutOps = {
103583
- readFileSync: (p, enc) => readFileSync58(p, enc),
103584
- writeFileSync: (p, data, opts) => writeFileSync48(p, data, opts),
103585
- existsSync: (p) => existsSync54(p),
103586
- mkdirSync: (p, opts) => mkdirSync47(p, opts),
103587
- joinPath: (...parts) => join60(...parts)
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)
103588
103958
  };
103589
103959
  var FLEET_FALLBACK_DEDUP_MS = 30000;
103590
103960
  function isAuthBrokerSocketReachable() {
103591
103961
  try {
103592
- return existsSync54(resolveAuthBrokerSocketPath2());
103962
+ return existsSync55(resolveAuthBrokerSocketPath2());
103593
103963
  } catch {
103594
103964
  return false;
103595
103965
  }
@@ -103844,7 +104214,7 @@ async function runCreditWatch() {
103844
104214
  if (!agentDir)
103845
104215
  return;
103846
104216
  const agentName3 = getMyAgentName();
103847
- const claudeConfigDir = join60(agentDir, ".claude");
104217
+ const claudeConfigDir = join61(agentDir, ".claude");
103848
104218
  const stateDir = STATE_DIR;
103849
104219
  const reason = readClaudeJsonOverage(claudeConfigDir);
103850
104220
  const prev = loadCreditState(stateDir);
@@ -105051,7 +105421,7 @@ _Google accounts are connected from the host CLI._`, { html: true });
105051
105421
  return;
105052
105422
  }
105053
105423
  const appNote = started.source === "default" ? "_Using switchroom's shipped Microsoft app._" : "_Using your configured Microsoft app._";
105054
- const keyboard = new import_grammy15.InlineKeyboard().url("\uD83D\uDD10 Sign in to Microsoft", started.device.verification_uri).row().text("\u2716 Cancel", `cn:cancel:${key}`);
105424
+ const keyboard = new import_grammy16.InlineKeyboard().url("\uD83D\uDD10 Sign in to Microsoft", started.device.verification_uri).row().text("\u2716 Cancel", `cn:cancel:${key}`);
105055
105425
  const sent = await ctx.replyWithRichMessage(richMessage2(`\uD83D\uDD17 **Connect a Microsoft account**
105056
105426
 
105057
105427
  ` + `1. Tap **Sign in to Microsoft** below.
@@ -105187,7 +105557,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105187
105557
  tz: process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ
105188
105558
  });
105189
105559
  if (reply.keyboard && reply.keyboard.length > 0) {
105190
- const kb = new import_grammy15.InlineKeyboard;
105560
+ const kb = new import_grammy16.InlineKeyboard;
105191
105561
  for (let i = 0;i < reply.keyboard.length; i++) {
105192
105562
  const row = reply.keyboard[i];
105193
105563
  for (const b of row) {
@@ -105329,7 +105699,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105329
105699
  }
105330
105700
  lines.push("");
105331
105701
  const denials = readRecentDenialsForAgent(targetAgent, 604800000, 5);
105332
- const denialKeyboard = new import_grammy15.InlineKeyboard;
105702
+ const denialKeyboard = new import_grammy16.InlineKeyboard;
105333
105703
  if (denials.length > 0) {
105334
105704
  lines.push("**Recent denials (last 7d):**");
105335
105705
  for (const d of denials) {
@@ -105377,7 +105747,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105377
105747
  byAgent.set(g.agent_slug, list3);
105378
105748
  }
105379
105749
  const lines = ["**\uD83D\uDCDC Active grants**", ""];
105380
- const keyboard = new import_grammy15.InlineKeyboard;
105750
+ const keyboard = new import_grammy16.InlineKeyboard;
105381
105751
  for (const [agentName3, agentGrants] of byAgent) {
105382
105752
  lines.push(`**${escapeHtmlForTg2(agentName3)}:**`);
105383
105753
  for (const g of agentGrants) {
@@ -105574,7 +105944,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105574
105944
  if (ctx.chat?.type !== "private") {
105575
105945
  kbRows = kbRows.filter((row) => !row.some((b) => b.callbackData?.startsWith("auth:use:")));
105576
105946
  }
105577
- const keyboard = new import_grammy15.InlineKeyboard;
105947
+ const keyboard = new import_grammy16.InlineKeyboard;
105578
105948
  kbRows.forEach((row, ri) => {
105579
105949
  if (ri > 0)
105580
105950
  keyboard.row();
@@ -105600,7 +105970,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
105600
105970
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
105601
105971
  return;
105602
105972
  }
105603
- const result = await fetchQuota2({ claudeConfigDir: join60(agentDir, ".claude") });
105973
+ const result = await fetchQuota2({ claudeConfigDir: join61(agentDir, ".claude") });
105604
105974
  if (!result.ok) {
105605
105975
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
105606
105976
  return;
@@ -105926,7 +106296,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
105926
106296
  await ctx.answerCallbackQuery({ text: "No always-allow rule for this tool." }).catch(() => {});
105927
106297
  return;
105928
106298
  }
105929
- keyboard = new import_grammy15.InlineKeyboard().text("\u2190 Back", `perm:back:${request_id}`);
106299
+ keyboard = new import_grammy16.InlineKeyboard().text("\u2190 Back", `perm:back:${request_id}`);
105930
106300
  if (choices.specific)
105931
106301
  keyboard.text(choices.specific.buttonLabel, `perm:asn:${request_id}`);
105932
106302
  keyboard.text(`${choices.broad.buttonLabel} \u26A0\uFE0F`, `perm:asb:${request_id}`);
@@ -105986,7 +106356,7 @@ ${interimLabel}` : interimLabel
105986
106356
  const unifiedDiff = (() => {
105987
106357
  try {
105988
106358
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
105989
- const raw = readFileSync58(cfgPath, "utf8");
106359
+ const raw = readFileSync59(cfgPath, "utf8");
105990
106360
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
105991
106361
  } catch (err) {
105992
106362
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -106228,7 +106598,7 @@ ${labelWithResume}` : labelWithResume,
106228
106598
  verb: "voice-ondemand.sendVoice",
106229
106599
  ...threadId != null ? { threadId } : {}
106230
106600
  }),
106231
- sendVoiceByUpload: (chatId, audio, sendOpts, threadId) => robustApiCall(() => bot2.api.sendVoice(chatId, new import_grammy15.InputFile(Buffer.from(audio)), sendOpts), {
106601
+ sendVoiceByUpload: (chatId, audio, sendOpts, threadId) => robustApiCall(() => bot2.api.sendVoice(chatId, new import_grammy16.InputFile(Buffer.from(audio)), sendOpts), {
106232
106602
  chat_id: chatId,
106233
106603
  verb: "voice-ondemand.sendVoice",
106234
106604
  ...threadId != null ? { threadId } : {}
@@ -106487,7 +106857,7 @@ async function initGatewayBot() {
106487
106857
  `);
106488
106858
  process.exit(1);
106489
106859
  }
106490
- bot = new import_grammy15.Bot(TOKEN);
106860
+ bot = new import_grammy16.Bot(TOKEN);
106491
106861
  installTgPostLogger(bot);
106492
106862
  installRichMarkdownGuard(bot);
106493
106863
  installUpdateTap(bot, (line) => process.stderr.write(line));
@@ -106842,7 +107212,7 @@ async function startGateway() {
106842
107212
  return;
106843
107213
  }
106844
107214
  })();
106845
- const resolvedAgentDirForBootCard = agentDir ?? join60(homedir19(), ".switchroom", "agents", agentSlug);
107215
+ const resolvedAgentDirForBootCard = agentDir ?? join61(homedir20(), ".switchroom", "agents", agentSlug);
106846
107216
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
106847
107217
  agentName: agentDisplayName,
106848
107218
  agentSlug,
@@ -106856,8 +107226,8 @@ async function startGateway() {
106856
107226
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
106857
107227
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
106858
107228
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
106859
- configSnapshotPath: join60(resolvedAgentDirForBootCard, ".config-snapshot.json"),
106860
- bootCardStatePath: join60(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
107229
+ configSnapshotPath: join61(resolvedAgentDirForBootCard, ".config-snapshot.json"),
107230
+ bootCardStatePath: join61(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
106861
107231
  floodStatePath: FLOOD_STATE_PATH,
106862
107232
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
106863
107233
  }, ackMsgId);
@@ -106888,10 +107258,10 @@ async function startGateway() {
106888
107258
  try {
106889
107259
  const smAgentDir = resolveAgentDirFromEnv();
106890
107260
  if (smAgentDir) {
106891
- const activePath = join60(smAgentDir, ".active-session-model");
106892
- if (existsSync54(activePath)) {
107261
+ const activePath = join61(smAgentDir, ".active-session-model");
107262
+ if (existsSync55(activePath)) {
106893
107263
  try {
106894
- const launched = readFileSync58(activePath, "utf8").trim();
107264
+ const launched = readFileSync59(activePath, "utf8").trim();
106895
107265
  const configured = (() => {
106896
107266
  const d = switchroomExecJson(["agent", "list"]);
106897
107267
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -106924,29 +107294,29 @@ async function startGateway() {
106924
107294
  deliverModelSwitchBootNotice({
106925
107295
  ...modelBootCardDeps,
106926
107296
  confirmation,
106927
- hasSessionModelAlert: existsSync54(join60(smAgentDir, ".session-model-alert"))
107297
+ hasSessionModelAlert: existsSync55(join61(smAgentDir, ".session-model-alert"))
106928
107298
  });
106929
107299
  }
106930
107300
  } catch {}
106931
107301
  }
106932
- const activeEffortPath = join60(smAgentDir, ".active-session-effort");
106933
- if (existsSync54(activeEffortPath)) {
107302
+ const activeEffortPath = join61(smAgentDir, ".active-session-effort");
107303
+ if (existsSync55(activeEffortPath)) {
106934
107304
  try {
106935
- const launchedEffort = readFileSync58(activeEffortPath, "utf8").trim();
107305
+ const launchedEffort = readFileSync59(activeEffortPath, "utf8").trim();
106936
107306
  const configuredEffort = getConfiguredEffortForPersist();
106937
107307
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
106938
107308
  } catch {}
106939
107309
  }
106940
- const alertPath = join60(smAgentDir, ".session-model-alert");
106941
- if (existsSync54(alertPath)) {
107310
+ const alertPath = join61(smAgentDir, ".session-model-alert");
107311
+ if (existsSync55(alertPath)) {
106942
107312
  let alertText = null;
106943
107313
  try {
106944
- alertText = readFileSync58(alertPath, "utf8").trim();
107314
+ alertText = readFileSync59(alertPath, "utf8").trim();
106945
107315
  } catch {
106946
107316
  alertText = null;
106947
107317
  }
106948
107318
  try {
106949
- unlinkSync26(alertPath);
107319
+ unlinkSync27(alertPath);
106950
107320
  } catch {}
106951
107321
  if (alertText && alertText.length > 0) {
106952
107322
  const operators = loadAccess().allowFrom;
@@ -107442,7 +107812,7 @@ async function startGateway() {
107442
107812
  await runnerHandle.task();
107443
107813
  return;
107444
107814
  } catch (err) {
107445
- if (err instanceof import_grammy15.GrammyError && err.error_code === 409) {
107815
+ if (err instanceof import_grammy16.GrammyError && err.error_code === 409) {
107446
107816
  const delay = Math.min(1000 * attempt, 15000);
107447
107817
  const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
107448
107818
  process.stderr.write(`telegram gateway: poll.409.detected attempt=${attempt} retry_in_ms=${delay} agent=${agentName3}