switchroom 0.21.9 → 0.21.11

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 (46) hide show
  1. package/dist/cli/switchroom.js +143 -27
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +3 -2
  4. package/telegram-plugin/dist/gateway/gateway.js +1152 -296
  5. package/telegram-plugin/format.ts +74 -1
  6. package/telegram-plugin/gateway/answer-route-overrides.ts +163 -0
  7. package/telegram-plugin/gateway/answer-thread-resolve.test.ts +175 -1
  8. package/telegram-plugin/gateway/answer-thread-resolve.ts +58 -6
  9. package/telegram-plugin/gateway/escalation-staleness.ts +526 -0
  10. package/telegram-plugin/gateway/gateway.ts +61 -68
  11. package/telegram-plugin/gateway/obligation-wiring.ts +91 -3
  12. package/telegram-plugin/gateway/outbound-send-path.ts +62 -1
  13. package/telegram-plugin/gateway/reply-route-log.test.ts +134 -0
  14. package/telegram-plugin/gateway/reply-route-log.ts +118 -0
  15. package/telegram-plugin/gateway/speech-capture.ts +158 -0
  16. package/telegram-plugin/gateway/stream-render.ts +1 -1
  17. package/telegram-plugin/history.ts +21 -0
  18. package/telegram-plugin/registry/subagents-bugs.test.ts +3 -3
  19. package/telegram-plugin/render/html-fold.ts +372 -0
  20. package/telegram-plugin/render/parse.ts +578 -29
  21. package/telegram-plugin/render/render.ts +14 -13
  22. package/telegram-plugin/tests/answer-route-side-effect.test.ts +111 -0
  23. package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +3 -3
  24. package/telegram-plugin/tests/escalation-staleness.test.ts +1275 -0
  25. package/telegram-plugin/tests/forwarded-rich-message-coalesce.test.ts +6 -6
  26. package/telegram-plugin/tests/forwarded-rich-message.test.ts +8 -8
  27. package/telegram-plugin/tests/history.test.ts +78 -0
  28. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +29 -1
  29. package/telegram-plugin/tests/orphaned-db-sweep.test.ts +17 -1
  30. package/telegram-plugin/tests/render/html-dialect-content-loss.test.ts +326 -0
  31. package/telegram-plugin/tests/render/html-dialect.test.ts +283 -0
  32. package/telegram-plugin/tests/render/parse.test.ts +9 -5
  33. package/telegram-plugin/tests/send-reply-golden.test.ts +290 -3
  34. package/telegram-plugin/tests/speech-capture.test.ts +296 -0
  35. package/telegram-plugin/tests/status-pin.test.ts +2 -2
  36. package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +2 -2
  37. package/telegram-plugin/tests/subagent-progress-inbound-builder.test.ts +2 -2
  38. package/telegram-plugin/tests/telegram-format.test.ts +52 -0
  39. package/telegram-plugin/tests/tts-normalize.test.ts +114 -0
  40. package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +1 -1
  41. package/telegram-plugin/tests/voice-normalize-text.test.ts +89 -0
  42. package/telegram-plugin/tests/worker-origin-gap-dispatch.test.ts +1 -1
  43. package/telegram-plugin/tts-normalize.ts +47 -9
  44. package/telegram-plugin/uat/scenarios/jtbd-supergroup-reply-channel.test.ts +1 -1
  45. package/telegram-plugin/voice-normalize-text.ts +48 -9
  46. package/telegram-plugin/worker-activity-feed.ts +2 -2
@@ -6769,8 +6769,22 @@ function escapeMarkdown(text) {
6769
6769
  function codeSpanSafe(s) {
6770
6770
  return s.replace(/`/g, "`\u200b");
6771
6771
  }
6772
+ function codeFenceFor(text) {
6773
+ let longestRun = 0;
6774
+ let current = 0;
6775
+ for (let i = 0;i < text.length; i++) {
6776
+ if (text[i] === "`") {
6777
+ current++;
6778
+ if (current > longestRun)
6779
+ longestRun = current;
6780
+ } else {
6781
+ current = 0;
6782
+ }
6783
+ }
6784
+ return "`".repeat(Math.max(3, longestRun + 1));
6785
+ }
6772
6786
  function escapeLinkHref(href) {
6773
- return href.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
6787
+ return href.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)").replace(/[\x00-\x20\x7f]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
6774
6788
  }
6775
6789
  function repairEscapedWhitespace(text) {
6776
6790
  if (!/\\[nrt"\\]/.test(text))
@@ -30173,7 +30187,7 @@ __export(exports_history, {
30173
30187
  MIN_PREFIX_MATCH_CHARS: () => MIN_PREFIX_MATCH_CHARS2
30174
30188
  });
30175
30189
  import { chmodSync as chmodSync9, existsSync as existsSync29 } from "fs";
30176
- import { join as join37 } from "path";
30190
+ import { join as join38 } from "path";
30177
30191
  function loadDatabaseClass2() {
30178
30192
  if (DatabaseClass2 != null)
30179
30193
  return DatabaseClass2;
@@ -30231,7 +30245,7 @@ function initHistory2(stateDir, retentionDays = 30) {
30231
30245
  return;
30232
30246
  const Database = loadDatabaseClass2();
30233
30247
  mkdirStateSync(stateDir, { recursive: true, mode: 448 });
30234
- const path2 = join37(stateDir, "history.db");
30248
+ const path2 = join38(stateDir, "history.db");
30235
30249
  dbPath2 = path2;
30236
30250
  db2 = new Database(path2, { create: true });
30237
30251
  db2.exec("PRAGMA journal_mode = WAL");
@@ -30565,12 +30579,16 @@ function getRecentOutboundCount(chatId, withinSeconds) {
30565
30579
  const row = prep2("SELECT COUNT(*) as cnt FROM messages WHERE chat_id = ? AND role = ? AND ts >= ?").get(chatId, "assistant", cutoff);
30566
30580
  return row?.cnt ?? 0;
30567
30581
  }
30568
- function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200) {
30582
+ function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200, untilMs) {
30569
30583
  try {
30570
30584
  const cutoffSec = Math.floor(sinceMs / 1000);
30571
30585
  const minLen = Math.max(1, Math.floor(minChars));
30572
30586
  const params = [chatId, cutoffSec, minLen];
30573
30587
  let sql = "SELECT 1 FROM messages WHERE chat_id = ? AND role = 'assistant' AND ts >= ? AND LENGTH(text) >= ?";
30588
+ if (untilMs != null && Number.isFinite(untilMs)) {
30589
+ sql += " AND ts <= ?";
30590
+ params.push(Math.ceil(untilMs / 1000));
30591
+ }
30574
30592
  if (threadId !== undefined) {
30575
30593
  if (threadId === null) {
30576
30594
  sql += " AND thread_id IS NULL";
@@ -30685,7 +30703,7 @@ var init_turn_flush_suppression = () => {};
30685
30703
  // ../src/agents/generation-stamp.ts
30686
30704
  import { createHash as createHash6 } from "node:crypto";
30687
30705
  import { existsSync as existsSync36, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "node:fs";
30688
- import { join as join44 } from "node:path";
30706
+ import { join as join45 } from "node:path";
30689
30707
  function sha256Text(s) {
30690
30708
  return createHash6("sha256").update(s, "utf-8").digest("hex");
30691
30709
  }
@@ -30695,7 +30713,7 @@ function hashManagedClaudeMd(content3) {
30695
30713
  return sha256Text(managed);
30696
30714
  }
30697
30715
  function hashStampedFileOnDisk(agentDir, rel) {
30698
- const p = join44(agentDir, rel);
30716
+ const p = join45(agentDir, rel);
30699
30717
  if (!existsSync36(p))
30700
30718
  return null;
30701
30719
  let content3;
@@ -30707,7 +30725,7 @@ function hashStampedFileOnDisk(agentDir, rel) {
30707
30725
  return rel === "CLAUDE.md" ? hashManagedClaudeMd(content3) : sha256Text(content3);
30708
30726
  }
30709
30727
  function readGenerationStamp(agentDir) {
30710
- const p = join44(agentDir, GENERATION_STAMP_FILE);
30728
+ const p = join45(agentDir, GENERATION_STAMP_FILE);
30711
30729
  if (!existsSync36(p))
30712
30730
  return null;
30713
30731
  try {
@@ -33711,7 +33729,7 @@ var require_util = __commonJS((exports2) => {
33711
33729
  return path2;
33712
33730
  }
33713
33731
  exports2.normalize = normalize;
33714
- function join46(aRoot, aPath) {
33732
+ function join47(aRoot, aPath) {
33715
33733
  if (aRoot === "") {
33716
33734
  aRoot = ".";
33717
33735
  }
@@ -33743,7 +33761,7 @@ var require_util = __commonJS((exports2) => {
33743
33761
  }
33744
33762
  return joined;
33745
33763
  }
33746
- exports2.join = join46;
33764
+ exports2.join = join47;
33747
33765
  exports2.isAbsolute = function(aPath) {
33748
33766
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
33749
33767
  };
@@ -33916,7 +33934,7 @@ var require_util = __commonJS((exports2) => {
33916
33934
  parsed.path = parsed.path.substring(0, index2 + 1);
33917
33935
  }
33918
33936
  }
33919
- sourceURL = join46(urlGenerate(parsed), sourceURL);
33937
+ sourceURL = join47(urlGenerate(parsed), sourceURL);
33920
33938
  }
33921
33939
  return normalize(sourceURL);
33922
33940
  }
@@ -36522,9 +36540,9 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
36522
36540
 
36523
36541
  // gateway/quota-cache.ts
36524
36542
  import { existsSync as existsSync49, readFileSync as readFileSync49, writeFileSync as writeFileSync41, mkdirSync as mkdirSync42 } from "fs";
36525
- import { join as join59, dirname as dirname25 } from "path";
36543
+ import { join as join60, dirname as dirname25 } from "path";
36526
36544
  function defaultCachePath() {
36527
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join59(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
36545
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join60(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
36528
36546
  }
36529
36547
  function readQuotaCache(opts = {}) {
36530
36548
  const path3 = opts.path ?? defaultCachePath();
@@ -36571,7 +36589,7 @@ var init_quota_cache = __esm(() => {
36571
36589
 
36572
36590
  // gateway/boot-probes.ts
36573
36591
  import { readFileSync as readFileSync50, readdirSync as readdirSync14, existsSync as existsSync50 } from "fs";
36574
- import { join as join60 } from "path";
36592
+ import { join as join61 } from "path";
36575
36593
  import { execFile as execFileCb } from "child_process";
36576
36594
  import { promisify } from "util";
36577
36595
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -36613,8 +36631,8 @@ function mapPlan(billingType, hasExtra) {
36613
36631
  }
36614
36632
  async function probeAccount(agentDir) {
36615
36633
  return withTimeout("Account", (async () => {
36616
- const claudeDir = join60(agentDir, ".claude");
36617
- const claudeJsonPath = join60(claudeDir, ".claude.json");
36634
+ const claudeDir = join61(agentDir, ".claude");
36635
+ const claudeJsonPath = join61(claudeDir, ".claude.json");
36618
36636
  let cfg = {};
36619
36637
  try {
36620
36638
  const raw = readFileSync50(claudeJsonPath, "utf8");
@@ -36635,8 +36653,8 @@ async function probeAccount(agentDir) {
36635
36653
  let tokenStr = "";
36636
36654
  let status = "ok";
36637
36655
  for (const candidate of [
36638
- join60(claudeDir, ".oauth-token.meta.json"),
36639
- join60(claudeDir, "accounts", "default", ".oauth-token.meta.json")
36656
+ join61(claudeDir, ".oauth-token.meta.json"),
36657
+ join61(claudeDir, "accounts", "default", ".oauth-token.meta.json")
36640
36658
  ]) {
36641
36659
  if (existsSync50(candidate)) {
36642
36660
  try {
@@ -37022,9 +37040,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
37022
37040
  let claudeDirForProbe = null;
37023
37041
  for (const candidate of [
37024
37042
  claudeConfigDir,
37025
- join60(claudeConfigDir, "accounts", "default")
37043
+ join61(claudeConfigDir, "accounts", "default")
37026
37044
  ]) {
37027
- if (existsSync50(join60(candidate, ".oauth-token"))) {
37045
+ if (existsSync50(join61(candidate, ".oauth-token"))) {
37028
37046
  claudeDirForProbe = candidate;
37029
37047
  break;
37030
37048
  }
@@ -37259,7 +37277,7 @@ async function probeSkills(agentDir, opts = {}) {
37259
37277
  return withTimeout("Skills", (async () => {
37260
37278
  const fs3 = opts.fs ?? realSkillsFs;
37261
37279
  const max = opts.maxNamesShown ?? 3;
37262
- const skillsDir = join60(agentDir, ".claude", "skills");
37280
+ const skillsDir = join61(agentDir, ".claude", "skills");
37263
37281
  if (!fs3.exists(skillsDir)) {
37264
37282
  return { status: "ok", label: "Skills", detail: "no skills dir" };
37265
37283
  }
@@ -37274,17 +37292,17 @@ async function probeSkills(agentDir, opts = {}) {
37274
37292
  }
37275
37293
  const dangling = [];
37276
37294
  for (const name of entries) {
37277
- const skillPath = join60(skillsDir, name);
37295
+ const skillPath = join61(skillsDir, name);
37278
37296
  if (!fs3.exists(skillPath)) {
37279
37297
  dangling.push(name);
37280
37298
  continue;
37281
37299
  }
37282
- const skillMd = join60(skillPath, "SKILL.md");
37300
+ const skillMd = join61(skillPath, "SKILL.md");
37283
37301
  if (!fs3.exists(skillMd) && !fs3.exists(skillPath + ".md")) {
37284
37302
  continue;
37285
37303
  }
37286
37304
  }
37287
- const overlayDir = opts.overlaySkillsDir ?? join60(agentDir, "skills.d");
37305
+ const overlayDir = opts.overlaySkillsDir ?? join61(agentDir, "skills.d");
37288
37306
  const overlaySlugs = new Set;
37289
37307
  if (fs3.exists(overlayDir)) {
37290
37308
  let overlayEntries = [];
@@ -37326,7 +37344,7 @@ function renderBucketedSkills(switchroom, agent) {
37326
37344
  }
37327
37345
  async function probeConnections(agentDir, opts = {}) {
37328
37346
  return withTimeout("Connections", (async () => {
37329
- const path3 = join60(agentDir, ".claude", "connection-health.json");
37347
+ const path3 = join61(agentDir, ".claude", "connection-health.json");
37330
37348
  const read = opts.readFileImpl ?? ((p) => readFileSync50(p, "utf8"));
37331
37349
  let issues = [];
37332
37350
  try {
@@ -37367,7 +37385,7 @@ async function probeDrift(agentDir, opts = {}) {
37367
37385
  }
37368
37386
  } catch {}
37369
37387
  try {
37370
- const raw = readFileSync50(join60(agentDir, ".switchroom-drift.json"), "utf8");
37388
+ const raw = readFileSync50(join61(agentDir, ".switchroom-drift.json"), "utf8");
37371
37389
  const report = JSON.parse(raw);
37372
37390
  if (report?.version === 1 && Array.isArray(report.findings)) {
37373
37391
  const gen = report.generatedAt ? Date.parse(report.generatedAt) : NaN;
@@ -37727,7 +37745,7 @@ __export(exports_boot_card, {
37727
37745
  readRoutingMode: () => readRoutingMode,
37728
37746
  containerBootStartMs: () => containerBootStartMs
37729
37747
  });
37730
- import { join as join61 } from "path";
37748
+ import { join as join62 } from "path";
37731
37749
  import { readFileSync as readFileSync54, statSync as statSync19 } from "fs";
37732
37750
  function resolvePersonaName(slug, loadConfig3) {
37733
37751
  try {
@@ -37772,7 +37790,7 @@ function containerBootStartMs(fsImpl = { readFileSync: readFileSync54 }) {
37772
37790
  }
37773
37791
  }
37774
37792
  function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync54, statSync: statSync19 }) {
37775
- const path3 = join61(agentDir, ".routing-mode");
37793
+ const path3 = join62(agentDir, ".routing-mode");
37776
37794
  try {
37777
37795
  const raw = fsImpl.readFileSync(path3, "utf-8");
37778
37796
  const line = raw.split(`
@@ -37879,7 +37897,7 @@ function renderBootCard(opts) {
37879
37897
  return stackCardLines(flatLines);
37880
37898
  }
37881
37899
  async function runAllProbes(opts) {
37882
- const claudeDir = join61(opts.agentDir, ".claude");
37900
+ const claudeDir = join62(opts.agentDir, ".claude");
37883
37901
  const probes = {};
37884
37902
  const slug = opts.agentSlug ?? opts.agentName;
37885
37903
  await Promise.allSettled([
@@ -39870,7 +39888,7 @@ import {
39870
39888
  closeSync as closeSync18,
39871
39889
  existsSync as existsSync65,
39872
39890
  unlinkSync as unlinkSync33,
39873
- appendFileSync as appendFileSync11
39891
+ appendFileSync as appendFileSync12
39874
39892
  } from "fs";
39875
39893
 
39876
39894
  // ../src/util/atomic.ts
@@ -39887,7 +39905,7 @@ function fsyncPathSync(path) {
39887
39905
 
39888
39906
  // gateway/gateway.ts
39889
39907
  import { homedir as homedir22 } from "os";
39890
- import { join as join77, sep as sep6, basename as basename19 } from "path";
39908
+ import { join as join78, sep as sep6, basename as basename19 } from "path";
39891
39909
 
39892
39910
  // plugin-logger.ts
39893
39911
  import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
@@ -41632,7 +41650,7 @@ function normalizeForSpeech(input) {
41632
41650
  const w = numberToWords(Number(n) * 1000);
41633
41651
  return w ? w : m;
41634
41652
  });
41635
- s = s.replace(/\b(\d{1,9})(ms|sec|min|kb|mb|gb|tb|hr|s|m|h|d)(?![a-z])/gi, (m, num, unitRaw) => {
41653
+ const unitReplacer = (m, num, unitRaw) => {
41636
41654
  const unit = UNIT_MAP[unitRaw.toLowerCase()];
41637
41655
  if (!unit)
41638
41656
  return m;
@@ -41641,7 +41659,9 @@ function normalizeForSpeech(input) {
41641
41659
  if (!w)
41642
41660
  return m;
41643
41661
  return `${w} ${n === 1 ? unit.s : unit.p}`;
41644
- });
41662
+ };
41663
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})(ms|sec|min|kb|mb|gb|tb|hr)(?![a-zA-Z])/gi, unitReplacer);
41664
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})(s|m|h|d)(?![a-zA-Z])/g, unitReplacer);
41645
41665
  s = s.replace(/(\S)\s*\+\s*(\S)/g, "$1 plus $2");
41646
41666
  s = s.replace(/\s=\s/g, " equals ");
41647
41667
  s = s.replace(/(\s)&(\s)/g, "$1and$2");
@@ -42003,7 +42023,7 @@ table omitted.`);
42003
42023
  const w = ordinalToWords(num);
42004
42024
  return w ?? m;
42005
42025
  });
42006
- s = s.replace(/\b(\d{1,9})\s?(ms|sec|min|km|cm|mm|kg|kb|mb|gb|tb|ghz|mhz|hr|mi|s|m|h|d|g)(?![a-z])/gi, (m, num, unitRaw) => {
42026
+ const unitReplacer = (m, num, unitRaw) => {
42007
42027
  const unit = UNIT_MAP2[unitRaw.toLowerCase()];
42008
42028
  if (!unit)
42009
42029
  return m;
@@ -42012,7 +42032,9 @@ table omitted.`);
42012
42032
  if (!w)
42013
42033
  return m;
42014
42034
  return `${w} ${n === 1 ? unit.s : unit.p}`;
42015
- });
42035
+ };
42036
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})\s?(ms|sec|min|km|cm|mm|kg|kb|mb|gb|tb|ghz|mhz|hr|mi)(?![a-zA-Z])/gi, unitReplacer);
42037
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})\s?(s|m|h|d|g)(?![a-zA-Z])/g, unitReplacer);
42016
42038
  s = s.replace(/(\s)&(\s)/g, "$1and$2");
42017
42039
  s = s.replace(/(\w)&(\w)/g, "$1 and $2");
42018
42040
  s = s.replace(/(^|\s)@(\w)/g, "$1at $2");
@@ -48111,7 +48133,7 @@ table omitted.`);
48111
48133
  const w = ordinalToWords2(num);
48112
48134
  return w ?? m;
48113
48135
  });
48114
- s = s.replace(/\b(\d{1,9})\s?(ms|sec|min|km|cm|mm|kg|kb|mb|gb|tb|ghz|mhz|hr|mi|s|m|h|d|g)(?![a-z])/gi, (m, num, unitRaw) => {
48136
+ const unitReplacer = (m, num, unitRaw) => {
48115
48137
  const unit = UNIT_MAP3[unitRaw.toLowerCase()];
48116
48138
  if (!unit)
48117
48139
  return m;
@@ -48120,7 +48142,9 @@ table omitted.`);
48120
48142
  if (!w)
48121
48143
  return m;
48122
48144
  return `${w} ${n === 1 ? unit.s : unit.p}`;
48123
- });
48145
+ };
48146
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})\s?(ms|sec|min|km|cm|mm|kg|kb|mb|gb|tb|ghz|mhz|hr|mi)(?![a-zA-Z])/gi, unitReplacer);
48147
+ s = s.replace(/(?<![\d.,])\b(\d{1,9})\s?(s|m|h|d|g)(?![a-zA-Z])/g, unitReplacer);
48124
48148
  s = s.replace(/(\s)&(\s)/g, "$1and$2");
48125
48149
  s = s.replace(/(\w)&(\w)/g, "$1 and $2");
48126
48150
  s = s.replace(/(^|\s)@(\w)/g, "$1at $2");
@@ -65005,7 +65029,178 @@ function gfmFromMarkdown() {
65005
65029
  gfmTaskListItemFromMarkdown()
65006
65030
  ];
65007
65031
  }
65032
+ // render/html-fold.ts
65033
+ var HTML_FOLD_TAGS = {
65034
+ b: "bold",
65035
+ strong: "bold",
65036
+ i: "italic",
65037
+ em: "italic",
65038
+ s: "strike",
65039
+ del: "strike",
65040
+ strike: "strike",
65041
+ code: "code",
65042
+ a: "link",
65043
+ br: "break",
65044
+ pre: "pre"
65045
+ };
65046
+ var HTML_VOID_TAGS = new Set([
65047
+ "area",
65048
+ "base",
65049
+ "col",
65050
+ "embed",
65051
+ "hr",
65052
+ "img",
65053
+ "input",
65054
+ "link",
65055
+ "meta",
65056
+ "param",
65057
+ "source",
65058
+ "track",
65059
+ "wbr"
65060
+ ]);
65061
+ var HTML_BLOCK_LEVEL_TAGS = new Set([
65062
+ "address",
65063
+ "article",
65064
+ "blockquote",
65065
+ "center",
65066
+ "dd",
65067
+ "div",
65068
+ "dl",
65069
+ "dt",
65070
+ "fieldset",
65071
+ "figcaption",
65072
+ "figure",
65073
+ "footer",
65074
+ "form",
65075
+ "h1",
65076
+ "h2",
65077
+ "h3",
65078
+ "h4",
65079
+ "h5",
65080
+ "h6",
65081
+ "header",
65082
+ "hr",
65083
+ "li",
65084
+ "main",
65085
+ "nav",
65086
+ "ol",
65087
+ "p",
65088
+ "section",
65089
+ "table",
65090
+ "tbody",
65091
+ "td",
65092
+ "tfoot",
65093
+ "th",
65094
+ "thead",
65095
+ "tr",
65096
+ "ul"
65097
+ ]);
65098
+ function isBlockLevelTag(name) {
65099
+ return HTML_BLOCK_LEVEL_TAGS.has(name);
65100
+ }
65101
+ function isVoidTag(name) {
65102
+ return HTML_VOID_TAGS.has(name);
65103
+ }
65104
+ function escapeHtmlLiteral(text4) {
65105
+ return text4.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
65106
+ }
65107
+ var LANGUAGE_CLASS_RE = /(?:^|\s)class\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/i;
65108
+ function languageOf(tag) {
65109
+ const m = LANGUAGE_CLASS_RE.exec(tag.attrs);
65110
+ if (m == null)
65111
+ return null;
65112
+ const cls = m[1] ?? m[2] ?? m[3] ?? "";
65113
+ const lang = cls.split(/\s+/).map((c) => /^language-(.+)$/.exec(c)?.[1]).find((c) => c != null && c.length > 0);
65114
+ return lang != null && !lang.includes("`") ? lang : null;
65115
+ }
65116
+ var HTML_PASSTHROUGH_TAGS = new Set([
65117
+ "u",
65118
+ "sub",
65119
+ "sup",
65120
+ "details",
65121
+ "summary",
65122
+ "aside",
65123
+ "cite"
65124
+ ]);
65125
+ var HTML_TOKEN_RE = /<!--[\s\S]*?-->|<\/?[A-Za-z][A-Za-z0-9-]*(?:\s[^<>]*?)?\/?>/g;
65126
+ var TAG_RE = /^<(\/?)([A-Za-z][A-Za-z0-9-]*)((?:\s[^<>]*?)?)(\/?)>$/;
65127
+ var HREF_RE = /(?:^|\s)href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/i;
65128
+ function classifyHtmlTag(raw) {
65129
+ if (raw.startsWith("<!--"))
65130
+ return { kind: "comment", name: "", raw, attrs: "" };
65131
+ const m = TAG_RE.exec(raw);
65132
+ if (m == null)
65133
+ return { kind: "other", name: "", raw, attrs: "" };
65134
+ const [, slash, name, attrs, selfClose] = m;
65135
+ const kind = slash === "/" ? "close" : selfClose === "/" ? "selfclose" : "open";
65136
+ return { kind, name: name.toLowerCase(), raw, attrs: attrs ?? "" };
65137
+ }
65138
+ function hrefOf(tag) {
65139
+ const m = HREF_RE.exec(tag.attrs);
65140
+ if (m == null)
65141
+ return null;
65142
+ const value = m[1] ?? m[2] ?? m[3] ?? "";
65143
+ return value.length > 0 ? value : null;
65144
+ }
65145
+ function isPassthroughTag(tag) {
65146
+ return tag.name.length > 0 && HTML_PASSTHROUGH_TAGS.has(tag.name);
65147
+ }
65148
+ function matchedTagOffsets(tokens) {
65149
+ const matched = new Set;
65150
+ const stack = [];
65151
+ for (const { tag, start } of tokens) {
65152
+ if (tag.kind === "open" && !isVoidTag(tag.name)) {
65153
+ stack.push({ name: tag.name, start });
65154
+ continue;
65155
+ }
65156
+ if (tag.kind !== "close")
65157
+ continue;
65158
+ for (let i = stack.length - 1;i >= 0; i--) {
65159
+ if (stack[i].name !== tag.name)
65160
+ continue;
65161
+ matched.add(stack[i].start);
65162
+ matched.add(start);
65163
+ stack.length = i;
65164
+ break;
65165
+ }
65166
+ }
65167
+ return matched;
65168
+ }
65169
+ function tokenizeHtml(raw, base) {
65170
+ const pieces = [];
65171
+ const re = new RegExp(HTML_TOKEN_RE.source, "g");
65172
+ let last = 0;
65173
+ let m;
65174
+ while ((m = re.exec(raw)) != null) {
65175
+ if (m.index > last) {
65176
+ pieces.push({
65177
+ kind: "text",
65178
+ text: raw.slice(last, m.index),
65179
+ start: base + last,
65180
+ end: base + m.index
65181
+ });
65182
+ }
65183
+ pieces.push({
65184
+ kind: "tag",
65185
+ tag: classifyHtmlTag(m[0]),
65186
+ start: base + m.index,
65187
+ end: base + m.index + m[0].length
65188
+ });
65189
+ last = m.index + m[0].length;
65190
+ }
65191
+ if (last < raw.length) {
65192
+ pieces.push({
65193
+ kind: "text",
65194
+ text: raw.slice(last),
65195
+ start: base + last,
65196
+ end: base + raw.length
65197
+ });
65198
+ }
65199
+ return pieces;
65200
+ }
65201
+
65008
65202
  // render/parse.ts
65203
+ init_format();
65009
65204
  function pos(node2) {
65010
65205
  const p = node2.position;
65011
65206
  return {
@@ -65023,11 +65218,32 @@ function isTgInlineEntityHref(href) {
65023
65218
  const h = href.toLowerCase();
65024
65219
  return TG_INLINE_ENTITY_HREFS.some((base) => h === base || h.startsWith(`${base}?`));
65025
65220
  }
65221
+ var FENCE_DELIM_RE = /^ {0,3}(`{3,}|~{3,})/;
65026
65222
  function markExpandableQuotes(markdown) {
65027
65223
  const expandableLineStarts = new Set;
65028
65224
  let out = "";
65029
65225
  let offset = 0;
65226
+ let openFence = null;
65030
65227
  for (const line of markdown.split(/(?<=\n)/)) {
65228
+ const body = line.replace(/\r?\n$/, "");
65229
+ const fence = FENCE_DELIM_RE.exec(body)?.[1] ?? null;
65230
+ if (openFence !== null) {
65231
+ out += line;
65232
+ if (fence !== null && fence[0] === openFence[0] && fence.length >= openFence.length && body.slice(body.indexOf(fence) + fence.length).trim() === "") {
65233
+ openFence = null;
65234
+ }
65235
+ offset += line.length;
65236
+ continue;
65237
+ }
65238
+ if (fence !== null) {
65239
+ const rest = body.slice(body.indexOf(fence) + fence.length);
65240
+ if (!(fence[0] === "`" && rest.includes("`"))) {
65241
+ openFence = fence;
65242
+ out += line;
65243
+ offset += line.length;
65244
+ continue;
65245
+ }
65246
+ }
65031
65247
  if (EXPANDABLE_MARKER_RE.test(line)) {
65032
65248
  expandableLineStarts.add(offset);
65033
65249
  out += " " + line.slice(2);
@@ -65048,7 +65264,7 @@ function lineStart(source, offset) {
65048
65264
  function foldAlign(a) {
65049
65265
  return a ?? null;
65050
65266
  }
65051
- function foldInline(node2, source) {
65267
+ function foldInline(node2, source, ctx) {
65052
65268
  switch (node2.type) {
65053
65269
  case "text":
65054
65270
  return { type: "plain", text: node2.value, ...pos(node2) };
@@ -65057,21 +65273,21 @@ function foldInline(node2, source) {
65057
65273
  const underline = source.slice(p.start, p.start + 2) === "__";
65058
65274
  return {
65059
65275
  type: underline ? "underline" : "bold",
65060
- children: foldInlineChildren(node2, source),
65276
+ children: foldInlineChildren(node2, source, ctx),
65061
65277
  ...p
65062
65278
  };
65063
65279
  }
65064
65280
  case "emphasis":
65065
- return { type: "italic", children: foldInlineChildren(node2, source), ...pos(node2) };
65281
+ return { type: "italic", children: foldInlineChildren(node2, source, ctx), ...pos(node2) };
65066
65282
  case "delete":
65067
- return { type: "strike", children: foldInlineChildren(node2, source), ...pos(node2) };
65283
+ return { type: "strike", children: foldInlineChildren(node2, source, ctx), ...pos(node2) };
65068
65284
  case "inlineCode":
65069
65285
  return { type: "code", text: node2.value, ...pos(node2) };
65070
65286
  case "link":
65071
65287
  return {
65072
65288
  type: "link",
65073
65289
  href: node2.url,
65074
- children: foldInlineChildren(node2, source),
65290
+ children: foldInlineChildren(node2, source, ctx),
65075
65291
  ...pos(node2)
65076
65292
  };
65077
65293
  case "image": {
@@ -65127,28 +65343,250 @@ function expandPlainNode(node2, source) {
65127
65343
  }
65128
65344
  return out;
65129
65345
  }
65130
- function buildInlines(children, source) {
65131
- return children.map((c) => foldInline(c, source)).flatMap((n) => n.type === "plain" ? expandPlainNode(n, source) : [n]);
65346
+ function findClosingTag(items, from, name) {
65347
+ let depth = 0;
65348
+ for (let i = from;i < items.length; i++) {
65349
+ const it = items[i];
65350
+ if (it.kind !== "tag" || it.tag.name !== name)
65351
+ continue;
65352
+ if (it.tag.kind === "open")
65353
+ depth++;
65354
+ else if (it.tag.kind === "close") {
65355
+ if (depth === 0)
65356
+ return i;
65357
+ depth--;
65358
+ }
65359
+ }
65360
+ return -1;
65361
+ }
65362
+ function inlineLiteralText(nodes) {
65363
+ let out = "";
65364
+ for (const n of nodes) {
65365
+ if (n.type !== "plain")
65366
+ return null;
65367
+ out += n.text;
65368
+ }
65369
+ return out;
65370
+ }
65371
+ var structuralSeparators = new WeakSet;
65372
+ function mkSeparator(ctx, start, end) {
65373
+ const node2 = {
65374
+ type: "raw",
65375
+ text: ctx.noBreaks === true ? " " : `
65376
+ `,
65377
+ start,
65378
+ end
65379
+ };
65380
+ structuralSeparators.add(node2);
65381
+ return node2;
65382
+ }
65383
+ function normalizeSeparators(nodes) {
65384
+ const out = [];
65385
+ for (const node2 of nodes) {
65386
+ if (!structuralSeparators.has(node2)) {
65387
+ out.push(node2);
65388
+ continue;
65389
+ }
65390
+ if (out.length === 0)
65391
+ continue;
65392
+ if (structuralSeparators.has(out[out.length - 1]))
65393
+ continue;
65394
+ out.push(node2);
65395
+ }
65396
+ while (out.length > 0 && structuralSeparators.has(out[out.length - 1]))
65397
+ out.pop();
65398
+ return out;
65399
+ }
65400
+ function literalTag(tag, start, end) {
65401
+ return { type: "plain", text: escapeHtmlLiteral(tag.raw), start, end };
65402
+ }
65403
+ function foldHtmlItems(items, active, ctx) {
65404
+ const out = [];
65405
+ let i = 0;
65406
+ while (i < items.length) {
65407
+ const it = items[i];
65408
+ if (it.kind === "inline") {
65409
+ out.push(it.node);
65410
+ i++;
65411
+ continue;
65412
+ }
65413
+ const tag = it.tag;
65414
+ if (tag.kind === "comment") {
65415
+ i++;
65416
+ continue;
65417
+ }
65418
+ if (HTML_FOLD_TAGS[tag.name] === "break" && tag.kind !== "close") {
65419
+ out.push(ctx.noBreaks === true ? { type: "raw", text: " ", start: it.start, end: it.end } : { type: "raw", text: `
65420
+ `, start: it.start, end: it.end });
65421
+ i++;
65422
+ continue;
65423
+ }
65424
+ if (tag.kind === "selfclose" || tag.kind === "open" && isVoidTag(tag.name)) {
65425
+ if (isPassthroughTag(tag)) {
65426
+ out.push({ type: "raw", text: tag.raw, start: it.start, end: it.end });
65427
+ } else if (isBlockLevelTag(tag.name)) {
65428
+ out.push(mkSeparator(ctx, it.start, it.end));
65429
+ }
65430
+ i++;
65431
+ continue;
65432
+ }
65433
+ if (isPassthroughTag(tag) && (tag.kind === "open" || tag.kind === "close")) {
65434
+ out.push(ctx.matched.has(it.start) ? { type: "raw", text: tag.raw, start: it.start, end: it.end } : literalTag(tag, it.start, it.end));
65435
+ i++;
65436
+ continue;
65437
+ }
65438
+ if (tag.kind === "open") {
65439
+ const close = findClosingTag(items, i + 1, tag.name);
65440
+ const closeItem = close >= 0 ? items[close] : undefined;
65441
+ if (closeItem !== undefined && closeItem.kind === "tag") {
65442
+ out.push(...foldMatchedTag(tag, closeItem, items.slice(i + 1, close), active, ctx, {
65443
+ start: it.start,
65444
+ end: closeItem.end
65445
+ }));
65446
+ i = close + 1;
65447
+ continue;
65448
+ }
65449
+ }
65450
+ if (ctx.matched.has(it.start) && tag.kind !== "other") {
65451
+ if (isBlockLevelTag(tag.name))
65452
+ out.push(mkSeparator(ctx, it.start, it.end));
65453
+ i++;
65454
+ continue;
65455
+ }
65456
+ out.push(literalTag(tag, it.start, it.end));
65457
+ i++;
65458
+ }
65459
+ return normalizeSeparators(out);
65460
+ }
65461
+ function foldMatchedTag(tag, closeTag, innerItems, active, ctx, span) {
65462
+ const target = HTML_FOLD_TAGS[tag.name];
65463
+ if (target === "pre") {
65464
+ const folded = buildPreBlock(innerItems, ctx, span);
65465
+ if (folded !== null)
65466
+ return folded;
65467
+ }
65468
+ if (target !== undefined && target !== "break" && target !== "pre") {
65469
+ const nested = new Set(active);
65470
+ nested.add(target);
65471
+ const inner2 = foldHtmlItems(innerItems, nested, ctx);
65472
+ if (active.has(target)) {
65473
+ return target === "link" ? [
65474
+ literalTag(tag, span.start, span.start + tag.raw.length),
65475
+ ...inner2,
65476
+ literalTag(closeTag.tag, closeTag.start, closeTag.end)
65477
+ ] : inner2;
65478
+ }
65479
+ const folded = buildFoldedTag(target, tag, inner2, span.start, span.end);
65480
+ return folded !== null ? [folded] : inner2;
65481
+ }
65482
+ const inner = foldHtmlItems(innerItems, active, ctx);
65483
+ if (!isBlockLevelTag(tag.name))
65484
+ return inner;
65485
+ return [
65486
+ mkSeparator(ctx, span.start, span.start + tag.raw.length),
65487
+ ...inner,
65488
+ mkSeparator(ctx, closeTag.start, closeTag.end)
65489
+ ];
65490
+ }
65491
+ function buildPreBlock(innerItems, ctx, span) {
65492
+ let body = innerItems;
65493
+ let language = null;
65494
+ const first = body[0];
65495
+ const last = body[body.length - 1];
65496
+ if (body.length >= 2 && first?.kind === "tag" && first.tag.kind === "open" && first.tag.name === "code" && last?.kind === "tag" && last.tag.kind === "close" && last.tag.name === "code") {
65497
+ language = languageOf(first.tag);
65498
+ body = body.slice(1, -1);
65499
+ }
65500
+ let text4 = "";
65501
+ for (const item of body) {
65502
+ if (item.kind !== "inline" || item.node.type !== "plain")
65503
+ return null;
65504
+ text4 += item.node.text;
65505
+ }
65506
+ text4 = text4.replace(/^\r?\n/, "").replace(/[ \t]*\r?\n[ \t]*$/, "");
65507
+ if (ctx.noBreaks === true) {
65508
+ return [{ type: "code", text: text4.replace(/\s*\r?\n\s*/g, " "), ...span }];
65509
+ }
65510
+ const fence = codeFenceFor(text4);
65511
+ return [
65512
+ mkSeparator(ctx, span.start, span.start),
65513
+ {
65514
+ type: "raw",
65515
+ text: `${fence}${language ?? ""}
65516
+ ${text4}
65517
+ ${fence}`,
65518
+ ...span
65519
+ },
65520
+ mkSeparator(ctx, span.end, span.end)
65521
+ ];
65522
+ }
65523
+ function buildFoldedTag(target, tag, children, start, end) {
65524
+ switch (target) {
65525
+ case "bold":
65526
+ case "italic":
65527
+ case "strike":
65528
+ if (children.length === 1 && children[0].type === target)
65529
+ return children[0];
65530
+ return { type: target, children, start, end };
65531
+ case "code": {
65532
+ const text4 = inlineLiteralText(children);
65533
+ return text4 === null ? null : { type: "code", text: text4, start, end };
65534
+ }
65535
+ case "link": {
65536
+ const href = hrefOf(tag);
65537
+ return href === null ? null : { type: "link", href, children, start, end };
65538
+ }
65539
+ default:
65540
+ return null;
65541
+ }
65542
+ }
65543
+ function htmlStringToItems(raw, base, source) {
65544
+ return tokenizeHtml(raw, base).flatMap((piece) => piece.kind === "tag" ? [{ kind: "tag", tag: piece.tag, start: piece.start, end: piece.end }] : expandPlainNode(mkPlain(piece.text, piece.start, piece.end), source).map((node2) => ({ kind: "inline", node: node2 })));
65545
+ }
65546
+ function buildInlines(children, source, ctx) {
65547
+ const items = [];
65548
+ let sawHtml = false;
65549
+ for (const child of children) {
65550
+ if (child.type === "html") {
65551
+ sawHtml = true;
65552
+ const p = pos(child);
65553
+ items.push(...htmlStringToItems(slice(source, child), p.start, source));
65554
+ continue;
65555
+ }
65556
+ const folded = foldInline(child, source, ctx);
65557
+ const expanded = folded.type === "plain" ? expandPlainNode(folded, source) : [folded];
65558
+ for (const node2 of expanded)
65559
+ items.push({ kind: "inline", node: node2 });
65560
+ }
65561
+ if (!sawHtml)
65562
+ return items.map((it) => it.node);
65563
+ return foldHtmlItems(items, new Set, ctx);
65132
65564
  }
65133
- function foldInlineChildren(node2, source) {
65134
- return buildInlines(node2.children, source);
65565
+ function foldInlineChildren(node2, source, ctx) {
65566
+ return buildInlines(node2.children, source, ctx);
65135
65567
  }
65136
- function foldTableRow(row, source) {
65568
+ function foldTableRow(row, source, ctx) {
65137
65569
  const cells = row.children.map((cell) => ({
65138
- children: buildInlines(cell.children, source),
65570
+ children: buildInlines(cell.children, source, { ...ctx, noBreaks: true }),
65139
65571
  ...pos(cell)
65140
65572
  }));
65141
65573
  return { cells, ...pos(row) };
65142
65574
  }
65143
- function foldBlock(node2, source, expandableLineStarts) {
65575
+ function isEmptyBlock(block) {
65576
+ return block.type === "paragraph" && block.children.length === 0;
65577
+ }
65578
+ function foldBlocks(nodes, source, expandableLineStarts, ctx) {
65579
+ return nodes.map((n) => foldBlock(n, source, expandableLineStarts, ctx)).filter((b) => !isEmptyBlock(b));
65580
+ }
65581
+ function foldBlock(node2, source, expandableLineStarts, ctx) {
65144
65582
  switch (node2.type) {
65145
65583
  case "paragraph":
65146
- return { type: "paragraph", children: foldInlineChildren(node2, source), ...pos(node2) };
65584
+ return { type: "paragraph", children: foldInlineChildren(node2, source, ctx), ...pos(node2) };
65147
65585
  case "heading":
65148
65586
  return {
65149
65587
  type: "heading",
65150
65588
  level: node2.depth,
65151
- children: foldInlineChildren(node2, source),
65589
+ children: foldInlineChildren(node2, source, { ...ctx, noBreaks: true }),
65152
65590
  ...pos(node2)
65153
65591
  };
65154
65592
  case "blockquote": {
@@ -65156,7 +65594,7 @@ function foldBlock(node2, source, expandableLineStarts) {
65156
65594
  const expandable = expandableLineStarts.has(lineStart(source, p.start));
65157
65595
  return {
65158
65596
  type: "blockquote",
65159
- children: node2.children.map((c) => foldBlock(c, source, expandableLineStarts)),
65597
+ children: foldBlocks(node2.children, source, expandableLineStarts, ctx),
65160
65598
  expandable,
65161
65599
  ...p
65162
65600
  };
@@ -65170,7 +65608,7 @@ function foldBlock(node2, source, expandableLineStarts) {
65170
65608
  };
65171
65609
  case "list": {
65172
65610
  const items = node2.children.map((li) => ({
65173
- children: li.children.map((c) => foldBlock(c, source, expandableLineStarts)),
65611
+ children: foldBlocks(li.children, source, expandableLineStarts, ctx),
65174
65612
  checked: li.checked ?? null,
65175
65613
  spread: li.spread ?? false,
65176
65614
  ...pos(li)
@@ -65191,8 +65629,8 @@ function foldBlock(node2, source, expandableLineStarts) {
65191
65629
  const [headerRow2, ...bodyRows] = rows;
65192
65630
  return {
65193
65631
  type: "table",
65194
- header: foldTableRow(headerRow2, source),
65195
- rows: bodyRows.map((r) => foldTableRow(r, source)),
65632
+ header: foldTableRow(headerRow2, source, ctx),
65633
+ rows: bodyRows.map((r) => foldTableRow(r, source, ctx)),
65196
65634
  align: (node2.align ?? []).map(foldAlign),
65197
65635
  ...pos(node2)
65198
65636
  };
@@ -65203,6 +65641,14 @@ function foldBlock(node2, source, expandableLineStarts) {
65203
65641
  children: [{ type: "raw", text: slice(source, node2), ...pos(node2) }],
65204
65642
  ...pos(node2)
65205
65643
  };
65644
+ case "html": {
65645
+ const p = pos(node2);
65646
+ return {
65647
+ type: "paragraph",
65648
+ children: foldHtmlItems(htmlStringToItems(slice(source, node2), p.start, source), new Set, ctx),
65649
+ ...p
65650
+ };
65651
+ }
65206
65652
  default:
65207
65653
  return {
65208
65654
  type: "paragraph",
@@ -65218,8 +65664,30 @@ function parse2(markdown) {
65218
65664
  mdastExtensions: [gfmFromMarkdown()]
65219
65665
  });
65220
65666
  return {
65221
- blocks: tree.children.map((child) => foldBlock(child, markdown, expandableLineStarts))
65667
+ blocks: foldBlocks(tree.children, markdown, expandableLineStarts, {
65668
+ matched: matchedTagOffsets(collectHtmlTokens(tree.children, markdown))
65669
+ })
65670
+ };
65671
+ }
65672
+ function collectHtmlTokens(nodes, source) {
65673
+ const out = [];
65674
+ const walk = (list2) => {
65675
+ for (const node2 of list2) {
65676
+ if (node2.type === "html") {
65677
+ const p = pos(node2);
65678
+ for (const piece of tokenizeHtml(slice(source, node2), p.start)) {
65679
+ if (piece.kind === "tag")
65680
+ out.push({ tag: piece.tag, start: piece.start });
65681
+ }
65682
+ continue;
65683
+ }
65684
+ const children = node2.children;
65685
+ if (Array.isArray(children))
65686
+ walk(children);
65687
+ }
65222
65688
  };
65689
+ walk(nodes);
65690
+ return out;
65223
65691
  }
65224
65692
 
65225
65693
  // render/render.ts
@@ -65273,8 +65741,7 @@ function renderBlockquote(node2) {
65273
65741
  }
65274
65742
  function renderCodeBlock(node2) {
65275
65743
  const lang = node2.language ?? "";
65276
- const longestRun = Math.max(0, ...(node2.text.match(/`+/g) ?? []).map((run2) => run2.length));
65277
- const fence = "`".repeat(Math.max(3, longestRun + 1));
65744
+ const fence = codeFenceFor(node2.text);
65278
65745
  return `${fence}${lang}
65279
65746
  ${node2.text}
65280
65747
  ${fence}`;
@@ -65374,8 +65841,7 @@ function tableDegradationReason(node2) {
65374
65841
  }
65375
65842
  function degradeToCodeFence(source, node2) {
65376
65843
  const raw = source.slice(node2.start, node2.end);
65377
- const longestRun = Math.max(0, ...(raw.match(/`+/g) ?? []).map((run2) => run2.length));
65378
- const fence = "`".repeat(Math.max(3, longestRun + 1));
65844
+ const fence = codeFenceFor(raw);
65379
65845
  return `${fence}
65380
65846
  ${raw}
65381
65847
  ${fence}`;
@@ -76790,12 +77256,16 @@ function lookupMessageRoleAndText(chatId, messageId, opts) {
76790
77256
  return null;
76791
77257
  return { role: row.role, text: row.text ?? "", kind: row.kind ?? null };
76792
77258
  }
76793
- function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
77259
+ function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200, untilMs) {
76794
77260
  try {
76795
77261
  const cutoffSec = Math.floor(sinceMs / 1000);
76796
77262
  const minLen = Math.max(1, Math.floor(minChars));
76797
77263
  const params = [chatId, cutoffSec, minLen];
76798
77264
  let sql = "SELECT 1 FROM messages WHERE chat_id = ? AND role = 'assistant' AND ts >= ? AND LENGTH(text) >= ?";
77265
+ if (untilMs != null && Number.isFinite(untilMs)) {
77266
+ sql += " AND ts <= ?";
77267
+ params.push(Math.ceil(untilMs / 1000));
77268
+ }
76799
77269
  if (threadId !== undefined) {
76800
77270
  if (threadId === null) {
76801
77271
  sql += " AND thread_id IS NULL";
@@ -79880,6 +80350,62 @@ function normalizeTemporal(text4, tz, nowMs2) {
79880
80350
  return normalizeRelativeDays(normalizeUtcDatetimes(text4, tz, nowMs2), tz, nowMs2);
79881
80351
  }
79882
80352
 
80353
+ // gateway/speech-capture.ts
80354
+ import { appendFileSync as appendFileSync5 } from "node:fs";
80355
+ import { join as join34 } from "node:path";
80356
+ var SPEECH_CAPTURE_FILE_NAME = "speech-capture.jsonl";
80357
+ var SPEECH_CAPTURE_FILE_MODE = 384;
80358
+ function envFlagEnabled() {
80359
+ const v = process.env.SWITCHROOM_SPEECH_CAPTURE;
80360
+ return v === "1" || v === "true";
80361
+ }
80362
+ function isCaptureEnabled(override) {
80363
+ if (override !== undefined)
80364
+ return override;
80365
+ return envFlagEnabled();
80366
+ }
80367
+ var loggedEnabledOnce = false;
80368
+ var lastWriteErrorLoggedAt = null;
80369
+ var WRITE_ERROR_LOG_INTERVAL_MS = 5 * 60000;
80370
+ function logEnabledOnce() {
80371
+ if (loggedEnabledOnce)
80372
+ return;
80373
+ loggedEnabledOnce = true;
80374
+ try {
80375
+ process.stderr.write(`telegram gateway: speech-capture: enabled \u2014 writing $TELEGRAM_STATE_DIR/${SPEECH_CAPTURE_FILE_NAME}
80376
+ `);
80377
+ } catch {}
80378
+ }
80379
+ function logWriteErrorRateLimited(err, nowMs2) {
80380
+ if (lastWriteErrorLoggedAt !== null && nowMs2 - lastWriteErrorLoggedAt < WRITE_ERROR_LOG_INTERVAL_MS)
80381
+ return;
80382
+ lastWriteErrorLoggedAt = nowMs2;
80383
+ try {
80384
+ const msg = err instanceof Error ? err.message : String(err);
80385
+ process.stderr.write(`telegram gateway: speech-capture: write failed (further failures rate-limited ` + `${Math.round(WRITE_ERROR_LOG_INTERVAL_MS / 60000)}m) err=${msg}
80386
+ `);
80387
+ } catch {}
80388
+ }
80389
+ function captureSpeechText(text4, opts = {}) {
80390
+ if (!isCaptureEnabled(opts.enabled))
80391
+ return;
80392
+ logEnabledOnce();
80393
+ try {
80394
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR;
80395
+ if (stateDir == null || stateDir.length === 0)
80396
+ return;
80397
+ const now = opts.now ?? Date.now;
80398
+ const line = `${JSON.stringify({ ts: now(), text: text4 })}
80399
+ `;
80400
+ appendFileSync5(join34(stateDir, SPEECH_CAPTURE_FILE_NAME), line, {
80401
+ encoding: "utf8",
80402
+ mode: SPEECH_CAPTURE_FILE_MODE
80403
+ });
80404
+ } catch (err) {
80405
+ logWriteErrorRateLimited(err, Date.now());
80406
+ }
80407
+ }
80408
+
79883
80409
  // gateway/outbound-send-path.ts
79884
80410
  var import_grammy11 = __toESM(require_mod2(), 1);
79885
80411
  import { statSync as statSync12 } from "fs";
@@ -80024,9 +80550,9 @@ import {
80024
80550
  statSync as statSync11,
80025
80551
  unlinkSync as unlinkSync14,
80026
80552
  writeFileSync as writeFileSync25,
80027
- appendFileSync as appendFileSync5
80553
+ appendFileSync as appendFileSync6
80028
80554
  } from "node:fs";
80029
- import { join as join34 } from "node:path";
80555
+ import { join as join35 } from "node:path";
80030
80556
  import { homedir as homedir9 } from "node:os";
80031
80557
 
80032
80558
  // hooks/audience-classify.mjs
@@ -80110,10 +80636,10 @@ function resolveStateDir2(explicit) {
80110
80636
  if (env != null && env !== "")
80111
80637
  return env;
80112
80638
  const home2 = process.env.HOME ?? homedir9();
80113
- return join34(home2, ".claude", "channels", "telegram");
80639
+ return join35(home2, ".claude", "channels", "telegram");
80114
80640
  }
80115
80641
  function resolveOutboxDir(stateDir) {
80116
- return join34(resolveStateDir2(stateDir), "outbox");
80642
+ return join35(resolveStateDir2(stateDir), "outbox");
80117
80643
  }
80118
80644
  var JOURNAL_FILE = "delivered.jsonl";
80119
80645
  var OUTBOX_MAX_AGE_MS = 30 * 60000;
@@ -80123,10 +80649,10 @@ function writeOutboxRecordAtomic(record, stateDir) {
80123
80649
  const dir = resolveOutboxDir(stateDir);
80124
80650
  try {
80125
80651
  mkdirSync29(dir, { recursive: true });
80126
- const finalPath = join34(dir, `${record.turnNonce}.json`);
80652
+ const finalPath = join35(dir, `${record.turnNonce}.json`);
80127
80653
  if (existsSync26(finalPath))
80128
80654
  return true;
80129
- const tmpPath = join34(dir, `.${record.turnNonce}.${process.pid}.tmp`);
80655
+ const tmpPath = join35(dir, `.${record.turnNonce}.${process.pid}.tmp`);
80130
80656
  writeFileSync25(tmpPath, JSON.stringify(record), { mode: 384 });
80131
80657
  renameSync14(tmpPath, finalPath);
80132
80658
  return true;
@@ -80144,7 +80670,7 @@ function listPendingRecords(stateDir) {
80144
80670
  }
80145
80671
  function readOutboxRecord(fileName, stateDir) {
80146
80672
  try {
80147
- const raw = readFileSync26(join34(resolveOutboxDir(stateDir), fileName), "utf8");
80673
+ const raw = readFileSync26(join35(resolveOutboxDir(stateDir), fileName), "utf8");
80148
80674
  return JSON.parse(raw);
80149
80675
  } catch {
80150
80676
  return null;
@@ -80152,8 +80678,8 @@ function readOutboxRecord(fileName, stateDir) {
80152
80678
  }
80153
80679
  function claimRecord(nonce, stateDir) {
80154
80680
  const dir = resolveOutboxDir(stateDir);
80155
- const from = join34(dir, `${nonce}.json`);
80156
- const to = join34(dir, `${nonce}.sending`);
80681
+ const from = join35(dir, `${nonce}.json`);
80682
+ const to = join35(dir, `${nonce}.sending`);
80157
80683
  try {
80158
80684
  renameSync14(from, to);
80159
80685
  return to;
@@ -80164,12 +80690,12 @@ function claimRecord(nonce, stateDir) {
80164
80690
  function releaseClaim(nonce, stateDir) {
80165
80691
  const dir = resolveOutboxDir(stateDir);
80166
80692
  try {
80167
- renameSync14(join34(dir, `${nonce}.sending`), join34(dir, `${nonce}.json`));
80693
+ renameSync14(join35(dir, `${nonce}.sending`), join35(dir, `${nonce}.json`));
80168
80694
  } catch {}
80169
80695
  }
80170
80696
  function removeClaimed(nonce, stateDir) {
80171
80697
  try {
80172
- unlinkSync14(join34(resolveOutboxDir(stateDir), `${nonce}.sending`));
80698
+ unlinkSync14(join35(resolveOutboxDir(stateDir), `${nonce}.sending`));
80173
80699
  } catch {}
80174
80700
  }
80175
80701
  function reclaimStaleSending(stateDir, now = Date.now()) {
@@ -80182,16 +80708,16 @@ function reclaimStaleSending(stateDir, now = Date.now()) {
80182
80708
  }
80183
80709
  for (const f of files) {
80184
80710
  try {
80185
- const st = statSync11(join34(dir, f));
80711
+ const st = statSync11(join35(dir, f));
80186
80712
  if (now - st.mtimeMs > OUTBOX_SENDING_TIMEOUT_MS) {
80187
- renameSync14(join34(dir, f), join34(dir, f.replace(/\.sending$/, ".json")));
80713
+ renameSync14(join35(dir, f), join35(dir, f.replace(/\.sending$/, ".json")));
80188
80714
  }
80189
80715
  } catch {}
80190
80716
  }
80191
80717
  }
80192
80718
  function readDeliveredNonces(stateDir) {
80193
80719
  const set = new Set;
80194
- const path2 = join34(resolveOutboxDir(stateDir), JOURNAL_FILE);
80720
+ const path2 = join35(resolveOutboxDir(stateDir), JOURNAL_FILE);
80195
80721
  if (!existsSync26(path2))
80196
80722
  return set;
80197
80723
  try {
@@ -80218,7 +80744,7 @@ function isBackstopDeliveredEntry(e) {
80218
80744
  function backstopAlreadyDelivered(nonce, stateDir) {
80219
80745
  if (nonce == null || nonce === "")
80220
80746
  return false;
80221
- const path2 = join34(resolveOutboxDir(stateDir), JOURNAL_FILE);
80747
+ const path2 = join35(resolveOutboxDir(stateDir), JOURNAL_FILE);
80222
80748
  if (!existsSync26(path2))
80223
80749
  return false;
80224
80750
  try {
@@ -80239,10 +80765,10 @@ var JOURNAL_KEEP = 2000;
80239
80765
  var JOURNAL_ROTATE_AT = 4000;
80240
80766
  function appendDelivered(entry, stateDir) {
80241
80767
  const dir = resolveOutboxDir(stateDir);
80242
- const path2 = join34(dir, JOURNAL_FILE);
80768
+ const path2 = join35(dir, JOURNAL_FILE);
80243
80769
  try {
80244
80770
  mkdirSync29(dir, { recursive: true });
80245
- appendFileSync5(path2, JSON.stringify(entry) + `
80771
+ appendFileSync6(path2, JSON.stringify(entry) + `
80246
80772
  `, { mode: 384 });
80247
80773
  compactJournalIfLarge(path2);
80248
80774
  } catch {}
@@ -80265,7 +80791,7 @@ function clearOutboxRecord(nonce, stateDir) {
80265
80791
  const dir = resolveOutboxDir(stateDir);
80266
80792
  for (const suffix of [".json", ".sending"]) {
80267
80793
  try {
80268
- unlinkSync14(join34(dir, `${nonce}${suffix}`));
80794
+ unlinkSync14(join35(dir, `${nonce}${suffix}`));
80269
80795
  } catch {}
80270
80796
  }
80271
80797
  }
@@ -80335,9 +80861,9 @@ import {
80335
80861
  readFileSync as readFileSync27,
80336
80862
  renameSync as renameSync15,
80337
80863
  writeFileSync as writeFileSync26,
80338
- appendFileSync as appendFileSync6
80864
+ appendFileSync as appendFileSync7
80339
80865
  } from "node:fs";
80340
- import { join as join35 } from "node:path";
80866
+ import { join as join36 } from "node:path";
80341
80867
 
80342
80868
  // hooks/narration-classify.mjs
80343
80869
  import { createHash as createHash5 } from "node:crypto";
@@ -80408,7 +80934,7 @@ var SHOWN_LEDGER_FILE = "shown-ledger.jsonl";
80408
80934
  var SHOWN_LEDGER_KEEP = 2000;
80409
80935
  var SHOWN_LEDGER_ROTATE_AT = 4000;
80410
80936
  function shownLedgerPath(stateDir) {
80411
- return join35(resolveOutboxDir(stateDir), SHOWN_LEDGER_FILE);
80937
+ return join36(resolveOutboxDir(stateDir), SHOWN_LEDGER_FILE);
80412
80938
  }
80413
80939
  function appendShownBlock(turnNonce, text4, stateDir, now = Date.now()) {
80414
80940
  if (turnNonce == null || turnNonce === "")
@@ -80421,7 +80947,7 @@ function appendShownBlock(turnNonce, text4, stateDir, now = Date.now()) {
80421
80947
  try {
80422
80948
  mkdirSync30(dir, { recursive: true });
80423
80949
  const entry = { turnNonce, hash: ledgerHashHex(trimmed), ts: now };
80424
- appendFileSync6(path2, JSON.stringify(entry) + `
80950
+ appendFileSync7(path2, JSON.stringify(entry) + `
80425
80951
  `, { mode: 384 });
80426
80952
  compactIfLarge(path2);
80427
80953
  } catch {}
@@ -80643,6 +81169,33 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
80643
81169
  clearOutboxRecord(nonce, stateDir);
80644
81170
  }
80645
81171
 
81172
+ // gateway/answer-thread-resolve.ts
81173
+ function isCrossChatAnchor(targetChatId, anchorChatId) {
81174
+ if (targetChatId == null || targetChatId === "")
81175
+ return false;
81176
+ if (anchorChatId == null || anchorChatId === "")
81177
+ return false;
81178
+ return String(anchorChatId) !== String(targetChatId);
81179
+ }
81180
+
81181
+ // gateway/reply-route-log.ts
81182
+ function formatReplyRouteLog(i) {
81183
+ const originCrossChat = isCrossChatAnchor(i.chatId, i.originTurn?.sessionChatId);
81184
+ const liveCrossChat = isCrossChatAnchor(i.chatId, i.liveTurn?.sessionChatId);
81185
+ const crossChatDropped = originCrossChat || liveCrossChat;
81186
+ const originAnchor = originCrossChat ? null : i.originTurn;
81187
+ const liveAnchor = liveCrossChat ? null : i.liveTurn;
81188
+ const explicit = i.explicitThreadId;
81189
+ const via = i.frameworkTopicAuthority ? originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor != null ? "live" : explicit != null ? "explicit" : i.recovered != null ? "recovered" : "none" : explicit != null ? "explicit" : originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor?.sessionThreadId != null ? "live" : i.recovered != null ? "recovered" : "none";
81190
+ const explicitOverridden = i.frameworkTopicAuthority && explicit != null && (originAnchor != null || liveAnchor != null) && i.threadId !== explicit;
81191
+ const ownerTurn = originAnchor ?? i.recovered ?? liveAnchor;
81192
+ const isSupergroup = i.chatId.startsWith("-100");
81193
+ const unrouted = isSupergroup && i.threadId == null && ownerTurn == null && !crossChatDropped;
81194
+ const misrouteRisk = isSupergroup && via === "live" && i.hasDifferentThreadedRecentTurn(liveAnchor?.sessionThreadId);
81195
+ return `telegram gateway: reply-route surface=${i.surface} chat=${i.chatId} ` + `resolved_thread=${i.threadId ?? "-"} via=${via} late=${liveAnchor == null} ` + `originTurn=${ownerTurn?.turnId ?? "-"} origin_thread=${ownerTurn?.sessionThreadId ?? "-"}` + (via === "recovered" ? " RECOVERED" : "") + (via === "quoted" ? " QUOTED(framework-origin)" : "") + (unrouted ? " UNROUTED(supergroup\u2192no-topic)" : "") + (misrouteRisk ? " MISROUTE_RISK(no-echo\u2192live-successor)" : "") + (crossChatDropped ? ` CROSS_CHAT_ANCHOR_DROPPED(target=${i.chatId}` + (originCrossChat ? `,origin_chat=${i.originTurn?.sessionChatId},origin_thread=${i.originTurn?.sessionThreadId ?? "-"}` : "") + (liveCrossChat ? `,live_chat=${i.liveTurn?.sessionChatId},live_thread=${i.liveTurn?.sessionThreadId ?? "-"}` : "") + `,routed\u2192${i.threadId ?? "-"})` : "") + (explicitOverridden ? ` EXPLICIT_OVERRIDDEN(model\u2192${explicit},routed\u2192${i.threadId ?? "-"})` : "") + `
81196
+ `;
81197
+ }
81198
+
80646
81199
  // gateway/flood-reply-queue.ts
80647
81200
  var FLOOD_QUEUED_SOURCE = "flood-deferred-reply";
80648
81201
  function isFloodRejection(err) {
@@ -81229,7 +81782,7 @@ function monotonicNowMs2() {
81229
81782
 
81230
81783
  // silent-end.ts
81231
81784
  import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync27, unlinkSync as unlinkSync15, mkdirSync as mkdirSync31 } from "node:fs";
81232
- import { dirname as dirname17, join as join36 } from "node:path";
81785
+ import { dirname as dirname17, join as join37 } from "node:path";
81233
81786
  import { homedir as homedir10 } from "node:os";
81234
81787
  var SILENT_END_MAX_RETRIES2 = 2;
81235
81788
  var SILENT_END_STALE_RECORD_MAX_AGE_MS2 = 30 * 60000;
@@ -81244,10 +81797,10 @@ function resolveStateDir3(deps) {
81244
81797
  if (env != null && env !== "")
81245
81798
  return env;
81246
81799
  const home2 = process.env.HOME ?? homedir10();
81247
- return join36(home2, ".claude", "channels", "telegram");
81800
+ return join37(home2, ".claude", "channels", "telegram");
81248
81801
  }
81249
81802
  function resolveStatePath3(deps) {
81250
- return join36(resolveStateDir3(deps), "silent-end-pending.json");
81803
+ return join37(resolveStateDir3(deps), "silent-end-pending.json");
81251
81804
  }
81252
81805
  function emitLog2(deps, line) {
81253
81806
  if (deps?.log != null)
@@ -81775,6 +82328,7 @@ async function sendReply(deps, req) {
81775
82328
  const protectContent = args.protect_content === true;
81776
82329
  const quoteText = args.quote_text;
81777
82330
  const access = loadAccess();
82331
+ captureSpeechText(text4);
81778
82332
  const voiceOutPlan = resolveVoiceOutPlan(access.voice_out, text4);
81779
82333
  const configParseMode = access.parseMode ?? "html";
81780
82334
  const format = args.format ?? configParseMode;
@@ -81851,7 +82405,24 @@ ${url}`;
81851
82405
  replyRoutedOriginTurn = originTurn ?? null;
81852
82406
  threadId = resolveAnswerThreadWithLog(chat_id, Number.isFinite(explicit) ? explicit : undefined, originTurn, originTurn == null ? null : echoedTurn != null ? "echo" : "quoted", turn, "reply");
81853
82407
  } else {
81854
- threadId = resolveThreadId(chat_id, args.message_thread_id ?? (turn?.sessionThreadId != null ? turn.sessionThreadId : undefined));
82408
+ const anchorCrossChat = isCrossChatAnchor(chat_id, turn?.sessionChatId);
82409
+ const anchorThreadId = anchorCrossChat ? undefined : turn?.sessionThreadId;
82410
+ threadId = resolveThreadId(chat_id, args.message_thread_id ?? (anchorThreadId != null ? anchorThreadId : undefined));
82411
+ if (anchorCrossChat) {
82412
+ const explicitRaw = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
82413
+ process.stderr.write(formatReplyRouteLog({
82414
+ surface: "reply",
82415
+ chatId: chat_id,
82416
+ threadId,
82417
+ explicitThreadId: Number.isFinite(explicitRaw) ? explicitRaw : undefined,
82418
+ originTurn: null,
82419
+ originVia: null,
82420
+ liveTurn: turn,
82421
+ recovered: null,
82422
+ frameworkTopicAuthority: false,
82423
+ hasDifferentThreadedRecentTurn: () => false
82424
+ }));
82425
+ }
81855
82426
  }
81856
82427
  let antecedentFromQuoteOptInDefault = false;
81857
82428
  if (reply_to == null && quoteOptIn && HISTORY_ENABLED) {
@@ -83102,20 +83673,20 @@ var INTERRUPTED_VIA = new Set([
83102
83673
  init_rich_send();
83103
83674
 
83104
83675
  // runtime-metrics.ts
83105
- import { mkdirSync as mkdirSync32, appendFileSync as appendFileSync7 } from "node:fs";
83106
- import { dirname as dirname18, join as join38 } from "node:path";
83676
+ import { mkdirSync as mkdirSync32, appendFileSync as appendFileSync8 } from "node:fs";
83677
+ import { dirname as dirname18, join as join39 } from "node:path";
83107
83678
  function resolveJsonlPath2() {
83108
83679
  const override = process.env.SWITCHROOM_RUNTIME_METRICS_PATH;
83109
83680
  if (override && override.trim() !== "")
83110
83681
  return override.trim();
83111
83682
  const base = process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent";
83112
- return join38(base, "runtime-metrics.jsonl");
83683
+ return join39(base, "runtime-metrics.jsonl");
83113
83684
  }
83114
83685
  function appendJsonl2(line) {
83115
83686
  const path2 = resolveJsonlPath2();
83116
83687
  try {
83117
83688
  mkdirSync32(dirname18(path2), { recursive: true });
83118
- appendFileSync7(path2, line + `
83689
+ appendFileSync8(path2, line + `
83119
83690
  `, "utf-8");
83120
83691
  } catch (err) {
83121
83692
  process.stderr.write(`runtime-metrics: jsonl write failed: ${err.message}
@@ -83659,18 +84230,18 @@ import {
83659
84230
  unlinkSync as unlinkSync16,
83660
84231
  utimesSync
83661
84232
  } from "node:fs";
83662
- import { join as join39 } from "node:path";
84233
+ import { join as join40 } from "node:path";
83663
84234
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
83664
84235
  var TURN_ACTIVE_HARD_TTL_MS = 10 * 60000;
83665
84236
  function writeTurnActiveMarker(stateDir, marker) {
83666
84237
  try {
83667
84238
  mkdirStateSync(stateDir, { recursive: true });
83668
- writeStateFileSync(join39(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
84239
+ writeStateFileSync(join40(stateDir, TURN_ACTIVE_MARKER_FILE), JSON.stringify(marker, null, 2) + `
83669
84240
  `, { mode: 384 });
83670
84241
  } catch {}
83671
84242
  }
83672
84243
  function touchTurnActiveMarker(stateDir) {
83673
- const path2 = join39(stateDir, TURN_ACTIVE_MARKER_FILE);
84244
+ const path2 = join40(stateDir, TURN_ACTIVE_MARKER_FILE);
83674
84245
  if (!existsSync31(path2))
83675
84246
  return;
83676
84247
  const now = new Date;
@@ -83685,11 +84256,11 @@ function touchTurnActiveMarker(stateDir) {
83685
84256
  }
83686
84257
  function removeTurnActiveMarker(stateDir) {
83687
84258
  try {
83688
- unlinkSync16(join39(stateDir, TURN_ACTIVE_MARKER_FILE));
84259
+ unlinkSync16(join40(stateDir, TURN_ACTIVE_MARKER_FILE));
83689
84260
  } catch {}
83690
84261
  }
83691
84262
  function readTurnActiveMarkerAgeMs(stateDir, now) {
83692
- const path2 = join39(stateDir, TURN_ACTIVE_MARKER_FILE);
84263
+ const path2 = join40(stateDir, TURN_ACTIVE_MARKER_FILE);
83693
84264
  try {
83694
84265
  const st = statSync13(path2);
83695
84266
  return (now ?? Date.now()) - st.mtimeMs;
@@ -86011,10 +86582,10 @@ function resolveAgentDirFromEnv() {
86011
86582
 
86012
86583
  // active-reactions.ts
86013
86584
  import { readFileSync as readFileSync30, writeFileSync as writeFileSync28, renameSync as renameSync16, existsSync as existsSync32, unlinkSync as unlinkSync17 } from "node:fs";
86014
- import { join as join40 } from "node:path";
86585
+ import { join as join41 } from "node:path";
86015
86586
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
86016
86587
  function reactionsPath(agentDir) {
86017
- return join40(agentDir, ACTIVE_REACTIONS_FILENAME);
86588
+ return join41(agentDir, ACTIVE_REACTIONS_FILENAME);
86018
86589
  }
86019
86590
  function readActiveReactions(agentDir) {
86020
86591
  const p = reactionsPath(agentDir);
@@ -86079,10 +86650,10 @@ function clearActiveReactions(agentDir) {
86079
86650
 
86080
86651
  // active-reactions.ts
86081
86652
  import { readFileSync as readFileSync31, writeFileSync as writeFileSync29, renameSync as renameSync17, existsSync as existsSync33, unlinkSync as unlinkSync18 } from "node:fs";
86082
- import { join as join41 } from "node:path";
86653
+ import { join as join42 } from "node:path";
86083
86654
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
86084
86655
  function reactionsPath2(agentDir) {
86085
- return join41(agentDir, ACTIVE_REACTIONS_FILENAME2);
86656
+ return join42(agentDir, ACTIVE_REACTIONS_FILENAME2);
86086
86657
  }
86087
86658
  function readActiveReactions2(agentDir) {
86088
86659
  const p = reactionsPath2(agentDir);
@@ -86962,12 +87533,12 @@ async function approvalRecord(args, opts) {
86962
87533
 
86963
87534
  // quota-check.ts
86964
87535
  import { readFileSync as readFileSync32, existsSync as existsSync34 } from "fs";
86965
- import { join as join42 } from "path";
87536
+ import { join as join43 } from "path";
86966
87537
  var OAUTH_BETA2 = "oauth-2025-04-20";
86967
87538
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
86968
87539
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
86969
87540
  function readOauthToken2(claudeConfigDir) {
86970
- const tokenFile = join42(claudeConfigDir, ".oauth-token");
87541
+ const tokenFile = join43(claudeConfigDir, ".oauth-token");
86971
87542
  if (!existsSync34(tokenFile))
86972
87543
  return null;
86973
87544
  try {
@@ -88357,7 +88928,7 @@ async function menuWithBannerStatic(deps, banner) {
88357
88928
 
88358
88929
  // gateway/session-model-file.ts
88359
88930
  import { readFileSync as readFileSync33, writeFileSync as writeFileSync30, renameSync as renameSync18, rmSync as rmSync7 } from "node:fs";
88360
- import { join as join43 } from "node:path";
88931
+ import { join as join44 } from "node:path";
88361
88932
  var SESSION_MODEL_FILE = ".session-model";
88362
88933
  var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
88363
88934
  var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
@@ -88379,28 +88950,28 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
88379
88950
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
88380
88951
  }
88381
88952
  try {
88382
- rmSync7(join43(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
88953
+ rmSync7(join44(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
88383
88954
  } catch {}
88384
- atomicWrite(join43(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
88955
+ atomicWrite(join44(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
88385
88956
  }
88386
88957
  function readSessionModelFileRaw(agentDir) {
88387
88958
  try {
88388
- return readFileSync33(join43(agentDir, SESSION_MODEL_FILE), "utf8");
88959
+ return readFileSync33(join44(agentDir, SESSION_MODEL_FILE), "utf8");
88389
88960
  } catch {
88390
88961
  return null;
88391
88962
  }
88392
88963
  }
88393
88964
  function clearSessionModelFile(agentDir) {
88394
88965
  try {
88395
- rmSync7(join43(agentDir, SESSION_MODEL_FILE), { force: true });
88966
+ rmSync7(join44(agentDir, SESSION_MODEL_FILE), { force: true });
88396
88967
  } catch {}
88397
88968
  }
88398
88969
  function consumeSessionModelCarrierOnHealthyBoot(agentDir) {
88399
88970
  try {
88400
- rmSync7(join43(agentDir, SESSION_MODEL_FILE), { force: true });
88971
+ rmSync7(join44(agentDir, SESSION_MODEL_FILE), { force: true });
88401
88972
  } catch {}
88402
88973
  try {
88403
- rmSync7(join43(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
88974
+ rmSync7(join44(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
88404
88975
  } catch {}
88405
88976
  }
88406
88977
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -88409,12 +88980,12 @@ function restoreSessionModelFileRaw(agentDir, raw) {
88409
88980
  return;
88410
88981
  }
88411
88982
  try {
88412
- atomicWrite(join43(agentDir, SESSION_MODEL_FILE), raw);
88983
+ atomicWrite(join44(agentDir, SESSION_MODEL_FILE), raw);
88413
88984
  } catch {}
88414
88985
  }
88415
88986
  function readConfiguredDefaultModel(agentDir) {
88416
88987
  try {
88417
- const v = readFileSync33(join43(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
88988
+ const v = readFileSync33(join44(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
88418
88989
  return v.length > 0 ? v : null;
88419
88990
  } catch {
88420
88991
  return null;
@@ -88426,12 +88997,12 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
88426
88997
  if (!EFFORT_LEVEL_RE.test(level)) {
88427
88998
  throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
88428
88999
  }
88429
- atomicWrite(join43(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
89000
+ atomicWrite(join44(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
88430
89001
  `);
88431
89002
  }
88432
89003
  function clearSessionEffortFile(agentDir) {
88433
89004
  try {
88434
- rmSync7(join43(agentDir, SESSION_EFFORT_FILE), { force: true });
89005
+ rmSync7(join44(agentDir, SESSION_EFFORT_FILE), { force: true });
88435
89006
  } catch {}
88436
89007
  }
88437
89008
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -88454,13 +89025,13 @@ function writePremiumRecoveryFile(agentDir, premiumModel, chats) {
88454
89025
  if (clean.length === 0) {
88455
89026
  throw new Error("refusing to persist premium-recovery marker with no chats to notify");
88456
89027
  }
88457
- atomicWrite(join43(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
89028
+ atomicWrite(join44(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
88458
89029
  `);
88459
89030
  }
88460
89031
  function readPremiumRecoveryFile(agentDir) {
88461
89032
  let raw;
88462
89033
  try {
88463
- raw = readFileSync33(join43(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
89034
+ raw = readFileSync33(join44(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
88464
89035
  } catch {
88465
89036
  return null;
88466
89037
  }
@@ -88473,7 +89044,7 @@ function readPremiumRecoveryFile(agentDir) {
88473
89044
  }
88474
89045
  function clearPremiumRecoveryFile(agentDir) {
88475
89046
  try {
88476
- rmSync7(join43(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
89047
+ rmSync7(join44(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
88477
89048
  } catch {}
88478
89049
  }
88479
89050
 
@@ -88867,7 +89438,7 @@ init_merge();
88867
89438
  init_timezone();
88868
89439
 
88869
89440
  // ../src/cli/agent-config.ts
88870
- import { join as join45 } from "node:path";
89441
+ import { join as join46 } from "node:path";
88871
89442
  import { homedir as homedir11 } from "node:os";
88872
89443
 
88873
89444
  // ../src/cli/helpers.ts
@@ -88888,12 +89459,12 @@ var WEBKITE_VAULT_KEYS = new Set([
88888
89459
  init_overlay_loader();
88889
89460
 
88890
89461
  // ../src/cli/agent-config.ts
88891
- var AUDIT_ROOT = join45(homedir11(), ".switchroom", "audit");
89462
+ var AUDIT_ROOT = join46(homedir11(), ".switchroom", "audit");
88892
89463
 
88893
89464
  // ../src/agents/profiles.ts
88894
89465
  var import_handlebars = __toESM(require_lib(), 1);
88895
89466
  import { readFileSync as readFileSync36, writeFileSync as writeFileSync32, existsSync as existsSync37, readdirSync as readdirSync9, statSync as statSync14, copyFileSync, mkdirSync as mkdirSync33, realpathSync as realpathSync4 } from "node:fs";
88896
- import { resolve as resolve8, join as join46, sep as pathSep } from "node:path";
89467
+ import { resolve as resolve8, join as join47, sep as pathSep } from "node:path";
88897
89468
  function resolveProfilesRootDetailed() {
88898
89469
  return resolveShippedAsset(PROFILES_ASSET, {
88899
89470
  bundleDir: import.meta.dirname,
@@ -88912,7 +89483,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
88912
89483
  var SHARED_FRAGMENTS_DIR = resolve8(PROFILES_ROOT, "_shared");
88913
89484
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol", "local-time"];
88914
89485
  for (const name of SHARED_FRAGMENTS) {
88915
- const fragPath = join46(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
89486
+ const fragPath = join47(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
88916
89487
  if (existsSync37(fragPath)) {
88917
89488
  import_handlebars.default.registerPartial(name, readFileSync36(fragPath, "utf-8"));
88918
89489
  }
@@ -90864,7 +91435,7 @@ function numField(obj, key) {
90864
91435
 
90865
91436
  // gateway/context-occupancy.ts
90866
91437
  import { mkdirSync as mkdirSync34, writeFileSync as writeFileSync33 } from "node:fs";
90867
- import { join as join47 } from "node:path";
91438
+ import { join as join48 } from "node:path";
90868
91439
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
90869
91440
  var TIGHT_FRACTION = 0.8;
90870
91441
  function buildContextOccupancy(occupancy, cap, now) {
@@ -90887,7 +91458,7 @@ function buildContextOccupancy(occupancy, cap, now) {
90887
91458
  }
90888
91459
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
90889
91460
  try {
90890
- const path2 = join47(stateDir, CONTEXT_OCCUPANCY_FILENAME);
91461
+ const path2 = join48(stateDir, CONTEXT_OCCUPANCY_FILENAME);
90891
91462
  (deps?.mkdir ?? ((p, o) => mkdirSync34(p, o)))(stateDir, { recursive: true });
90892
91463
  (deps?.writeFile ?? ((p, d) => writeFileSync33(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
90893
91464
  `);
@@ -91025,17 +91596,17 @@ function idleDurationToMs(raw) {
91025
91596
  init_state_owner();
91026
91597
  import { readFileSync as readFileSync39, mkdirSync as mkdirSync35 } from "node:fs";
91027
91598
  import { homedir as homedir13 } from "node:os";
91028
- import { join as join48 } from "node:path";
91599
+ import { join as join49 } from "node:path";
91029
91600
  function emptyPrivacyState() {
91030
91601
  return { version: 1, intervals: [] };
91031
91602
  }
91032
91603
  var PRIVATE_ON_REPLY = "\uD83D\uDD12 Private mode ON \u2014 memory writing paused. Nothing said until /public is stored.";
91033
91604
  var PUBLIC_REPLY = "\uD83D\uDD13 Public mode \u2014 memory writing resumed. The private stretch was excluded from memory.";
91034
91605
  function resolvePrivacyStateDir() {
91035
- return process.env.TELEGRAM_STATE_DIR ?? join48(homedir13(), ".claude", "channels", "telegram");
91606
+ return process.env.TELEGRAM_STATE_DIR ?? join49(homedir13(), ".claude", "channels", "telegram");
91036
91607
  }
91037
91608
  function privacyStatePath(stateDir = resolvePrivacyStateDir()) {
91038
- return join48(stateDir, "privacy-state.json");
91609
+ return join49(stateDir, "privacy-state.json");
91039
91610
  }
91040
91611
  function isInterval(v) {
91041
91612
  if (v === null || typeof v !== "object")
@@ -91091,22 +91662,22 @@ function closePrivateInterval(now = new Date, stateDir = resolvePrivacyStateDir(
91091
91662
 
91092
91663
  // gateway/privacy-reset.ts
91093
91664
  import { existsSync as existsSync40 } from "node:fs";
91094
- import { join as join50 } from "node:path";
91665
+ import { join as join51 } from "node:path";
91095
91666
 
91096
91667
  // gateway/privacy-state.ts
91097
91668
  init_state_owner();
91098
91669
  import { readFileSync as readFileSync40, mkdirSync as mkdirSync36 } from "node:fs";
91099
91670
  import { homedir as homedir14 } from "node:os";
91100
- import { join as join49 } from "node:path";
91671
+ import { join as join50 } from "node:path";
91101
91672
  function emptyPrivacyState2() {
91102
91673
  return { version: 1, intervals: [] };
91103
91674
  }
91104
91675
  var SESSION_RESET_ALERT = "\uD83D\uDD13 New session \u2014 memory writing is ON by default. Private mode from the previous session was reset.";
91105
91676
  function resolvePrivacyStateDir2() {
91106
- return process.env.TELEGRAM_STATE_DIR ?? join49(homedir14(), ".claude", "channels", "telegram");
91677
+ return process.env.TELEGRAM_STATE_DIR ?? join50(homedir14(), ".claude", "channels", "telegram");
91107
91678
  }
91108
91679
  function privacyStatePath2(stateDir = resolvePrivacyStateDir2()) {
91109
- return join49(stateDir, "privacy-state.json");
91680
+ return join50(stateDir, "privacy-state.json");
91110
91681
  }
91111
91682
  function isInterval2(v) {
91112
91683
  if (v === null || typeof v !== "object")
@@ -91170,7 +91741,7 @@ function bootRestoresTranscript(opts) {
91170
91741
  return opts.resumeMode === "continue" || opts.resumeMode === "auto";
91171
91742
  }
91172
91743
  function isContinueRestoreBoot(agentDir) {
91173
- const forceFresh = process.env.SWITCHROOM_FORCE_FRESH === "1" || agentDir != null && existsSync40(join50(agentDir, ".force-fresh-session"));
91744
+ const forceFresh = process.env.SWITCHROOM_FORCE_FRESH === "1" || agentDir != null && existsSync40(join51(agentDir, ".force-fresh-session"));
91174
91745
  return bootRestoresTranscript({
91175
91746
  resumeMode: process.env.SWITCHROOM_RESUME_MODE,
91176
91747
  forceFresh
@@ -92261,7 +92832,7 @@ function reseedSweepLedger(path2, fs2, log = (l) => process.stderr.write(l)) {
92261
92832
  // ../src/util/state-owner.ts
92262
92833
  init_atomic();
92263
92834
  import {
92264
- appendFileSync as appendFileSync8,
92835
+ appendFileSync as appendFileSync9,
92265
92836
  closeSync as closeSync11,
92266
92837
  constants as constants5,
92267
92838
  existsSync as existsSync42,
@@ -92273,7 +92844,7 @@ import {
92273
92844
  statSync as statSync15,
92274
92845
  writeFileSync as writeFileSync34
92275
92846
  } from "node:fs";
92276
- import { dirname as dirname21, join as join51, relative as relative2, sep as sep5 } from "node:path";
92847
+ import { dirname as dirname21, join as join52, relative as relative2, sep as sep5 } from "node:path";
92277
92848
  var NOFOLLOW2 = constants5.O_NOFOLLOW ?? 0;
92278
92849
  var ownerCache2 = new Map;
92279
92850
  var loggedFailure2 = false;
@@ -92347,7 +92918,7 @@ function mkdirStateSync2(path2, options = { recursive: true }, log = defaultLog3
92347
92918
  for (const part of tail.split(sep5)) {
92348
92919
  if (part === "")
92349
92920
  continue;
92350
- current = join51(current, part);
92921
+ current = join52(current, part);
92351
92922
  adoptStateOwnership2(current, owner, log);
92352
92923
  }
92353
92924
  }
@@ -92389,11 +92960,11 @@ function writeStateFileSync2(path2, data, options, log = defaultLog3) {
92389
92960
  function appendStateFileSync(path2, data, options, log = defaultLog3) {
92390
92961
  const owner = resolveStateOwner2(dirname21(path2));
92391
92962
  if (!owner) {
92392
- appendFileSync8(path2, data, options);
92963
+ appendFileSync9(path2, data, options);
92393
92964
  return;
92394
92965
  }
92395
92966
  const existed = existsSync42(path2);
92396
- appendFileSync8(path2, data, options);
92967
+ appendFileSync9(path2, data, options);
92397
92968
  if (!existed)
92398
92969
  adoptStateOwnership2(path2, owner, log);
92399
92970
  }
@@ -92449,7 +93020,7 @@ function reconcileStateDirOwnership(dir, opts = {}) {
92449
93020
  result.symlinksSkipped++;
92450
93021
  continue;
92451
93022
  }
92452
- const child = join51(current, entry.name);
93023
+ const child = join52(current, entry.name);
92453
93024
  result.scanned++;
92454
93025
  if (adoptAndReport(child, owner, log))
92455
93026
  result.adopted++;
@@ -92614,13 +93185,13 @@ function startWebhookIngestServer(opts) {
92614
93185
  }
92615
93186
 
92616
93187
  // ../src/web/webhook-gateway-record.ts
92617
- import { appendFileSync as appendFileSync10, mkdirSync as mkdirSync40 } from "fs";
92618
- import { join as join55 } from "path";
93188
+ import { appendFileSync as appendFileSync11, mkdirSync as mkdirSync40 } from "fs";
93189
+ import { join as join56 } from "path";
92619
93190
  import { homedir as homedir16 } from "os";
92620
93191
 
92621
93192
  // ../src/web/webhook-handler.ts
92622
- import { appendFileSync as appendFileSync9, existsSync as existsSync45, mkdirSync as mkdirSync38, readFileSync as readFileSync41, writeFileSync as writeFileSync35 } from "fs";
92623
- import { join as join53 } from "path";
93193
+ import { appendFileSync as appendFileSync10, existsSync as existsSync45, mkdirSync as mkdirSync38, readFileSync as readFileSync41, writeFileSync as writeFileSync35 } from "fs";
93194
+ import { join as join54 } from "path";
92624
93195
 
92625
93196
  // ../src/util/log-rotation.ts
92626
93197
  import * as fs2 from "node:fs";
@@ -92982,8 +93553,8 @@ var agentDedupCache = new Map;
92982
93553
  function createFileDedupStore(resolveAgentDir) {
92983
93554
  return {
92984
93555
  check(agent, deliveryId, now) {
92985
- const telegramDir = join53(resolveAgentDir(agent), "telegram");
92986
- const filePath = join53(telegramDir, "webhook-dedup.json");
93556
+ const telegramDir = join54(resolveAgentDir(agent), "telegram");
93557
+ const filePath = join54(telegramDir, "webhook-dedup.json");
92987
93558
  if (!agentDedupCache.has(agent)) {
92988
93559
  agentDedupCache.set(agent, loadDedupFile(filePath));
92989
93560
  }
@@ -93005,7 +93576,7 @@ var throttleIssueWindow = new Map;
93005
93576
 
93006
93577
  // ../src/web/webhook-dispatch.ts
93007
93578
  import { existsSync as existsSync46, mkdirSync as mkdirSync39, readFileSync as readFileSync42, writeFileSync as writeFileSync36 } from "fs";
93008
- import { join as join54 } from "path";
93579
+ import { join as join55 } from "path";
93009
93580
  import { homedir as homedir15 } from "os";
93010
93581
 
93011
93582
  // ../src/agent-scheduler/ipc-client.ts
@@ -93325,8 +93896,8 @@ function createFileCooldownStore(resolveAgentDir) {
93325
93896
  isCoolingDown(agent, key, cooldownMs, now) {
93326
93897
  if (cooldownMs <= 0)
93327
93898
  return false;
93328
- const telegramDir = join54(resolveAgentDir(agent), "telegram");
93329
- const filePath = join54(telegramDir, "webhook-cooldown.json");
93899
+ const telegramDir = join55(resolveAgentDir(agent), "telegram");
93900
+ const filePath = join55(telegramDir, "webhook-cooldown.json");
93330
93901
  if (!cache.has(agent)) {
93331
93902
  cache.set(agent, loadCooldownFile(filePath));
93332
93903
  }
@@ -93383,9 +93954,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
93383
93954
  }
93384
93955
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
93385
93956
  const log = deps.log ?? ((s) => process.stderr.write(s));
93386
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join54(homedir15(), ".switchroom", "agents", a));
93957
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join55(homedir15(), ".switchroom", "agents", a));
93387
93958
  const now = (deps.now ?? Date.now)();
93388
- const socketPath = join54(resolveAgentDir(agent), "telegram", "gateway.sock");
93959
+ const socketPath = join55(resolveAgentDir(agent), "telegram", "gateway.sock");
93389
93960
  const inbound = {
93390
93961
  type: "inbound",
93391
93962
  chatId: ctx.chatId,
@@ -93457,7 +94028,7 @@ function evaluateDispatch(args, deps = {}) {
93457
94028
  const log = deps.log ?? ((s) => process.stderr.write(s));
93458
94029
  const now = (deps.now ?? Date.now)();
93459
94030
  const nowDate = deps.nowDate ?? (() => new Date(now));
93460
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join54(homedir15(), ".switchroom", "agents", a));
94031
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join55(homedir15(), ".switchroom", "agents", a));
93461
94032
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
93462
94033
  if (!DISPATCH_SOURCES.includes(args.source))
93463
94034
  return 0;
@@ -93535,10 +94106,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
93535
94106
  function recordWebhookEvent(rec, deps = {}) {
93536
94107
  const log = deps.log ?? ((s) => process.stderr.write(s));
93537
94108
  const now = rec.ts || (deps.now ?? Date.now)();
93538
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join55(homedir16(), ".switchroom", "agents", a));
94109
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join56(homedir16(), ".switchroom", "agents", a));
93539
94110
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
93540
94111
  const agent = rec.agent;
93541
- const telegramDir = join55(resolveAgentDir(agent), "telegram");
94112
+ const telegramDir = join56(resolveAgentDir(agent), "telegram");
93542
94113
  if (rec.source === "github" && rec.delivery_id) {
93543
94114
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
93544
94115
  if (originalTs !== undefined) {
@@ -93547,7 +94118,7 @@ function recordWebhookEvent(rec, deps = {}) {
93547
94118
  return { status: "deduped", ts: originalTs };
93548
94119
  }
93549
94120
  }
93550
- const logPath = join55(telegramDir, "webhook-events.jsonl");
94121
+ const logPath = join56(telegramDir, "webhook-events.jsonl");
93551
94122
  try {
93552
94123
  mkdirSync40(telegramDir, { recursive: true });
93553
94124
  rotateWebhookLogIfNeeded(logPath);
@@ -93558,7 +94129,7 @@ function recordWebhookEvent(rec, deps = {}) {
93558
94129
  rendered_text: rec.rendered_text,
93559
94130
  payload: rec.payload
93560
94131
  };
93561
- appendFileSync10(logPath, JSON.stringify(record) + `
94132
+ appendFileSync11(logPath, JSON.stringify(record) + `
93562
94133
  `, { mode: 384 });
93563
94134
  } catch (err) {
93564
94135
  log(`webhook-gateway: agent='${agent}' source='${rec.source}' write failed: ${err.message}
@@ -96589,27 +97160,120 @@ class CurrentTurnMap {
96589
97160
  }
96590
97161
 
96591
97162
  // gateway/answer-thread-resolve.ts
97163
+ function isCrossChatAnchor2(targetChatId, anchorChatId) {
97164
+ if (targetChatId == null || targetChatId === "")
97165
+ return false;
97166
+ if (anchorChatId == null || anchorChatId === "")
97167
+ return false;
97168
+ return String(anchorChatId) !== String(targetChatId);
97169
+ }
96592
97170
  function resolveAnswerThreadId(input) {
97171
+ const originCrossChat = isCrossChatAnchor2(input.targetChatId, input.originChatId);
97172
+ const liveCrossChat = isCrossChatAnchor2(input.targetChatId, input.liveChatId);
97173
+ const originResolved = input.originResolved && !originCrossChat;
97174
+ const originThreadId = originCrossChat ? undefined : input.originThreadId;
97175
+ const liveTurnPresent = input.liveTurnPresent === true && !liveCrossChat;
97176
+ const liveThreadId = liveCrossChat ? undefined : input.liveThreadId;
96593
97177
  if (input.frameworkTopicAuthority === false) {
96594
97178
  if (input.explicitThreadId != null)
96595
97179
  return input.explicitThreadId;
96596
- if (input.originResolved)
96597
- return input.originThreadId;
96598
- if (input.liveThreadId != null)
96599
- return input.liveThreadId;
97180
+ if (originResolved)
97181
+ return originThreadId;
97182
+ if (liveThreadId != null)
97183
+ return liveThreadId;
96600
97184
  if (input.lastEndedResolvedForChat)
96601
97185
  return input.lastEndedThreadIdForChat;
96602
- return input.liveThreadId;
97186
+ return liveThreadId;
96603
97187
  }
96604
- if (input.originResolved)
96605
- return input.originThreadId;
96606
- if (input.liveTurnPresent)
96607
- return input.liveThreadId;
97188
+ if (originResolved)
97189
+ return originThreadId;
97190
+ if (liveTurnPresent)
97191
+ return liveThreadId;
96608
97192
  if (input.explicitThreadId != null)
96609
97193
  return input.explicitThreadId;
96610
97194
  if (input.lastEndedResolvedForChat)
96611
97195
  return input.lastEndedThreadIdForChat;
96612
- return input.liveThreadId;
97196
+ return liveThreadId;
97197
+ }
97198
+
97199
+ // gateway/answer-route-overrides.ts
97200
+ function keyFor(chatId, threadId) {
97201
+ return `${chatId}:${threadId ?? "_"}`;
97202
+ }
97203
+ function createAnswerRouteOverrides(maxKeys = 64, maxPerKey = 8) {
97204
+ const byKey = new Map;
97205
+ return {
97206
+ note(args) {
97207
+ const overridden = args.enabled && args.explicitThreadId != null && args.anchored && args.routedThreadId !== args.explicitThreadId;
97208
+ if (!overridden)
97209
+ return false;
97210
+ const key = keyFor(args.chatId, args.explicitThreadId);
97211
+ const list2 = byKey.get(key) ?? [];
97212
+ list2.push({ routedThreadId: args.routedThreadId ?? null, atMs: args.nowMs });
97213
+ while (list2.length > maxPerKey)
97214
+ list2.shift();
97215
+ byKey.set(key, list2);
97216
+ while (byKey.size > maxKeys) {
97217
+ const oldest = byKey.keys().next().value;
97218
+ if (oldest === undefined)
97219
+ break;
97220
+ byKey.delete(oldest);
97221
+ }
97222
+ return true;
97223
+ },
97224
+ routedOverridesSince(chatId, intendedThreadId, notBeforeMs) {
97225
+ const list2 = byKey.get(keyFor(chatId, intendedThreadId));
97226
+ if (list2 == null)
97227
+ return [];
97228
+ const out = [];
97229
+ for (const e of list2) {
97230
+ if (e.atMs < notBeforeMs)
97231
+ continue;
97232
+ if (out.some((seen) => seen.routedThreadId === e.routedThreadId))
97233
+ continue;
97234
+ out.push({ ...e });
97235
+ }
97236
+ return out;
97237
+ },
97238
+ newestOverrideSince(chatId, intendedThreadId, notBeforeMs) {
97239
+ const list2 = byKey.get(keyFor(chatId, intendedThreadId));
97240
+ if (list2 == null)
97241
+ return;
97242
+ let newest;
97243
+ for (const e of list2) {
97244
+ if (e.atMs < notBeforeMs)
97245
+ continue;
97246
+ if (newest == null || e.atMs > newest.atMs)
97247
+ newest = e;
97248
+ }
97249
+ return newest == null ? undefined : { ...newest };
97250
+ },
97251
+ size() {
97252
+ return byKey.size;
97253
+ },
97254
+ clear() {
97255
+ byKey.clear();
97256
+ }
97257
+ };
97258
+ }
97259
+ var answerRouteOverrides = createAnswerRouteOverrides();
97260
+
97261
+ // gateway/reply-route-log.ts
97262
+ function formatReplyRouteLog2(i) {
97263
+ const originCrossChat = isCrossChatAnchor(i.chatId, i.originTurn?.sessionChatId);
97264
+ const liveCrossChat = isCrossChatAnchor(i.chatId, i.liveTurn?.sessionChatId);
97265
+ const crossChatDropped = originCrossChat || liveCrossChat;
97266
+ const originAnchor = originCrossChat ? null : i.originTurn;
97267
+ const liveAnchor = liveCrossChat ? null : i.liveTurn;
97268
+ const explicit = i.explicitThreadId;
97269
+ const via = i.frameworkTopicAuthority ? originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor != null ? "live" : explicit != null ? "explicit" : i.recovered != null ? "recovered" : "none" : explicit != null ? "explicit" : originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor?.sessionThreadId != null ? "live" : i.recovered != null ? "recovered" : "none";
97270
+ const explicitOverridden = i.frameworkTopicAuthority && explicit != null && (originAnchor != null || liveAnchor != null) && i.threadId !== explicit;
97271
+ const ownerTurn = originAnchor ?? i.recovered ?? liveAnchor;
97272
+ const isSupergroup = i.chatId.startsWith("-100");
97273
+ const unrouted = isSupergroup && i.threadId == null && ownerTurn == null && !crossChatDropped;
97274
+ const misrouteRisk = isSupergroup && via === "live" && i.hasDifferentThreadedRecentTurn(liveAnchor?.sessionThreadId);
97275
+ return `telegram gateway: reply-route surface=${i.surface} chat=${i.chatId} ` + `resolved_thread=${i.threadId ?? "-"} via=${via} late=${liveAnchor == null} ` + `originTurn=${ownerTurn?.turnId ?? "-"} origin_thread=${ownerTurn?.sessionThreadId ?? "-"}` + (via === "recovered" ? " RECOVERED" : "") + (via === "quoted" ? " QUOTED(framework-origin)" : "") + (unrouted ? " UNROUTED(supergroup\u2192no-topic)" : "") + (misrouteRisk ? " MISROUTE_RISK(no-echo\u2192live-successor)" : "") + (crossChatDropped ? ` CROSS_CHAT_ANCHOR_DROPPED(target=${i.chatId}` + (originCrossChat ? `,origin_chat=${i.originTurn?.sessionChatId},origin_thread=${i.originTurn?.sessionThreadId ?? "-"}` : "") + (liveCrossChat ? `,live_chat=${i.liveTurn?.sessionChatId},live_thread=${i.liveTurn?.sessionThreadId ?? "-"}` : "") + `,routed\u2192${i.threadId ?? "-"})` : "") + (explicitOverridden ? ` EXPLICIT_OVERRIDDEN(model\u2192${explicit},routed\u2192${i.threadId ?? "-"})` : "") + `
97276
+ `;
96613
97277
  }
96614
97278
 
96615
97279
  // gateway/latest-turn-lookup.ts
@@ -98026,6 +98690,153 @@ function shouldDeferEscalationForBridge(deps) {
98026
98690
  return !deps.bridgeAlive;
98027
98691
  }
98028
98692
 
98693
+ // gateway/escalation-staleness.ts
98694
+ var OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT = 8500;
98695
+ var OBLIGATION_ESCALATE_SETTLE_MS_MAX = 60000;
98696
+ var REROUTE_MATCH_GRACE_MS = 1e4;
98697
+ var OBLIGATION_REROUTE_MATCH_MS_MAX = OBLIGATION_ESCALATE_SETTLE_MS_MAX + REROUTE_MATCH_GRACE_MS;
98698
+ function resolveRerouteMatchWindowMs(settleMs, env = process.env) {
98699
+ const derived = Math.max(settleMs, 0) + REROUTE_MATCH_GRACE_MS;
98700
+ const raw = env.SWITCHROOM_OBLIGATION_REROUTE_MATCH_MS;
98701
+ if (raw == null || raw.trim() === "")
98702
+ return derived;
98703
+ const n = Number(raw);
98704
+ if (!(Number.isFinite(n) && n >= 0))
98705
+ return derived;
98706
+ return Math.min(n, OBLIGATION_REROUTE_MATCH_MS_MAX);
98707
+ }
98708
+ function resolveEscalateSettleMs(env = process.env) {
98709
+ const raw = env.SWITCHROOM_OBLIGATION_ESCALATE_SETTLE_MS;
98710
+ if (raw == null || raw.trim() === "")
98711
+ return OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT;
98712
+ const n = Number(raw);
98713
+ if (!(Number.isFinite(n) && n >= 0))
98714
+ return OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT;
98715
+ return Math.min(n, OBLIGATION_ESCALATE_SETTLE_MS_MAX);
98716
+ }
98717
+ function answeredSinceOpen(o, deps) {
98718
+ const NOT_ANSWERED = { answered: false, via: null };
98719
+ if (!deps.historyEnabled)
98720
+ return NOT_ANSWERED;
98721
+ if (deps.hasOutboundDeliveredSince(o.chatId, o.openedAt, o.threadId)) {
98722
+ return { answered: true, via: "thread" };
98723
+ }
98724
+ if (!(deps.rerouteMatchWindowMs > 0))
98725
+ return NOT_ANSWERED;
98726
+ const notBefore = Math.max(o.openedAt, deps.anchorMs - deps.rerouteMatchWindowMs);
98727
+ for (const ovr of deps.routeOverrides.routedOverridesSince(o.chatId, o.threadId, notBefore)) {
98728
+ const since = Math.max(ovr.atMs, o.openedAt);
98729
+ const until = ovr.atMs + deps.rerouteMatchWindowMs;
98730
+ if (deps.hasOutboundDeliveredBetween(o.chatId, since, until, ovr.routedThreadId)) {
98731
+ return { answered: true, via: "reroute", routedThreadId: ovr.routedThreadId };
98732
+ }
98733
+ }
98734
+ const newest = deps.routeOverrides.newestOverrideSince(o.chatId, o.threadId, o.openedAt);
98735
+ if (newest != null && newest.atMs < notBefore) {
98736
+ return { answered: false, via: null, staleOverrideAgeMs: Math.max(0, deps.anchorMs - newest.atMs) };
98737
+ }
98738
+ return NOT_ANSWERED;
98739
+ }
98740
+ function createEscalationSettleGate(settleMs, maxKeys = 256) {
98741
+ const entries = new Map;
98742
+ return {
98743
+ shouldDefer(id, now, openedAt) {
98744
+ if (!(settleMs > 0))
98745
+ return false;
98746
+ const prev = entries.get(id);
98747
+ if (prev == null || prev.openedAt !== openedAt) {
98748
+ entries.set(id, { firstAt: now, openedAt });
98749
+ while (entries.size > maxKeys) {
98750
+ const oldest = entries.keys().next().value;
98751
+ if (oldest === undefined)
98752
+ break;
98753
+ entries.delete(oldest);
98754
+ }
98755
+ return true;
98756
+ }
98757
+ if (now < prev.firstAt) {
98758
+ entries.set(id, { firstAt: now, openedAt });
98759
+ return true;
98760
+ }
98761
+ return now - prev.firstAt < settleMs;
98762
+ },
98763
+ firstAt(id, openedAt) {
98764
+ const prev = entries.get(id);
98765
+ if (prev == null || prev.openedAt !== openedAt)
98766
+ return;
98767
+ return prev.firstAt;
98768
+ },
98769
+ clear(id) {
98770
+ entries.delete(id);
98771
+ },
98772
+ size() {
98773
+ return entries.size;
98774
+ }
98775
+ };
98776
+ }
98777
+
98778
+ // gateway/answer-route-overrides.ts
98779
+ function keyFor2(chatId, threadId) {
98780
+ return `${chatId}:${threadId ?? "_"}`;
98781
+ }
98782
+ function createAnswerRouteOverrides2(maxKeys = 64, maxPerKey = 8) {
98783
+ const byKey = new Map;
98784
+ return {
98785
+ note(args) {
98786
+ const overridden = args.enabled && args.explicitThreadId != null && args.anchored && args.routedThreadId !== args.explicitThreadId;
98787
+ if (!overridden)
98788
+ return false;
98789
+ const key = keyFor2(args.chatId, args.explicitThreadId);
98790
+ const list2 = byKey.get(key) ?? [];
98791
+ list2.push({ routedThreadId: args.routedThreadId ?? null, atMs: args.nowMs });
98792
+ while (list2.length > maxPerKey)
98793
+ list2.shift();
98794
+ byKey.set(key, list2);
98795
+ while (byKey.size > maxKeys) {
98796
+ const oldest = byKey.keys().next().value;
98797
+ if (oldest === undefined)
98798
+ break;
98799
+ byKey.delete(oldest);
98800
+ }
98801
+ return true;
98802
+ },
98803
+ routedOverridesSince(chatId, intendedThreadId, notBeforeMs) {
98804
+ const list2 = byKey.get(keyFor2(chatId, intendedThreadId));
98805
+ if (list2 == null)
98806
+ return [];
98807
+ const out = [];
98808
+ for (const e of list2) {
98809
+ if (e.atMs < notBeforeMs)
98810
+ continue;
98811
+ if (out.some((seen) => seen.routedThreadId === e.routedThreadId))
98812
+ continue;
98813
+ out.push({ ...e });
98814
+ }
98815
+ return out;
98816
+ },
98817
+ newestOverrideSince(chatId, intendedThreadId, notBeforeMs) {
98818
+ const list2 = byKey.get(keyFor2(chatId, intendedThreadId));
98819
+ if (list2 == null)
98820
+ return;
98821
+ let newest;
98822
+ for (const e of list2) {
98823
+ if (e.atMs < notBeforeMs)
98824
+ continue;
98825
+ if (newest == null || e.atMs > newest.atMs)
98826
+ newest = e;
98827
+ }
98828
+ return newest == null ? undefined : { ...newest };
98829
+ },
98830
+ size() {
98831
+ return byKey.size;
98832
+ },
98833
+ clear() {
98834
+ byKey.clear();
98835
+ }
98836
+ };
98837
+ }
98838
+ var answerRouteOverrides2 = createAnswerRouteOverrides2();
98839
+
98029
98840
  // gateway/stream-render.ts
98030
98841
  init_history();
98031
98842
  init_rich_send();
@@ -98064,14 +98875,19 @@ function createObligationWiring(deps) {
98064
98875
  bridgeAlive,
98065
98876
  sendEscalationNudge
98066
98877
  } = deps;
98878
+ const escalateSettleMs = resolveEscalateSettleMs();
98879
+ const escalateSettleGate = createEscalationSettleGate(escalateSettleMs);
98880
+ const rerouteMatchWindowMs = resolveRerouteMatchWindowMs(escalateSettleMs);
98067
98881
  function closeObligationOnSubstantiveReply(args, liveTurn, routedOriginTurn) {
98068
98882
  if (!OBLIGATION_LEDGER_ENABLED)
98069
98883
  return;
98070
98884
  const echoed = findTurnByOriginId(args.origin_turn_id);
98071
98885
  const routedOriginId = routedOriginTurn != null && echoed == null ? routedOriginTurn.turnId : null;
98072
98886
  const target = obligationLedger.resolveCloseTarget(echoed?.turnId, liveTurn?.turnId, routedOriginId);
98073
- if (target != null)
98887
+ if (target != null) {
98074
98888
  obligationLedger.close(target);
98889
+ escalateSettleGate.clear(target);
98890
+ }
98075
98891
  }
98076
98892
  function openObligationFromInbound(inboundMsg, gate) {
98077
98893
  if (!OBLIGATION_LEDGER_ENABLED)
@@ -98103,6 +98919,7 @@ function createObligationWiring(deps) {
98103
98919
  if (turn == null)
98104
98920
  return;
98105
98921
  if (obligationLedger.close(turn.turnId)) {
98922
+ escalateSettleGate.clear(turn.turnId);
98106
98923
  process.stderr.write(`telegram gateway: obligation cancelled by interrupt origin=${turn.turnId}
98107
98924
  `);
98108
98925
  }
@@ -98143,6 +98960,7 @@ function createObligationWiring(deps) {
98143
98960
  })) {
98144
98961
  process.stderr.write(`telegram gateway: obligation closed silently \u2014 reply delivered since last represent (no re-fire) origin=${o.originTurnId}
98145
98962
  `);
98963
+ escalateSettleGate.clear(o.originTurnId);
98146
98964
  obligationLedger.close(o.originTurnId);
98147
98965
  return;
98148
98966
  }
@@ -98164,12 +98982,31 @@ function createObligationWiring(deps) {
98164
98982
  `);
98165
98983
  return;
98166
98984
  }
98167
- if (HISTORY_ENABLED && hasOutboundDeliveredSince3(o.chatId, o.openedAt, o.threadId)) {
98168
- process.stderr.write(`telegram gateway: obligation closed silently \u2014 outbound delivered since open origin=${o.originTurnId}
98985
+ const answered = answeredSinceOpen(o, {
98986
+ historyEnabled: HISTORY_ENABLED,
98987
+ hasOutboundDeliveredSince: hasOutboundDeliveredSince3,
98988
+ hasOutboundDeliveredBetween: (chatId, sinceMs, untilMs, threadId) => hasOutboundDeliveredSince3(chatId, sinceMs, threadId, undefined, untilMs),
98989
+ routeOverrides: answerRouteOverrides2,
98990
+ anchorMs: escalateSettleGate.firstAt(o.originTurnId, o.openedAt) ?? now,
98991
+ rerouteMatchWindowMs
98992
+ });
98993
+ if (answered.answered) {
98994
+ const scope = answered.via === "reroute" ? `via=reroute routed_thread=${answered.routedThreadId ?? "-"}` : "via=thread";
98995
+ process.stderr.write(`telegram gateway: obligation closed silently \u2014 outbound delivered since open ${scope} origin=${o.originTurnId}
98169
98996
  `);
98997
+ escalateSettleGate.clear(o.originTurnId);
98170
98998
  obligationLedger.close(o.originTurnId);
98171
98999
  return;
98172
99000
  }
99001
+ if (answered.staleOverrideAgeMs != null) {
99002
+ process.stderr.write(`telegram gateway: obligation reroute record rejected age=${answered.staleOverrideAgeMs}ms ` + `window=${rerouteMatchWindowMs}ms origin=${o.originTurnId}
99003
+ `);
99004
+ }
99005
+ if (escalateSettleGate.shouldDefer(o.originTurnId, now, o.openedAt)) {
99006
+ process.stderr.write(`telegram gateway: obligation escalation deferred \u2014 settling (re-checking for an in-flight answer) origin=${o.originTurnId}
99007
+ `);
99008
+ return;
99009
+ }
98173
99010
  driveEscalation({
98174
99011
  escId: o.originTurnId,
98175
99012
  inFlight: obligationEscalateInFlight,
@@ -98178,6 +99015,7 @@ function createObligationWiring(deps) {
98178
99015
  maxAttempts: OBLIGATION_ESCALATE_MAX,
98179
99016
  deadlineMs: OBLIGATION_ESCALATE_SEND_DEADLINE_MS
98180
99017
  });
99018
+ escalateSettleGate.clear(o.originTurnId);
98181
99019
  }
98182
99020
  return {
98183
99021
  closeObligationOnSubstantiveReply,
@@ -98213,14 +99051,14 @@ function purgeStaleTurnsForChat(chatId, keys, purger, isStale = () => true) {
98213
99051
  // gateway/compaction-marker.ts
98214
99052
  import { statSync as statSync17, unlinkSync as unlinkSync22 } from "node:fs";
98215
99053
  import { homedir as homedir17 } from "node:os";
98216
- import { join as join56 } from "node:path";
99054
+ import { join as join57 } from "node:path";
98217
99055
  var COMPACTION_MARKER_FILE = "compaction-in-flight.json";
98218
99056
  function resolveTelegramStateDir() {
98219
- return process.env.TELEGRAM_STATE_DIR ?? join56(homedir17(), ".claude", "channels", "telegram");
99057
+ return process.env.TELEGRAM_STATE_DIR ?? join57(homedir17(), ".claude", "channels", "telegram");
98220
99058
  }
98221
99059
  function readCompactionMarkerAgeMs(stateDir, now) {
98222
99060
  try {
98223
- const st = statSync17(join56(stateDir, COMPACTION_MARKER_FILE));
99061
+ const st = statSync17(join57(stateDir, COMPACTION_MARKER_FILE));
98224
99062
  return (now ?? Date.now()) - st.mtimeMs;
98225
99063
  } catch {
98226
99064
  return null;
@@ -98228,7 +99066,7 @@ function readCompactionMarkerAgeMs(stateDir, now) {
98228
99066
  }
98229
99067
  function removeCompactionMarker(stateDir) {
98230
99068
  try {
98231
- unlinkSync22(join56(stateDir, COMPACTION_MARKER_FILE));
99069
+ unlinkSync22(join57(stateDir, COMPACTION_MARKER_FILE));
98232
99070
  } catch {}
98233
99071
  }
98234
99072
 
@@ -100293,7 +101131,7 @@ import {
100293
101131
  readdirSync as readdirSync12,
100294
101132
  readFileSync as readFileSync47
100295
101133
  } from "fs";
100296
- import { join as join57 } from "path";
101134
+ import { join as join58 } from "path";
100297
101135
 
100298
101136
  // session-tail.ts
100299
101137
  function sanitizeCwdToProjectName2(cwd) {
@@ -101616,8 +102454,8 @@ function startSubagentWatcher(config) {
101616
102454
  if (stopped)
101617
102455
  return;
101618
102456
  pruneVanishedDirWatchers();
101619
- const claudeHome = join57(agentDir, ".claude");
101620
- const projectsRoot = join57(claudeHome, "projects");
102457
+ const claudeHome = join58(agentDir, ".claude");
102458
+ const projectsRoot = join58(claudeHome, "projects");
101621
102459
  if (!fs3.existsSync(projectsRoot))
101622
102460
  return;
101623
102461
  let projectDirs;
@@ -101651,7 +102489,7 @@ function startSubagentWatcher(config) {
101651
102489
  continue;
101652
102490
  }
101653
102491
  warnedForeignSlugs.delete(pDir);
101654
- const projectPath = join57(projectsRoot, pDir);
102492
+ const projectPath = join58(projectsRoot, pDir);
101655
102493
  let sessionDirs;
101656
102494
  try {
101657
102495
  sessionDirs = fs3.readdirSync(projectPath);
@@ -101661,7 +102499,7 @@ function startSubagentWatcher(config) {
101661
102499
  for (const sDir of sessionDirs) {
101662
102500
  if (sDir.endsWith(".jsonl"))
101663
102501
  continue;
101664
- const subagentsPath = join57(projectPath, sDir, "subagents");
102502
+ const subagentsPath = join58(projectPath, sDir, "subagents");
101665
102503
  if (!fs3.existsSync(subagentsPath))
101666
102504
  continue;
101667
102505
  const watchAndScan = (dirPath) => {
@@ -101670,7 +102508,7 @@ function startSubagentWatcher(config) {
101670
102508
  const w = fs3.watch(dirPath, (_event, filename) => {
101671
102509
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
101672
102510
  return;
101673
- const filePath = join57(dirPath, filename.toString());
102511
+ const filePath = join58(dirPath, filename.toString());
101674
102512
  if (!knownFiles.has(filePath)) {
101675
102513
  scanSubagentsDir(dirPath);
101676
102514
  }
@@ -101684,7 +102522,7 @@ function startSubagentWatcher(config) {
101684
102522
  scanSubagentsDir(dirPath);
101685
102523
  };
101686
102524
  watchAndScan(subagentsPath);
101687
- const workflowsPath = join57(subagentsPath, "workflows");
102525
+ const workflowsPath = join58(subagentsPath, "workflows");
101688
102526
  if (fs3.existsSync(workflowsPath)) {
101689
102527
  let wfDirs;
101690
102528
  try {
@@ -101694,7 +102532,7 @@ function startSubagentWatcher(config) {
101694
102532
  }
101695
102533
  for (const wfDir of wfDirs) {
101696
102534
  try {
101697
- const wfPath = join57(workflowsPath, wfDir);
102535
+ const wfPath = join58(workflowsPath, wfDir);
101698
102536
  if (!fs3.statSync(wfPath).isDirectory())
101699
102537
  continue;
101700
102538
  watchAndScan(wfPath);
@@ -101714,7 +102552,7 @@ function startSubagentWatcher(config) {
101714
102552
  for (const e of entries) {
101715
102553
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
101716
102554
  continue;
101717
- const filePath = join57(subagentsPath, e);
102555
+ const filePath = join58(subagentsPath, e);
101718
102556
  if (knownFiles.has(filePath))
101719
102557
  continue;
101720
102558
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -101850,13 +102688,13 @@ import {
101850
102688
  existsSync as existsSync48,
101851
102689
  renameSync as renameSync24
101852
102690
  } from "node:fs";
101853
- import { join as join58, resolve as resolve10 } from "node:path";
102691
+ import { join as join59, resolve as resolve10 } from "node:path";
101854
102692
  import { homedir as homedir18 } from "node:os";
101855
102693
  function registryDir2() {
101856
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join58(homedir18(), ".switchroom", "worktrees"));
102694
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join59(homedir18(), ".switchroom", "worktrees"));
101857
102695
  }
101858
102696
  function recordPath(id) {
101859
- return join58(registryDir2(), `${id}.json`);
102697
+ return join59(registryDir2(), `${id}.json`);
101860
102698
  }
101861
102699
  function ensureDir4() {
101862
102700
  mkdirSync41(registryDir2(), { recursive: true });
@@ -102040,19 +102878,19 @@ function determineBridgeReconnectReason(opts) {
102040
102878
 
102041
102879
  // gateway/update-announce.ts
102042
102880
  import { existsSync as existsSync53, mkdirSync as mkdirSync45, openSync as openSync14, closeSync as closeSync14, readFileSync as readFileSync55 } from "node:fs";
102043
- import { join as join63 } from "node:path";
102881
+ import { join as join64 } from "node:path";
102044
102882
  import { homedir as homedir20 } from "node:os";
102045
102883
 
102046
102884
  // ../src/host-control/audit-reader.ts
102047
102885
  import { homedir as homedir19 } from "node:os";
102048
- import { join as join62 } from "node:path";
102886
+ import { join as join63 } from "node:path";
102049
102887
 
102050
102888
  // ../src/host-control/audit-rotation-config.ts
102051
102889
  var DEFAULT_HOSTD_AUDIT_MAX_BYTES = 32 * 1024 * 1024;
102052
102890
 
102053
102891
  // ../src/host-control/audit-reader.ts
102054
102892
  function defaultAuditLogPath(home2 = homedir19()) {
102055
- return join62(home2, ".switchroom", "host-control-audit.log");
102893
+ return join63(home2, ".switchroom", "host-control-audit.log");
102056
102894
  }
102057
102895
  var AUDIT_READ_WINDOW_BYTES = 4 * 1024 * 1024;
102058
102896
  function parseAuditLine(line) {
@@ -102251,15 +103089,15 @@ function renderUpdateOutcomeLine(entry) {
102251
103089
  `);
102252
103090
  }
102253
103091
  function claimUpdateAnnouncement(requestId, opts = {}) {
102254
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join63(homedir20(), ".switchroom");
102255
- const dir = join63(stateDir, "update-announced");
103092
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join64(homedir20(), ".switchroom");
103093
+ const dir = join64(stateDir, "update-announced");
102256
103094
  try {
102257
103095
  mkdirSync45(dir, { recursive: true });
102258
103096
  } catch {
102259
103097
  return false;
102260
103098
  }
102261
103099
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
102262
- const path3 = join63(dir, safeId);
103100
+ const path3 = join64(dir, safeId);
102263
103101
  try {
102264
103102
  const fd = openSync14(path3, "wx");
102265
103103
  closeSync14(fd);
@@ -102481,7 +103319,7 @@ function createIssuesCardHandle(opts) {
102481
103319
 
102482
103320
  // issues-watcher.ts
102483
103321
  import { existsSync as existsSync55, statSync as statSync21 } from "node:fs";
102484
- import { join as join65 } from "node:path";
103322
+ import { join as join66 } from "node:path";
102485
103323
 
102486
103324
  // ../src/issues/store.ts
102487
103325
  import {
@@ -102497,7 +103335,7 @@ import {
102497
103335
  writeFileSync as writeFileSync46,
102498
103336
  writeSync as writeSync8
102499
103337
  } from "node:fs";
102500
- import { join as join64 } from "node:path";
103338
+ import { join as join65 } from "node:path";
102501
103339
  import { randomBytes as randomBytes10 } from "node:crypto";
102502
103340
  import { execSync } from "node:child_process";
102503
103341
 
@@ -102516,7 +103354,7 @@ init_redact();
102516
103354
  var ISSUES_FILE = "issues.jsonl";
102517
103355
  var ISSUES_LOCK = "issues.lock";
102518
103356
  function readAll(stateDir) {
102519
- const path3 = join64(stateDir, ISSUES_FILE);
103357
+ const path3 = join65(stateDir, ISSUES_FILE);
102520
103358
  if (!existsSync54(path3))
102521
103359
  return [];
102522
103360
  let raw;
@@ -102553,7 +103391,7 @@ function list2(stateDir, opts = {}) {
102553
103391
  });
102554
103392
  }
102555
103393
  function resolve11(stateDir, fingerprint, nowFn = Date.now) {
102556
- if (!existsSync54(join64(stateDir, ISSUES_FILE)))
103394
+ if (!existsSync54(join65(stateDir, ISSUES_FILE)))
102557
103395
  return 0;
102558
103396
  return withLock(stateDir, () => {
102559
103397
  const all2 = readAll(stateDir);
@@ -102571,7 +103409,7 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
102571
103409
  });
102572
103410
  }
102573
103411
  function writeAll(stateDir, events) {
102574
- const path3 = join64(stateDir, ISSUES_FILE);
103412
+ const path3 = join65(stateDir, ISSUES_FILE);
102575
103413
  sweepOrphanTmpFiles(stateDir);
102576
103414
  const tmp = `${path3}.tmp-${process.pid}-${randomBytes10(4).toString("hex")}`;
102577
103415
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -102593,7 +103431,7 @@ function sweepOrphanTmpFiles(stateDir) {
102593
103431
  for (const entry of entries) {
102594
103432
  if (!entry.startsWith(TMP_PREFIX))
102595
103433
  continue;
102596
- const tmpPath2 = join64(stateDir, entry);
103434
+ const tmpPath2 = join65(stateDir, entry);
102597
103435
  try {
102598
103436
  const stat = statSync20(tmpPath2);
102599
103437
  if (stat.mtimeMs < cutoff) {
@@ -102605,7 +103443,7 @@ function sweepOrphanTmpFiles(stateDir) {
102605
103443
  var LOCK_RETRY_MS = 25;
102606
103444
  var LOCK_TIMEOUT_MS = 1e4;
102607
103445
  function withLock(stateDir, fn) {
102608
- const lockPath = join64(stateDir, ISSUES_LOCK);
103446
+ const lockPath = join65(stateDir, ISSUES_LOCK);
102609
103447
  const startedAt = Date.now();
102610
103448
  let fd = null;
102611
103449
  while (fd === null) {
@@ -102690,7 +103528,7 @@ function isIssueEvent(v) {
102690
103528
  // issues-watcher.ts
102691
103529
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
102692
103530
  function startIssuesWatcher(opts) {
102693
- const path3 = join65(opts.stateDir, ISSUES_FILE);
103531
+ const path3 = join66(opts.stateDir, ISSUES_FILE);
102694
103532
  const log = opts.log ?? (() => {});
102695
103533
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
102696
103534
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -103450,7 +104288,7 @@ function readBashCommand(inputPreview) {
103450
104288
 
103451
104289
  // gateway/scoped-grant-store.ts
103452
104290
  init_state_owner();
103453
- import { join as join66 } from "node:path";
104291
+ import { join as join67 } from "node:path";
103454
104292
 
103455
104293
  // scoped-approval.ts
103456
104294
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -103492,7 +104330,7 @@ function scopedGrantPersistEnabled(env = process.env) {
103492
104330
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
103493
104331
  }
103494
104332
  function createScopedGrantStore(stateDir, env = process.env, log = (l) => process.stderr.write(l)) {
103495
- const filePath = join66(stateDir, "scoped-grants.json");
104333
+ const filePath = join67(stateDir, "scoped-grants.json");
103496
104334
  const enabled7 = scopedGrantPersistEnabled(env);
103497
104335
  let unreadable = false;
103498
104336
  function read() {
@@ -103871,7 +104709,7 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
103871
104709
  // credits-watch.ts
103872
104710
  init_card_format();
103873
104711
  import { readFileSync as readFileSync58, writeFileSync as writeFileSync47, existsSync as existsSync56, mkdirSync as mkdirSync47 } from "fs";
103874
- import { join as join67 } from "path";
104712
+ import { join as join68 } from "path";
103875
104713
  var STATE_FILE = "credits-watch.json";
103876
104714
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
103877
104715
  var KNOWN_CREDIT_REASONS = [
@@ -103893,7 +104731,7 @@ function emptyCreditState() {
103893
104731
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
103894
104732
  }
103895
104733
  function readClaudeJsonOverage(claudeConfigDir) {
103896
- const path3 = join67(claudeConfigDir, ".claude.json");
104734
+ const path3 = join68(claudeConfigDir, ".claude.json");
103897
104735
  if (!existsSync56(path3))
103898
104736
  return null;
103899
104737
  let raw;
@@ -103976,7 +104814,7 @@ function humanizeReason(reason) {
103976
104814
  }
103977
104815
  }
103978
104816
  function loadCreditState(stateDir) {
103979
- const path3 = join67(stateDir, STATE_FILE);
104817
+ const path3 = join68(stateDir, STATE_FILE);
103980
104818
  if (!existsSync56(path3))
103981
104819
  return emptyCreditState();
103982
104820
  try {
@@ -103993,7 +104831,7 @@ function loadCreditState(stateDir) {
103993
104831
  }
103994
104832
  function saveCreditState(stateDir, state7) {
103995
104833
  mkdirSync47(stateDir, { recursive: true });
103996
- const path3 = join67(stateDir, STATE_FILE);
104834
+ const path3 = join68(stateDir, STATE_FILE);
103997
104835
  writeFileSync47(path3, JSON.stringify(state7, null, 2) + `
103998
104836
  `, { mode: 384 });
103999
104837
  }
@@ -104002,7 +104840,7 @@ function saveCreditState(stateDir, state7) {
104002
104840
  init_auth_snapshot_format();
104003
104841
  init_card_format();
104004
104842
  import { readFileSync as readFileSync59, writeFileSync as writeFileSync48, existsSync as existsSync57, mkdirSync as mkdirSync48 } from "fs";
104005
- import { join as join68 } from "path";
104843
+ import { join as join69 } from "path";
104006
104844
  var STATE_FILE2 = "quota-watch.json";
104007
104845
  function emptyQuotaWatchState() {
104008
104846
  return {};
@@ -104240,7 +105078,7 @@ function buildRecoveryMessage(agentName3, snap) {
104240
105078
  `);
104241
105079
  }
104242
105080
  function loadQuotaWatchState(stateDir) {
104243
- const path3 = join68(stateDir, STATE_FILE2);
105081
+ const path3 = join69(stateDir, STATE_FILE2);
104244
105082
  if (!existsSync57(path3))
104245
105083
  return emptyQuotaWatchState();
104246
105084
  try {
@@ -104262,7 +105100,7 @@ function loadQuotaWatchState(stateDir) {
104262
105100
  }
104263
105101
  function saveQuotaWatchState(stateDir, state7) {
104264
105102
  mkdirSync48(stateDir, { recursive: true });
104265
- const path3 = join68(stateDir, STATE_FILE2);
105103
+ const path3 = join69(stateDir, STATE_FILE2);
104266
105104
  writeFileSync48(path3, JSON.stringify(state7, null, 2) + `
104267
105105
  `, { mode: 384 });
104268
105106
  }
@@ -104319,17 +105157,17 @@ import {
104319
105157
  unlinkSync as unlinkSync28,
104320
105158
  utimesSync as utimesSync2
104321
105159
  } from "node:fs";
104322
- import { join as join69 } from "node:path";
105160
+ import { join as join70 } from "node:path";
104323
105161
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
104324
105162
  var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
104325
105163
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
104326
105164
  function removeTurnActiveMarker2(stateDir) {
104327
105165
  try {
104328
- unlinkSync28(join69(stateDir, TURN_ACTIVE_MARKER_FILE2));
105166
+ unlinkSync28(join70(stateDir, TURN_ACTIVE_MARKER_FILE2));
104329
105167
  } catch {}
104330
105168
  }
104331
105169
  function sweepStaleTurnActiveMarker(stateDir, opts) {
104332
- const path3 = join69(stateDir, TURN_ACTIVE_MARKER_FILE2);
105170
+ const path3 = join70(stateDir, TURN_ACTIVE_MARKER_FILE2);
104333
105171
  if (!existsSync58(path3))
104334
105172
  return false;
104335
105173
  const now = opts.now ?? Date.now();
@@ -104360,7 +105198,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
104360
105198
  }
104361
105199
  }
104362
105200
  function readTurnActiveMarkerAgeMs2(stateDir, now) {
104363
- const path3 = join69(stateDir, TURN_ACTIVE_MARKER_FILE2);
105201
+ const path3 = join70(stateDir, TURN_ACTIVE_MARKER_FILE2);
104364
105202
  try {
104365
105203
  const st = statSync22(path3);
104366
105204
  return (now ?? Date.now()) - st.mtimeMs;
@@ -104375,11 +105213,11 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
104375
105213
  // gateway/gateway-heartbeat.ts
104376
105214
  init_state_owner();
104377
105215
  import { utimesSync as utimesSync3 } from "node:fs";
104378
- import { join as join70 } from "node:path";
105216
+ import { join as join71 } from "node:path";
104379
105217
  var GATEWAY_HEARTBEAT_FILE = "gateway-heartbeat";
104380
105218
  var GATEWAY_HEARTBEAT_INTERVAL_MS = 15000;
104381
105219
  function touchGatewayHeartbeat(stateDir) {
104382
- const path3 = join70(stateDir, GATEWAY_HEARTBEAT_FILE);
105220
+ const path3 = join71(stateDir, GATEWAY_HEARTBEAT_FILE);
104383
105221
  const now = new Date;
104384
105222
  try {
104385
105223
  utimesSync3(path3, now, now);
@@ -104410,7 +105248,7 @@ import {
104410
105248
  unlinkSync as unlinkSync29,
104411
105249
  writeFileSync as writeFileSync49
104412
105250
  } from "node:fs";
104413
- import { join as join71 } from "node:path";
105251
+ import { join as join72 } from "node:path";
104414
105252
  var BOOT_BEACON_FILE = "gateway-beacon.json";
104415
105253
  var HOST_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id";
104416
105254
  var CGROUP_MEMORY_EVENTS_PATH = "/sys/fs/cgroup/memory.events";
@@ -104509,7 +105347,7 @@ function sampleBeaconHost(paths = {}) {
104509
105347
  return sample3;
104510
105348
  }
104511
105349
  function writeBootBeaconFile(stateDir, beacon) {
104512
- const path3 = join71(stateDir, BOOT_BEACON_FILE);
105350
+ const path3 = join72(stateDir, BOOT_BEACON_FILE);
104513
105351
  const tmp = `${path3}.tmp-${process.pid}`;
104514
105352
  let fd;
104515
105353
  try {
@@ -104901,10 +105739,10 @@ function startOutboxSweep(deps) {
104901
105739
  }
104902
105740
 
104903
105741
  // ../src/build-info.ts
104904
- var VERSION2 = "0.21.9";
104905
- var COMMIT_SHA = "08262924";
104906
- var COMMIT_DATE = "2026-08-13T12:13:46Z";
104907
- var LATEST_PR = 4686;
105742
+ var VERSION2 = "0.21.11";
105743
+ var COMMIT_SHA = "7a80af9d";
105744
+ var COMMIT_DATE = "2026-08-14T09:19:49Z";
105745
+ var LATEST_PR = 4713;
104908
105746
  var COMMITS_AHEAD_OF_TAG = 0;
104909
105747
 
104910
105748
  // gateway/boot-version.ts
@@ -104982,11 +105820,11 @@ init_peercred();
104982
105820
  import * as net5 from "node:net";
104983
105821
  import * as fs3 from "node:fs";
104984
105822
  import { homedir as homedir21 } from "node:os";
104985
- import { join as join72 } from "node:path";
105823
+ import { join as join73 } from "node:path";
104986
105824
  var DEFAULT_TIMEOUT_MS4 = 2000;
104987
105825
  var UNLOCK_TIMEOUT_MS = 30000;
104988
- var LEGACY_SOCKET_PATH2 = join72(homedir21(), ".switchroom", "vault-broker.sock");
104989
- var OPERATOR_SOCKET_PATH2 = join72(homedir21(), ".switchroom", "broker-operator", "sock");
105826
+ var LEGACY_SOCKET_PATH2 = join73(homedir21(), ".switchroom", "vault-broker.sock");
105827
+ var OPERATOR_SOCKET_PATH2 = join73(homedir21(), ".switchroom", "broker-operator", "sock");
104990
105828
  function defaultBrokerSocketPath2() {
104991
105829
  if (fs3.existsSync(OPERATOR_SOCKET_PATH2))
104992
105830
  return OPERATOR_SOCKET_PATH2;
@@ -105870,7 +106708,7 @@ function resolveVaultApprovalPosture(broker) {
105870
106708
  // registry/turns-schema.ts
105871
106709
  init_state_owner();
105872
106710
  import { chmodSync as chmodSync12 } from "fs";
105873
- import { join as join73 } from "path";
106711
+ import { join as join74 } from "path";
105874
106712
  var DatabaseClass3 = null;
105875
106713
  function loadDatabaseClass3() {
105876
106714
  if (DatabaseClass3 != null)
@@ -105942,9 +106780,9 @@ function applySchema(db3) {
105942
106780
  }
105943
106781
  function openTurnsDb(agentDir) {
105944
106782
  const Database = loadDatabaseClass3();
105945
- const dir = join73(agentDir, "telegram");
106783
+ const dir = join74(agentDir, "telegram");
105946
106784
  mkdirStateSync(dir, { recursive: true, mode: 448 });
105947
- const path3 = join73(dir, "registry.db");
106785
+ const path3 = join74(dir, "registry.db");
105948
106786
  const db3 = new Database(path3, { create: true });
105949
106787
  applySchema(db3);
105950
106788
  try {
@@ -106287,7 +107125,7 @@ function selectResumeBuilder(endedVia, opts) {
106287
107125
 
106288
107126
  // gateway/boot-briefing-wiring.ts
106289
107127
  import { existsSync as existsSync60, readFileSync as readFileSync63, writeFileSync as writeFileSync50 } from "node:fs";
106290
- import { join as join74 } from "node:path";
107128
+ import { join as join75 } from "node:path";
106291
107129
  init_history();
106292
107130
 
106293
107131
  // gateway/resume-inbound-builder.ts
@@ -106594,8 +107432,8 @@ function readDailyMemory(agentDir, env, nowMs3, readFile = (p) => readFileSync63
106594
107432
  const date = agentLocalDate(nowMs3, env.SWITCHROOM_TIMEZONE || env.TZ || undefined);
106595
107433
  if (!date)
106596
107434
  return null;
106597
- const workspaceDir = env.WORKSPACE_DIR && env.WORKSPACE_DIR.trim() ? env.WORKSPACE_DIR : join74(agentDir, "workspace");
106598
- const file = join74(workspaceDir, "memory", `${date}.md`);
107435
+ const workspaceDir = env.WORKSPACE_DIR && env.WORKSPACE_DIR.trim() ? env.WORKSPACE_DIR : join75(agentDir, "workspace");
107436
+ const file = join75(workspaceDir, "memory", `${date}.md`);
106599
107437
  try {
106600
107438
  const content3 = readFile(file);
106601
107439
  if (!content3 || !content3.trim())
@@ -106624,7 +107462,7 @@ async function maybeQueueBootBriefing(opts) {
106624
107462
  try {
106625
107463
  const agentDir = opts.stateDir.endsWith("/telegram") ? opts.stateDir.slice(0, -"/telegram".length) : opts.stateDir;
106626
107464
  const bootId = opts.env.SWITCHROOM_GATEWAY_BOOT_ID;
106627
- const genMarkerPath = join74(agentDir, ".boot-briefing-generation");
107465
+ const genMarkerPath = join75(agentDir, ".boot-briefing-generation");
106628
107466
  if (bootId) {
106629
107467
  let prevGen = null;
106630
107468
  try {
@@ -106646,7 +107484,7 @@ async function maybeQueueBootBriefing(opts) {
106646
107484
  `);
106647
107485
  } catch {}
106648
107486
  };
106649
- const forceFresh = opts.env.SWITCHROOM_FORCE_FRESH === "1" || existsSync60(join74(agentDir, ".force-fresh-session"));
107487
+ const forceFresh = opts.env.SWITCHROOM_FORCE_FRESH === "1" || existsSync60(join75(agentDir, ".force-fresh-session"));
106650
107488
  const decision = decideBootBriefing({
106651
107489
  briefingMode: opts.env.SWITCHROOM_SESSION_BRIEFING,
106652
107490
  resumeMode: opts.env.SWITCHROOM_RESUME_MODE,
@@ -106680,7 +107518,7 @@ async function maybeQueueBootBriefing(opts) {
106680
107518
  return null;
106681
107519
  }
106682
107520
  const restartReason = readRestartBreadcrumb({
106683
- restartReasonPath: join74(agentDir, ".restart-reason"),
107521
+ restartReasonPath: join75(agentDir, ".restart-reason"),
106684
107522
  env: opts.env,
106685
107523
  readFile: (p) => readFileSync63(p, "utf8")
106686
107524
  });
@@ -106717,9 +107555,9 @@ async function maybeQueueBootBriefing(opts) {
106717
107555
 
106718
107556
  // gateway/pending-turn-env.ts
106719
107557
  import { existsSync as existsSync61, renameSync as renameSync29, rmSync as rmSync8, writeFileSync as writeFileSync51 } from "node:fs";
106720
- import { join as join75 } from "node:path";
107558
+ import { join as join76 } from "node:path";
106721
107559
  function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.write(l)) {
106722
- const pendingEnvPath = join75(agentDir, ".pending-turn.env");
107560
+ const pendingEnvPath = join76(agentDir, ".pending-turn.env");
106723
107561
  try {
106724
107562
  if (pending != null) {
106725
107563
  const lines = [
@@ -106752,7 +107590,7 @@ function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.writ
106752
107590
 
106753
107591
  // gateway/agent-process-liveness.ts
106754
107592
  import { existsSync as existsSync62, readFileSync as readFileSync64, writeFileSync as writeFileSync52, renameSync as renameSync30, unlinkSync as unlinkSync30, readdirSync as readdirSync16 } from "node:fs";
106755
- import { join as join76, dirname as dirname28, basename as basename18 } from "node:path";
107593
+ import { join as join77, dirname as dirname28, basename as basename18 } from "node:path";
106756
107594
  var AGENT_PROCESS_RECORD_FILE = "agent-process.json";
106757
107595
  var BOOT_RESUME_DONE_FILE = ".boot-resume-done";
106758
107596
  function parseProcStat(raw) {
@@ -106775,7 +107613,7 @@ function readProcIdentity(pid, procRoot = "/proc") {
106775
107613
  return null;
106776
107614
  let identity;
106777
107615
  try {
106778
- identity = parseProcStat(readFileSync64(join76(procRoot, String(pid), "stat"), "utf8"));
107616
+ identity = parseProcStat(readFileSync64(join77(procRoot, String(pid), "stat"), "utf8"));
106779
107617
  } catch {
106780
107618
  return null;
106781
107619
  }
@@ -106807,7 +107645,7 @@ function readAgentProcessRecord(path3) {
106807
107645
  return rec;
106808
107646
  }
106809
107647
  function bootResumeDonePath(stateDir) {
106810
- return join76(stateDir, BOOT_RESUME_DONE_FILE);
107648
+ return join77(stateDir, BOOT_RESUME_DONE_FILE);
106811
107649
  }
106812
107650
  function containerBootIdentity(procRoot = "/proc") {
106813
107651
  return readProcIdentity(1, procRoot)?.starttime ?? null;
@@ -106840,7 +107678,7 @@ function sweepStrayTmps(path3) {
106840
107678
  if (!name.startsWith(prefix))
106841
107679
  continue;
106842
107680
  try {
106843
- unlinkSync30(join76(dirname28(path3), name));
107681
+ unlinkSync30(join77(dirname28(path3), name));
106844
107682
  } catch {}
106845
107683
  }
106846
107684
  } catch {}
@@ -106892,7 +107730,7 @@ function detectGatewayOnlyRespawn(opts) {
106892
107730
  return { gatewayOnly: false, reason: "guard-disabled", pid: null };
106893
107731
  const procRoot = opts.procRoot ?? "/proc";
106894
107732
  const sentinel = readBootResumeSentinel(opts.sentinelPath ?? bootResumeDonePath(opts.stateDir), { procRoot });
106895
- const recordPath2 = opts.recordPath ?? join76(opts.stateDir, AGENT_PROCESS_RECORD_FILE);
107733
+ const recordPath2 = opts.recordPath ?? join77(opts.stateDir, AGENT_PROCESS_RECORD_FILE);
106896
107734
  const record2 = readAgentProcessRecord(recordPath2);
106897
107735
  const live = record2 == null ? null : readProcIdentity(record2.pid, procRoot);
106898
107736
  return decideGatewayOnlyRespawn({
@@ -107450,7 +108288,7 @@ if (isGatewayMain) {
107450
108288
  shutdownAnalytics();
107451
108289
  });
107452
108290
  }
107453
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join77(homedir22(), ".claude", "channels", "telegram");
108291
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join78(homedir22(), ".claude", "channels", "telegram");
107454
108292
  var permCardStore = createPermissionCardStore(STATE_DIR);
107455
108293
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
107456
108294
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -107550,11 +108388,11 @@ function scheduleAlwaysAllowPersistDrain() {
107550
108388
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
107551
108389
  timer3.unref?.();
107552
108390
  }
107553
- var ACCESS_FILE = join77(STATE_DIR, "access.json");
107554
- var APPROVED_DIR = join77(STATE_DIR, "approved");
107555
- var ENV_FILE = join77(STATE_DIR, ".env");
107556
- var INBOX_DIR = join77(STATE_DIR, "inbox");
107557
- var PEOPLE_FILE = join77(STATE_DIR, "people.json");
108391
+ var ACCESS_FILE = join78(STATE_DIR, "access.json");
108392
+ var APPROVED_DIR = join78(STATE_DIR, "approved");
108393
+ var ENV_FILE = join78(STATE_DIR, ".env");
108394
+ var INBOX_DIR = join78(STATE_DIR, "inbox");
108395
+ var PEOPLE_FILE = join78(STATE_DIR, "people.json");
107558
108396
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
107559
108397
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
107560
108398
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -107681,7 +108519,7 @@ function assertSendable(f) {
107681
108519
  } catch {
107682
108520
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
107683
108521
  }
107684
- const inbox = join77(stateReal, "inbox");
108522
+ const inbox = join78(stateReal, "inbox");
107685
108523
  if (real.startsWith(stateReal + sep6) && !real.startsWith(inbox + sep6)) {
107686
108524
  throw new Error(`refusing to send channel state: ${f}`);
107687
108525
  }
@@ -107710,7 +108548,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
107710
108548
  if (isGatewayMain && HISTORY_ENABLED) {
107711
108549
  try {
107712
108550
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
107713
- process.stderr.write(`telegram gateway: history capture enabled at ${join77(STATE_DIR, "history.db")}
108551
+ process.stderr.write(`telegram gateway: history capture enabled at ${join78(STATE_DIR, "history.db")}
107714
108552
  `);
107715
108553
  } catch (err) {
107716
108554
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -107733,7 +108571,7 @@ bootResumeInit:
107733
108571
  let markerTurnKey = null;
107734
108572
  let markerAgeMs = null;
107735
108573
  try {
107736
- const markerPath = join77(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
108574
+ const markerPath = join78(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
107737
108575
  if (existsSync65(markerPath)) {
107738
108576
  const st = statSync25(markerPath);
107739
108577
  markerAgeMs = Date.now() - st.mtimeMs;
@@ -107758,10 +108596,10 @@ bootResumeInit:
107758
108596
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
107759
108597
  `);
107760
108598
  } else {
107761
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join77(agentDir, "telegram", "registry.db")}
108599
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join78(agentDir, "telegram", "registry.db")}
107762
108600
  `);
107763
108601
  }
107764
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join77(STATE_DIR, "bridge-dead-escalation.json"));
108602
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join78(STATE_DIR, "bridge-dead-escalation.json"));
107765
108603
  if (bridgeDeadMarker != null) {
107766
108604
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
107767
108605
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -107774,7 +108612,7 @@ bootResumeInit:
107774
108612
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
107775
108613
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
107776
108614
  if (pending2 != null && selfAgent) {
107777
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join77(STATE_DIR, "clean-shutdown.json");
108615
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join78(STATE_DIR, "clean-shutdown.json");
107778
108616
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
107779
108617
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
107780
108618
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -107981,7 +108819,7 @@ function checkApprovals() {
107981
108819
  return;
107982
108820
  }
107983
108821
  for (const senderId of files) {
107984
- const file = join77(APPROVED_DIR, senderId);
108822
+ const file = join78(APPROVED_DIR, senderId);
107985
108823
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync9(file, { force: true }), (err) => {
107986
108824
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
107987
108825
  `);
@@ -108159,7 +108997,7 @@ function noteAgentOutputAt(key, ts) {
108159
108997
  lastAgentOutputAt.delete(oldest);
108160
108998
  }
108161
108999
  }
108162
- var OBLIGATION_STORE_PATH = join77(STATE_DIR, "obligations.json");
109000
+ var OBLIGATION_STORE_PATH = join78(STATE_DIR, "obligations.json");
108163
109001
  var obligationStoreFs = {
108164
109002
  readFileSync: (p) => readFileSync67(p, "utf8"),
108165
109003
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
@@ -108433,7 +109271,9 @@ function resolveReplyOwnerTurn(liveTurn, chatId, args) {
108433
109271
  return resolveReplyOwnerTurnWith({ findTurnByOriginId, findTurnByQuotedMessageId, findLatestTurnForChat, now: Date.now }, liveTurn, chatId, args);
108434
109272
  }
108435
109273
  function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
108436
- const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
109274
+ const originAnchor = isCrossChatAnchor2(chatId, originTurn?.sessionChatId) ? null : originTurn;
109275
+ const liveAnchor = isCrossChatAnchor2(chatId, liveTurn?.sessionChatId) ? null : liveTurn;
109276
+ const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originAnchor == null && liveAnchor == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
108437
109277
  const threadId = resolveAnswerThreadId({
108438
109278
  explicitThreadId,
108439
109279
  originResolved: originTurn != null,
@@ -108442,16 +109282,31 @@ function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, origin
108442
109282
  liveTurnPresent: liveTurn != null,
108443
109283
  lastEndedResolvedForChat: recovered != null,
108444
109284
  lastEndedThreadIdForChat: recovered?.sessionThreadId,
108445
- frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED
109285
+ frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED,
109286
+ targetChatId: chatId,
109287
+ originChatId: originTurn?.sessionChatId,
109288
+ liveChatId: liveTurn?.sessionChatId
108446
109289
  });
108447
- const via = REPLY_TOPIC_AUTHORITY_ENABLED ? originTurn != null ? originVia === "quoted" ? "quoted" : "origin" : liveTurn != null ? "live" : explicitThreadId != null ? "explicit" : recovered != null ? "recovered" : "none" : explicitThreadId != null ? "explicit" : originTurn != null ? originVia === "quoted" ? "quoted" : "origin" : liveTurn?.sessionThreadId != null ? "live" : recovered != null ? "recovered" : "none";
108448
- const explicitOverridden = REPLY_TOPIC_AUTHORITY_ENABLED && explicitThreadId != null && (originTurn != null || liveTurn != null) && threadId !== explicitThreadId;
108449
- const ownerTurn = originTurn ?? recovered ?? liveTurn;
108450
- const isSupergroup = chatId.startsWith("-100");
108451
- const unrouted = isSupergroup && threadId == null && ownerTurn == null;
108452
- const misrouteRisk = isSupergroup && via === "live" && hasDifferentThreadedRecentTurn(chatId, liveTurn?.sessionThreadId);
108453
- process.stderr.write(`telegram gateway: reply-route surface=${surface} chat=${chatId} resolved_thread=${threadId ?? "-"} via=${via} late=${liveTurn == null} originTurn=${ownerTurn?.turnId ?? "-"} origin_thread=${ownerTurn?.sessionThreadId ?? "-"}` + (via === "recovered" ? " RECOVERED" : "") + (via === "quoted" ? " QUOTED(framework-origin)" : "") + (unrouted ? " UNROUTED(supergroup\u2192no-topic)" : "") + (misrouteRisk ? " MISROUTE_RISK(no-echo\u2192live-successor)" : "") + (explicitOverridden ? ` EXPLICIT_OVERRIDDEN(model\u2192${explicitThreadId},routed\u2192${threadId ?? "-"})` : "") + `
108454
- `);
109290
+ answerRouteOverrides.note({
109291
+ chatId,
109292
+ enabled: REPLY_TOPIC_AUTHORITY_ENABLED,
109293
+ explicitThreadId,
109294
+ anchored: originAnchor != null || liveAnchor != null,
109295
+ routedThreadId: threadId,
109296
+ nowMs: Date.now()
109297
+ });
109298
+ process.stderr.write(formatReplyRouteLog2({
109299
+ surface,
109300
+ chatId,
109301
+ threadId,
109302
+ explicitThreadId,
109303
+ originTurn,
109304
+ originVia,
109305
+ liveTurn,
109306
+ recovered,
109307
+ frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED,
109308
+ hasDifferentThreadedRecentTurn: (liveThreadId) => hasDifferentThreadedRecentTurn(chatId, liveThreadId)
109309
+ }));
108455
109310
  return threadId;
108456
109311
  }
108457
109312
  function hasDifferentThreadedRecentTurn(chatId, liveThreadId) {
@@ -108928,7 +109783,7 @@ function emitTurnRecord(turn, endedAt) {
108928
109783
  },
108929
109784
  rename: (from, to) => renameSync32(from, to)
108930
109785
  });
108931
- appendFileSync11(turnsPath, rec);
109786
+ appendFileSync12(turnsPath, rec);
108932
109787
  } catch {}
108933
109788
  }
108934
109789
  function maybeProactiveCompact() {
@@ -110497,7 +111352,7 @@ var PIN_STATUS_WHILE_WORKING = (() => {
110497
111352
  })();
110498
111353
  var statusPinClaims = new Map;
110499
111354
  var statusPinRightsCache = new PinRightsCache2;
110500
- var STATUS_PIN_STORE_PATH = join77(STATE_DIR, "status-pins.json");
111355
+ var STATUS_PIN_STORE_PATH = join78(STATE_DIR, "status-pins.json");
110501
111356
  var statusPinStoreFs = {
110502
111357
  readFileSync: (p) => readFileSync67(p, "utf8"),
110503
111358
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
@@ -110505,7 +111360,7 @@ var statusPinStoreFs = {
110505
111360
  existsSync: (p) => existsSync65(p)
110506
111361
  };
110507
111362
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
110508
- var ACTIVITY_CARD_STORE_PATH = join77(STATE_DIR, "activity-cards-pending.json");
111363
+ var ACTIVITY_CARD_STORE_PATH = join78(STATE_DIR, "activity-cards-pending.json");
110509
111364
  var activityCardStoreFs = {
110510
111365
  readFileSync: (p) => readFileSync67(p, "utf8"),
110511
111366
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
@@ -110513,7 +111368,7 @@ var activityCardStoreFs = {
110513
111368
  existsSync: (p) => existsSync65(p)
110514
111369
  };
110515
111370
  var activityCardPersistEnabled = !STATIC;
110516
- var QUEUED_CARD_STORE_PATH = join77(STATE_DIR, "queued-cards-pending.json");
111371
+ var QUEUED_CARD_STORE_PATH = join78(STATE_DIR, "queued-cards-pending.json");
110517
111372
  var queuedCardStoreFs = {
110518
111373
  readFileSync: (p) => readFileSync67(p, "utf8"),
110519
111374
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
@@ -110801,7 +111656,7 @@ async function unpinAllStatusPins() {
110801
111656
  }
110802
111657
  }
110803
111658
  var stalePinSweepEligible = false;
110804
- var STALE_PIN_SWEEP_STORE_PATH = join77(STATE_DIR, "stale-pin-sweep.json");
111659
+ var STALE_PIN_SWEEP_STORE_PATH = join78(STATE_DIR, "stale-pin-sweep.json");
110805
111660
  var sweepStoreFs = {
110806
111661
  readFileSync: (p) => readFileSync67(p, "utf-8"),
110807
111662
  writeFileSync: (p, data) => atomicWriteStateFileSync2(p, data, 384),
@@ -110842,12 +111697,12 @@ var getPinnedProgressCardMessageId = null;
110842
111697
  var completeProgressCardTurn = null;
110843
111698
  var subagentWatcher = null;
110844
111699
  var workerActivityFeed = null;
110845
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join77(STATE_DIR, "gateway.sock");
111700
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join78(STATE_DIR, "gateway.sock");
110846
111701
  if (isGatewayMain)
110847
111702
  mkdirStateSync2(STATE_DIR, { recursive: true, mode: 448 });
110848
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join77(STATE_DIR, "gateway.pid.json");
110849
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join77(STATE_DIR, "gateway-session.json");
110850
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join77(STATE_DIR, "clean-shutdown.json");
111703
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join78(STATE_DIR, "gateway.pid.json");
111704
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join78(STATE_DIR, "gateway-session.json");
111705
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join78(STATE_DIR, "clean-shutdown.json");
110851
111706
  var GATEWAY_STARTED_AT_MS = Date.now();
110852
111707
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
110853
111708
  var activeBootCard = null;
@@ -110877,7 +111732,7 @@ function ensureIssuesCard(chatId, threadId) {
110877
111732
  bot: botApi,
110878
111733
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
110879
111734
  `),
110880
- persistPath: join77(stateDir, "issues-card.json")
111735
+ persistPath: join78(stateDir, "issues-card.json")
110881
111736
  });
110882
111737
  activeIssuesWatcher = startIssuesWatcher({
110883
111738
  stateDir,
@@ -111082,7 +111937,7 @@ if (isGatewayMain)
111082
111937
  var inboundSpool;
111083
111938
  if (isGatewayMain)
111084
111939
  inboundSpool = STATIC ? undefined : createInboundSpool({
111085
- path: join77(STATE_DIR, "inbound-spool.jsonl"),
111940
+ path: join78(STATE_DIR, "inbound-spool.jsonl"),
111086
111941
  fs: {
111087
111942
  appendFileSync: (p, d) => appendStateFileSync(p, d),
111088
111943
  readFileSync: (p) => readFileSync67(p, "utf8"),
@@ -111153,7 +112008,7 @@ async function maybeRedeliverUndeliveredAnswer() {
111153
112008
  let transcriptText;
111154
112009
  try {
111155
112010
  const projectsDir = getProjectsDirForCwd();
111156
- const path3 = join77(projectsDir, `${sessionId}.jsonl`);
112011
+ const path3 = join78(projectsDir, `${sessionId}.jsonl`);
111157
112012
  if (!existsSync65(path3)) {
111158
112013
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
111159
112014
  `);
@@ -111328,8 +112183,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
111328
112183
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
111329
112184
  isShuttingDown: () => shuttingDown,
111330
112185
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
111331
- crashLogPath: join77(STATE_DIR, "bridge-crash.log"),
111332
- markerPath: join77(STATE_DIR, "bridge-dead-escalation.json"),
112186
+ crashLogPath: join78(STATE_DIR, "bridge-crash.log"),
112187
+ markerPath: join78(STATE_DIR, "bridge-dead-escalation.json"),
111333
112188
  log: (line) => process.stderr.write(`${line}
111334
112189
  `),
111335
112190
  priorStreak: bridgeDeadPriorStreak,
@@ -111436,8 +112291,8 @@ if (isGatewayMain)
111436
112291
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
111437
112292
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
111438
112293
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
111439
- configSnapshotPath: join77(resolvedAgentDirForCard, ".config-snapshot.json"),
111440
- bootCardStatePath: join77(resolvedAgentDirForCard, ".boot-card-msgid.json"),
112294
+ configSnapshotPath: join78(resolvedAgentDirForCard, ".config-snapshot.json"),
112295
+ bootCardStatePath: join78(resolvedAgentDirForCard, ".boot-card-msgid.json"),
111441
112296
  floodStatePath: FLOOD_STATE_PATH,
111442
112297
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
111443
112298
  }, ackMsgId).then((handle) => {
@@ -112071,7 +112926,7 @@ if (isGatewayMain)
112071
112926
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
112072
112927
  if (Number.isInteger(receiverUid))
112073
112928
  allowedUids.push(receiverUid);
112074
- const socketPath = join77(STATE_DIR, "webhook.sock");
112929
+ const socketPath = join78(STATE_DIR, "webhook.sock");
112075
112930
  const webhookInject = (agentName3, inbound) => {
112076
112931
  const msg = inbound;
112077
112932
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -112323,9 +113178,9 @@ function redactOutboundText(text5, site) {
112323
113178
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
112324
113179
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
112325
113180
  var voiceOnDemandCache = new VoiceOnDemandCache({
112326
- persistPath: join77(STATE_DIR, "voice-ondemand.json")
113181
+ persistPath: join78(STATE_DIR, "voice-ondemand.json")
112327
113182
  });
112328
- var VOICE_CACHE_DIR = join77(STATE_DIR, "voice-cache");
113183
+ var VOICE_CACHE_DIR = join78(STATE_DIR, "voice-cache");
112329
113184
  var voicePreSynthQueue = new PreSynthQueue({
112330
113185
  runJob: async (job) => {
112331
113186
  const sidecarToken = await materializeSidecarToken2();
@@ -112733,7 +113588,7 @@ async function executeSendGif(rawArgs) {
112733
113588
  };
112734
113589
  }
112735
113590
  async function publishToTelegraph(text5, shortName, authorName) {
112736
- const accountPath = join77(STATE_DIR, "telegraph-account.json");
113591
+ const accountPath = join78(STATE_DIR, "telegraph-account.json");
112737
113592
  let account = null;
112738
113593
  try {
112739
113594
  if (existsSync65(accountPath)) {
@@ -114283,7 +115138,7 @@ function restartMarkerPath() {
114283
115138
  const agentDir = resolveAgentDirFromEnv();
114284
115139
  if (!agentDir)
114285
115140
  return null;
114286
- return join77(agentDir, "restart-pending.json");
115141
+ return join78(agentDir, "restart-pending.json");
114287
115142
  }
114288
115143
  function writeRestartMarker(marker) {
114289
115144
  const p = restartMarkerPath();
@@ -114475,7 +115330,7 @@ function _resetDockerReachableCache() {
114475
115330
  }
114476
115331
  function spawnSwitchroomDetached(args, onFailure) {
114477
115332
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
114478
- const logPath = join77(STATE_DIR, "detached-spawn.log");
115333
+ const logPath = join78(STATE_DIR, "detached-spawn.log");
114479
115334
  let outFd = null;
114480
115335
  try {
114481
115336
  mkdirStateSync2(STATE_DIR, { recursive: true });
@@ -114768,7 +115623,7 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
114768
115623
  }
114769
115624
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
114770
115625
  try {
114771
- const auditPath = join77(homedir22(), ".switchroom", "vault-audit.log");
115626
+ const auditPath = join78(homedir22(), ".switchroom", "vault-audit.log");
114772
115627
  if (!existsSync65(auditPath))
114773
115628
  return [];
114774
115629
  const raw = readFileSync67(auditPath, "utf8");
@@ -114822,7 +115677,7 @@ async function buildAgentMetadata(agentName3) {
114822
115677
  try {
114823
115678
  const agentDir = resolveAgentDirFromEnv();
114824
115679
  if (agentDir) {
114825
- const raw = readFileSync67(join77(agentDir, ".claude", ".claude.json"), "utf8");
115680
+ const raw = readFileSync67(join78(agentDir, ".claude", ".claude.json"), "utf8");
114826
115681
  claudeJson = JSON.parse(raw);
114827
115682
  }
114828
115683
  } catch {}
@@ -114951,7 +115806,7 @@ function buildModelDeps(restartCtx) {
114951
115806
  try {
114952
115807
  const agentDir = resolveAgentDirFromEnv();
114953
115808
  if (agentDir) {
114954
- const local = await fetchQuota2({ claudeConfigDir: join77(agentDir, ".claude") });
115809
+ const local = await fetchQuota2({ claudeConfigDir: join78(agentDir, ".claude") });
114955
115810
  if (local.ok)
114956
115811
  return formatQuotaLine2(local.data);
114957
115812
  }
@@ -115198,7 +116053,7 @@ function effortMenuReplyMarkup(reply) {
115198
116053
  function flushAgentHandoff(agentDir) {
115199
116054
  let removed = 0;
115200
116055
  for (const fname of [".handoff.md", ".handoff-topic"]) {
115201
- const p = join77(agentDir, fname);
116056
+ const p = join78(agentDir, fname);
115202
116057
  try {
115203
116058
  if (existsSync65(p)) {
115204
116059
  unlinkSync33(p);
@@ -115256,7 +116111,7 @@ async function handleNewCommand(ctx) {
115256
116111
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
115257
116112
  if (agentDir != null) {
115258
116113
  try {
115259
- writeFileSync55(join77(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
116114
+ writeFileSync55(join78(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
115260
116115
  `, "utf8");
115261
116116
  } catch (err) {
115262
116117
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -115373,7 +116228,7 @@ var lockoutOps = {
115373
116228
  writeFileSync: (p, data, opts) => writeStateFileSync2(p, data, opts),
115374
116229
  existsSync: (p) => existsSync65(p),
115375
116230
  mkdirSync: (p, opts) => mkdirStateSync2(p, opts),
115376
- joinPath: (...parts) => join77(...parts)
116231
+ joinPath: (...parts) => join78(...parts)
115377
116232
  };
115378
116233
  var FLEET_FALLBACK_DEDUP_MS = 30000;
115379
116234
  function isAuthBrokerSocketReachable() {
@@ -115634,7 +116489,7 @@ async function runCreditWatch() {
115634
116489
  if (!agentDir)
115635
116490
  return;
115636
116491
  const agentName3 = getMyAgentName();
115637
- const claudeConfigDir = join77(agentDir, ".claude");
116492
+ const claudeConfigDir = join78(agentDir, ".claude");
115638
116493
  const stateDir = STATE_DIR;
115639
116494
  const reason = readClaudeJsonOverage(claudeConfigDir);
115640
116495
  const prev = loadCreditState(stateDir);
@@ -117404,7 +118259,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
117404
118259
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
117405
118260
  return;
117406
118261
  }
117407
- const result = await fetchQuota2({ claudeConfigDir: join77(agentDir, ".claude") });
118262
+ const result = await fetchQuota2({ claudeConfigDir: join78(agentDir, ".claude") });
117408
118263
  if (!result.ok) {
117409
118264
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
117410
118265
  return;
@@ -118670,7 +119525,7 @@ async function startGateway() {
118670
119525
  return;
118671
119526
  }
118672
119527
  })();
118673
- const resolvedAgentDirForBootCard = agentDir ?? join77(homedir22(), ".switchroom", "agents", agentSlug);
119528
+ const resolvedAgentDirForBootCard = agentDir ?? join78(homedir22(), ".switchroom", "agents", agentSlug);
118674
119529
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
118675
119530
  agentName: agentDisplayName,
118676
119531
  agentSlug,
@@ -118684,8 +119539,8 @@ async function startGateway() {
118684
119539
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
118685
119540
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
118686
119541
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
118687
- configSnapshotPath: join77(resolvedAgentDirForBootCard, ".config-snapshot.json"),
118688
- bootCardStatePath: join77(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
119542
+ configSnapshotPath: join78(resolvedAgentDirForBootCard, ".config-snapshot.json"),
119543
+ bootCardStatePath: join78(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
118689
119544
  floodStatePath: FLOOD_STATE_PATH,
118690
119545
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
118691
119546
  }, ackMsgId);
@@ -118718,7 +119573,7 @@ async function startGateway() {
118718
119573
  if (smAgentDir) {
118719
119574
  const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
118720
119575
  const resolved = await waitForSessionModelResolution({
118721
- barrierExists: () => existsSync65(join77(smAgentDir, ".session-model-resolved")),
119576
+ barrierExists: () => existsSync65(join78(smAgentDir, ".session-model-resolved")),
118722
119577
  timeoutMs: resolutionTimeoutMs
118723
119578
  });
118724
119579
  if (!resolved) {
@@ -118726,7 +119581,7 @@ async function startGateway() {
118726
119581
  process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
118727
119582
  `);
118728
119583
  } else {
118729
- const activePath = join77(smAgentDir, ".active-session-model");
119584
+ const activePath = join78(smAgentDir, ".active-session-model");
118730
119585
  if (existsSync65(activePath)) {
118731
119586
  try {
118732
119587
  const launched = readFileSync67(activePath, "utf8").trim();
@@ -118762,12 +119617,12 @@ async function startGateway() {
118762
119617
  deliverModelSwitchBootNotice({
118763
119618
  ...modelBootCardDeps,
118764
119619
  confirmation,
118765
- hasSessionModelAlert: existsSync65(join77(smAgentDir, ".session-model-alert"))
119620
+ hasSessionModelAlert: existsSync65(join78(smAgentDir, ".session-model-alert"))
118766
119621
  });
118767
119622
  }
118768
119623
  } catch {}
118769
119624
  }
118770
- const activeEffortPath = join77(smAgentDir, ".active-session-effort");
119625
+ const activeEffortPath = join78(smAgentDir, ".active-session-effort");
118771
119626
  if (existsSync65(activeEffortPath)) {
118772
119627
  try {
118773
119628
  const launchedEffort = readFileSync67(activeEffortPath, "utf8").trim();
@@ -118775,7 +119630,7 @@ async function startGateway() {
118775
119630
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
118776
119631
  } catch {}
118777
119632
  }
118778
- const alertPath = join77(smAgentDir, ".session-model-alert");
119633
+ const alertPath = join78(smAgentDir, ".session-model-alert");
118779
119634
  if (existsSync65(alertPath)) {
118780
119635
  let alertText = null;
118781
119636
  try {
@@ -119317,6 +120172,7 @@ async function startGateway() {
119317
120172
  if (isGatewayMain)
119318
120173
  startGateway();
119319
120174
  export {
120175
+ resolveAnswerThreadWithLog,
119320
120176
  initVaultApprovalPosture,
119321
120177
  handleInboundCoalesced,
119322
120178
  handleInbound,