switchroom 0.14.21 → 0.14.22

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 (39) hide show
  1. package/dist/agent-scheduler/index.js +0 -1
  2. package/dist/auth-broker/index.js +0 -1
  3. package/dist/cli/notion-write-pretool.mjs +0 -1
  4. package/dist/cli/switchroom.js +14 -6
  5. package/dist/host-control/main.js +0 -1
  6. package/dist/vault/approvals/kernel-server.js +0 -1
  7. package/dist/vault/broker/server.js +0 -1
  8. package/package.json +3 -3
  9. package/profiles/_base/start.sh.hbs +11 -24
  10. package/profiles/_shared/telegram-style.md.hbs +2 -2
  11. package/profiles/default/CLAUDE.md.hbs +4 -1
  12. package/skills/switchroom-runtime/SKILL.md +6 -16
  13. package/telegram-plugin/agent-dir.ts +15 -0
  14. package/telegram-plugin/dist/gateway/gateway.js +640 -509
  15. package/telegram-plugin/gateway/gateway.ts +216 -61
  16. package/telegram-plugin/gateway/inbound-spool.ts +15 -0
  17. package/telegram-plugin/gateway/resume-inbound-builder.ts +180 -0
  18. package/telegram-plugin/registry/turns-schema.ts +138 -33
  19. package/telegram-plugin/stream-reply-handler.ts +1 -11
  20. package/telegram-plugin/tests/agent-dir.test.ts +25 -0
  21. package/telegram-plugin/tests/e2e.test.ts +2 -77
  22. package/telegram-plugin/tests/inbound-spool.test.ts +45 -0
  23. package/telegram-plugin/tests/multi-turn-continuity.test.ts +0 -1
  24. package/telegram-plugin/tests/outbound-ordering.test.ts +0 -1
  25. package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -1
  26. package/telegram-plugin/tests/races.test.ts +0 -26
  27. package/telegram-plugin/tests/registry-turns.test.ts +106 -29
  28. package/telegram-plugin/tests/resume-inbound-builder.test.ts +182 -0
  29. package/telegram-plugin/tests/status-accent.test.ts +0 -1
  30. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +0 -1
  31. package/telegram-plugin/tests/stream-reply-handler.test.ts +0 -24
  32. package/telegram-plugin/tests/streaming-e2e.test.ts +0 -1
  33. package/telegram-plugin/tests/streaming-orchestration.test.ts +0 -1
  34. package/telegram-plugin/tests/tool-activity-summary.test.ts +44 -0
  35. package/telegram-plugin/tests/turns-writer.test.ts +16 -6
  36. package/telegram-plugin/tool-activity-summary.ts +55 -0
  37. package/telegram-plugin/uat/driver.ts +3 -1
  38. package/telegram-plugin/handoff-continuity.ts +0 -206
  39. package/telegram-plugin/tests/handoff-continuity.test.ts +0 -262
@@ -23707,7 +23707,6 @@ var init_schema = __esm(() => {
23707
23707
  }).optional();
23708
23708
  SessionContinuitySchema = exports_external.object({
23709
23709
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
23710
- show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '\u21a9\ufe0f Picked up\u2026' " + "line to the first assistant reply after a restart (default true)."),
23711
23710
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
23712
23711
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
23713
23712
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
@@ -25600,7 +25599,7 @@ function isDockerRuntime() {
25600
25599
  import * as net3 from "node:net";
25601
25600
  import * as fs from "node:fs";
25602
25601
  import { homedir as homedir7 } from "node:os";
25603
- import { join as join16 } from "node:path";
25602
+ import { join as join15 } from "node:path";
25604
25603
  function defaultBrokerSocketPath() {
25605
25604
  if (fs.existsSync(OPERATOR_SOCKET_PATH))
25606
25605
  return OPERATOR_SOCKET_PATH;
@@ -25609,7 +25608,7 @@ function defaultBrokerSocketPath() {
25609
25608
  return LEGACY_SOCKET_PATH;
25610
25609
  }
25611
25610
  function vaultTokenFilePath(agentSlug) {
25612
- return join16(homedir7(), ".switchroom", "agents", agentSlug, ".vault-token");
25611
+ return join15(homedir7(), ".switchroom", "agents", agentSlug, ".vault-token");
25613
25612
  }
25614
25613
  function readVaultTokenFile(agentSlug) {
25615
25614
  const filePath = vaultTokenFilePath(agentSlug);
@@ -25764,8 +25763,8 @@ var DEFAULT_TIMEOUT_MS3 = 2000, LEGACY_SOCKET_PATH, OPERATOR_SOCKET_PATH;
25764
25763
  var init_client2 = __esm(() => {
25765
25764
  init_protocol2();
25766
25765
  init_peercred();
25767
- LEGACY_SOCKET_PATH = join16(homedir7(), ".switchroom", "vault-broker.sock");
25768
- OPERATOR_SOCKET_PATH = join16(homedir7(), ".switchroom", "broker-operator", "sock");
25766
+ LEGACY_SOCKET_PATH = join15(homedir7(), ".switchroom", "vault-broker.sock");
25767
+ OPERATOR_SOCKET_PATH = join15(homedir7(), ".switchroom", "broker-operator", "sock");
25769
25768
  });
25770
25769
 
25771
25770
  // ../src/drive/deep-links.ts
@@ -27878,7 +27877,7 @@ var init_secretlint_source = __esm(() => {
27878
27877
  });
27879
27878
 
27880
27879
  // gateway/auth-line.ts
27881
- function escapeHtml9(s) {
27880
+ function escapeHtml8(s) {
27882
27881
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27883
27882
  }
27884
27883
  function formatRelativeMs2(ms) {
@@ -27938,7 +27937,7 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
27938
27937
  if (!acc)
27939
27938
  continue;
27940
27939
  const marker = label === activeLabel ? "\u25b6" : "\u21b3";
27941
- const labelHtml = `<code>${escapeHtml9(acc.label)}</code>`;
27940
+ const labelHtml = `<code>${escapeHtml8(acc.label)}</code>`;
27942
27941
  const quotaLine = formatAuthQuotaLine(acc, now);
27943
27942
  rows.push(quotaLine ? `${marker} ${labelHtml} ${quotaLine}` : `${marker} ${labelHtml}`);
27944
27943
  }
@@ -27946,19 +27945,19 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
27946
27945
  }
27947
27946
 
27948
27947
  // gateway/quota-cache.ts
27949
- import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14 } from "fs";
27950
- import { join as join23, dirname as dirname8 } from "path";
27948
+ import { existsSync as existsSync25, readFileSync as readFileSync23, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14 } from "fs";
27949
+ import { join as join22, dirname as dirname8 } from "path";
27951
27950
  function defaultCachePath() {
27952
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join23(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
27951
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join22(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
27953
27952
  }
27954
27953
  function readQuotaCache(opts = {}) {
27955
27954
  const path = opts.path ?? defaultCachePath();
27956
27955
  const now = opts.now ?? Date.now();
27957
- if (!existsSync26(path))
27956
+ if (!existsSync25(path))
27958
27957
  return null;
27959
27958
  let entry;
27960
27959
  try {
27961
- entry = JSON.parse(readFileSync24(path, "utf8"));
27960
+ entry = JSON.parse(readFileSync23(path, "utf8"));
27962
27961
  } catch {
27963
27962
  return null;
27964
27963
  }
@@ -27985,7 +27984,7 @@ function writeQuotaCache(result, opts = {}) {
27985
27984
  };
27986
27985
  try {
27987
27986
  mkdirSync14(dirname8(path), { recursive: true });
27988
- writeFileSync15(path, JSON.stringify(entry, null, 2), { mode: 384 });
27987
+ writeFileSync14(path, JSON.stringify(entry, null, 2), { mode: 384 });
27989
27988
  } catch {}
27990
27989
  }
27991
27990
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -27995,8 +27994,8 @@ var init_quota_cache = __esm(() => {
27995
27994
  });
27996
27995
 
27997
27996
  // gateway/boot-probes.ts
27998
- import { readFileSync as readFileSync25, readdirSync as readdirSync4, existsSync as existsSync27 } from "fs";
27999
- import { join as join24 } from "path";
27997
+ import { readFileSync as readFileSync24, readdirSync as readdirSync4, existsSync as existsSync26 } from "fs";
27998
+ import { join as join23 } from "path";
28000
27999
  import { execFile as execFileCb } from "child_process";
28001
28000
  import { promisify as promisify3 } from "util";
28002
28001
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -28038,11 +28037,11 @@ function mapPlan(billingType, hasExtra) {
28038
28037
  }
28039
28038
  async function probeAccount(agentDir) {
28040
28039
  return withTimeout("Account", (async () => {
28041
- const claudeDir = join24(agentDir, ".claude");
28042
- const claudeJsonPath = join24(claudeDir, ".claude.json");
28040
+ const claudeDir = join23(agentDir, ".claude");
28041
+ const claudeJsonPath = join23(claudeDir, ".claude.json");
28043
28042
  let cfg = {};
28044
28043
  try {
28045
- const raw = readFileSync25(claudeJsonPath, "utf8");
28044
+ const raw = readFileSync24(claudeJsonPath, "utf8");
28046
28045
  cfg = JSON.parse(raw);
28047
28046
  } catch {
28048
28047
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -28060,12 +28059,12 @@ async function probeAccount(agentDir) {
28060
28059
  let tokenStr = "";
28061
28060
  let status = "ok";
28062
28061
  for (const candidate of [
28063
- join24(claudeDir, ".oauth-token.meta.json"),
28064
- join24(claudeDir, "accounts", "default", ".oauth-token.meta.json")
28062
+ join23(claudeDir, ".oauth-token.meta.json"),
28063
+ join23(claudeDir, "accounts", "default", ".oauth-token.meta.json")
28065
28064
  ]) {
28066
- if (existsSync27(candidate)) {
28065
+ if (existsSync26(candidate)) {
28067
28066
  try {
28068
- const meta = JSON.parse(readFileSync25(candidate, "utf8"));
28067
+ const meta = JSON.parse(readFileSync24(candidate, "utf8"));
28069
28068
  if (meta.expiresAt) {
28070
28069
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
28071
28070
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -28240,9 +28239,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28240
28239
  if (!cgroup)
28241
28240
  return null;
28242
28241
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
28243
- if (!existsSync27(procsPath))
28242
+ if (!existsSync26(procsPath))
28244
28243
  return null;
28245
- const pidsRaw = readFileSync25(procsPath, "utf-8");
28244
+ const pidsRaw = readFileSync24(procsPath, "utf-8");
28246
28245
  const pids = pidsRaw.split(`
28247
28246
  `).map((s) => s.trim()).filter(Boolean);
28248
28247
  if (pids.length === 0)
@@ -28255,7 +28254,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28255
28254
  let rss = 0;
28256
28255
  let comm = "";
28257
28256
  try {
28258
- const status = readFileSync25(`/proc/${pid}/status`, "utf-8");
28257
+ const status = readFileSync24(`/proc/${pid}/status`, "utf-8");
28259
28258
  const rssLine = status.split(`
28260
28259
  `).find((l) => l.startsWith("VmRSS:"));
28261
28260
  if (rssLine) {
@@ -28267,7 +28266,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28267
28266
  continue;
28268
28267
  }
28269
28268
  try {
28270
- comm = readFileSync25(`/proc/${pid}/comm`, "utf-8").trim();
28269
+ comm = readFileSync24(`/proc/${pid}/comm`, "utf-8").trim();
28271
28270
  } catch {}
28272
28271
  candidates.push({ pid, rss, comm });
28273
28272
  }
@@ -28447,9 +28446,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
28447
28446
  let claudeDirForProbe = null;
28448
28447
  for (const candidate of [
28449
28448
  claudeConfigDir,
28450
- join24(claudeConfigDir, "accounts", "default")
28449
+ join23(claudeConfigDir, "accounts", "default")
28451
28450
  ]) {
28452
- if (existsSync27(join24(candidate, ".oauth-token"))) {
28451
+ if (existsSync26(join23(candidate, ".oauth-token"))) {
28453
28452
  claudeDirForProbe = candidate;
28454
28453
  break;
28455
28454
  }
@@ -28514,7 +28513,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
28514
28513
  }
28515
28514
  function readContainerBootTimeMsForProbe() {
28516
28515
  try {
28517
- const stat1 = readFileSync25("/proc/1/stat", "utf8");
28516
+ const stat1 = readFileSync24("/proc/1/stat", "utf8");
28518
28517
  const lastParen = stat1.lastIndexOf(")");
28519
28518
  if (lastParen < 0)
28520
28519
  return null;
@@ -28522,7 +28521,7 @@ function readContainerBootTimeMsForProbe() {
28522
28521
  const starttimeTicks = Number(after[19]);
28523
28522
  if (!Number.isFinite(starttimeTicks))
28524
28523
  return null;
28525
- const procStat = readFileSync25("/proc/stat", "utf8");
28524
+ const procStat = readFileSync24("/proc/stat", "utf8");
28526
28525
  const btimeLine = procStat.split(`
28527
28526
  `).find((l) => l.startsWith("btime "));
28528
28527
  if (!btimeLine)
@@ -28620,7 +28619,7 @@ async function probeUds(label, socketPath, opts = {}) {
28620
28619
  }
28621
28620
  return withTimeout(label, (async () => {
28622
28621
  if (!opts.connectImpl) {
28623
- if (!existsSync27(socketPath)) {
28622
+ if (!existsSync26(socketPath)) {
28624
28623
  return {
28625
28624
  status: "fail",
28626
28625
  label,
@@ -28684,7 +28683,7 @@ async function probeSkills(agentDir, opts = {}) {
28684
28683
  return withTimeout("Skills", (async () => {
28685
28684
  const fs2 = opts.fs ?? realSkillsFs;
28686
28685
  const max = opts.maxNamesShown ?? 3;
28687
- const skillsDir = join24(agentDir, ".claude", "skills");
28686
+ const skillsDir = join23(agentDir, ".claude", "skills");
28688
28687
  if (!fs2.exists(skillsDir)) {
28689
28688
  return { status: "ok", label: "Skills", detail: "no skills dir" };
28690
28689
  }
@@ -28699,17 +28698,17 @@ async function probeSkills(agentDir, opts = {}) {
28699
28698
  }
28700
28699
  const dangling = [];
28701
28700
  for (const name of entries) {
28702
- const skillPath = join24(skillsDir, name);
28701
+ const skillPath = join23(skillsDir, name);
28703
28702
  if (!fs2.exists(skillPath)) {
28704
28703
  dangling.push(name);
28705
28704
  continue;
28706
28705
  }
28707
- const skillMd = join24(skillPath, "SKILL.md");
28706
+ const skillMd = join23(skillPath, "SKILL.md");
28708
28707
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
28709
28708
  continue;
28710
28709
  }
28711
28710
  }
28712
- const overlayDir = opts.overlaySkillsDir ?? join24(agentDir, "skills.d");
28711
+ const overlayDir = opts.overlaySkillsDir ?? join23(agentDir, "skills.d");
28713
28712
  const overlaySlugs = new Set;
28714
28713
  if (fs2.exists(overlayDir)) {
28715
28714
  let overlayEntries = [];
@@ -28756,24 +28755,24 @@ var init_boot_probes = __esm(() => {
28756
28755
  execFile3 = promisify3(execFileCb);
28757
28756
  realProcFs = {
28758
28757
  readdir: (p) => readdirSync4(p),
28759
- readFile: (p) => readFileSync25(p, "utf-8")
28758
+ readFile: (p) => readFileSync24(p, "utf-8")
28760
28759
  };
28761
28760
  realSchedulerFs = {
28762
- readFile: (p) => readFileSync25(p, "utf-8"),
28761
+ readFile: (p) => readFileSync24(p, "utf-8"),
28763
28762
  mtimeMs: (p) => {
28764
28763
  const { statSync: statSync7 } = __require("fs");
28765
28764
  return statSync7(p).mtimeMs;
28766
28765
  },
28767
- exists: (p) => existsSync27(p)
28766
+ exists: (p) => existsSync26(p)
28768
28767
  };
28769
28768
  realSkillsFs = {
28770
28769
  readdir: (p) => readdirSync4(p),
28771
- exists: (p) => existsSync27(p)
28770
+ exists: (p) => existsSync26(p)
28772
28771
  };
28773
28772
  });
28774
28773
 
28775
28774
  // gateway/boot-issue-cache.ts
28776
- import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, renameSync as renameSync9 } from "fs";
28775
+ import { existsSync as existsSync27, readFileSync as readFileSync25, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15, renameSync as renameSync8 } from "fs";
28777
28776
  import { dirname as dirname9 } from "path";
28778
28777
  function fingerprintProbe(key, r) {
28779
28778
  if (r.status === "ok")
@@ -28853,11 +28852,11 @@ function diffProbes(probes, cache, opts = {}) {
28853
28852
  return out;
28854
28853
  }
28855
28854
  function loadCache(path, now = Date.now) {
28856
- if (!existsSync28(path))
28855
+ if (!existsSync27(path))
28857
28856
  return { ...EMPTY_CACHE, probes: {} };
28858
28857
  let raw;
28859
28858
  try {
28860
- raw = readFileSync26(path, "utf-8");
28859
+ raw = readFileSync25(path, "utf-8");
28861
28860
  } catch {
28862
28861
  return { ...EMPTY_CACHE, probes: {} };
28863
28862
  }
@@ -28866,7 +28865,7 @@ function loadCache(path, now = Date.now) {
28866
28865
  parsed = JSON.parse(raw);
28867
28866
  } catch {
28868
28867
  try {
28869
- renameSync9(path, `${path}.corrupt-${now()}`);
28868
+ renameSync8(path, `${path}.corrupt-${now()}`);
28870
28869
  } catch {}
28871
28870
  return { ...EMPTY_CACHE, probes: {} };
28872
28871
  }
@@ -28902,8 +28901,8 @@ function applyAndSave(path, cache, diff) {
28902
28901
  try {
28903
28902
  mkdirSync15(dirname9(path), { recursive: true });
28904
28903
  const tmp = `${path}.tmp`;
28905
- writeFileSync16(tmp, JSON.stringify(next), { mode: 384 });
28906
- renameSync9(tmp, path);
28904
+ writeFileSync15(tmp, JSON.stringify(next), { mode: 384 });
28905
+ renameSync8(tmp, path);
28907
28906
  } catch {}
28908
28907
  return next;
28909
28908
  }
@@ -28916,7 +28915,7 @@ var init_boot_issue_cache = __esm(() => {
28916
28915
 
28917
28916
  // gateway/config-snapshot.ts
28918
28917
  import { createHash as createHash2 } from "crypto";
28919
- import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16, renameSync as renameSync10 } from "fs";
28918
+ import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync16, mkdirSync as mkdirSync16, renameSync as renameSync9 } from "fs";
28920
28919
  import { dirname as dirname10 } from "path";
28921
28920
  function hashStringArray(items) {
28922
28921
  if (!items || items.length === 0)
@@ -28981,11 +28980,11 @@ function renderConfigChangeDim(dim) {
28981
28980
  }
28982
28981
  }
28983
28982
  function loadSnapshot(path, now = Date.now) {
28984
- if (!existsSync29(path))
28983
+ if (!existsSync28(path))
28985
28984
  return null;
28986
28985
  let raw;
28987
28986
  try {
28988
- raw = readFileSync27(path, "utf-8");
28987
+ raw = readFileSync26(path, "utf-8");
28989
28988
  } catch {
28990
28989
  return null;
28991
28990
  }
@@ -28994,7 +28993,7 @@ function loadSnapshot(path, now = Date.now) {
28994
28993
  parsed = JSON.parse(raw);
28995
28994
  } catch {
28996
28995
  try {
28997
- renameSync10(path, `${path}.corrupt-${now()}`);
28996
+ renameSync9(path, `${path}.corrupt-${now()}`);
28998
28997
  } catch {}
28999
28998
  return null;
29000
28999
  }
@@ -29017,8 +29016,8 @@ function persistSnapshot(path, snapshot) {
29017
29016
  try {
29018
29017
  mkdirSync16(dirname10(path), { recursive: true });
29019
29018
  const tmp = `${path}.tmp`;
29020
- writeFileSync17(tmp, JSON.stringify(snapshot), { mode: 384 });
29021
- renameSync10(tmp, path);
29019
+ writeFileSync16(tmp, JSON.stringify(snapshot), { mode: 384 });
29020
+ renameSync9(tmp, path);
29022
29021
  } catch {}
29023
29022
  }
29024
29023
  var init_config_snapshot = () => {};
@@ -29033,7 +29032,7 @@ __export(exports_boot_card, {
29033
29032
  renderBootCard: () => renderBootCard,
29034
29033
  renderAccountRows: () => renderAuthLine
29035
29034
  });
29036
- import { join as join25 } from "path";
29035
+ import { join as join24 } from "path";
29037
29036
  function resolvePersonaName(slug, loadConfig3) {
29038
29037
  try {
29039
29038
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -29123,7 +29122,7 @@ function renderBootCard(opts) {
29123
29122
  `);
29124
29123
  }
29125
29124
  async function runAllProbes(opts) {
29126
- const claudeDir = join25(opts.agentDir, ".claude");
29125
+ const claudeDir = join24(opts.agentDir, ".claude");
29127
29126
  const probes = {};
29128
29127
  const slug = opts.agentSlug ?? opts.agentName;
29129
29128
  await Promise.allSettled([
@@ -29472,16 +29471,16 @@ function renderAccountRow2(snap, opts) {
29472
29471
  const lines = [];
29473
29472
  const marker = snap.isActive ? "\u25cf " : "";
29474
29473
  if (!snap.quota) {
29475
- lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> <i>quota probe failed</i>`);
29474
+ lines.push(`${marker}<code>${escapeHtml11(snap.label)}</code> <i>quota probe failed</i>`);
29476
29475
  if (snap.quotaError) {
29477
- lines.push(` <i>${escapeHtml12(snap.quotaError)}</i>`);
29476
+ lines.push(` <i>${escapeHtml11(snap.quotaError)}</i>`);
29478
29477
  }
29479
29478
  return lines;
29480
29479
  }
29481
29480
  const q = snap.quota;
29482
29481
  const fiveStr = fmtPct2(q.fiveHourUtilizationPct);
29483
29482
  const sevenStr = fmtPct2(q.sevenDayUtilizationPct);
29484
- lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
29483
+ lines.push(`${marker}<code>${escapeHtml11(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
29485
29484
  const health = classifyHealth2(snap);
29486
29485
  if (health === "blocked") {
29487
29486
  const win = bindingWindow3(q);
@@ -29587,13 +29586,13 @@ function renderFallbackAnnouncement2(input) {
29587
29586
  const limitWord = input.oldQuota ? limitWordFor2(input.oldQuota) : "quota";
29588
29587
  const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
29589
29588
  if (!input.newLabel) {
29590
- lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
29589
+ lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml11(input.oldLabel)}</b>`);
29591
29590
  lines.push("");
29592
- lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
29591
+ lines.push(`Triggered by: agent <b>${escapeHtml11(input.triggerAgent)}</b>`);
29593
29592
  if (input.oldQuota) {
29594
29593
  const recovery = recoveryAtFor2(input.oldQuota);
29595
29594
  if (recovery) {
29596
- lines.push(`${escapeHtml12(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
29595
+ lines.push(`${escapeHtml11(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
29597
29596
  }
29598
29597
  }
29599
29598
  lines.push("");
@@ -29601,15 +29600,15 @@ function renderFallbackAnnouncement2(input) {
29601
29600
  return lines.join(`
29602
29601
  `);
29603
29602
  }
29604
- lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
29603
+ lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml11(input.oldLabel)}</b>`);
29605
29604
  lines.push("");
29606
- lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> \u2192 <code>${escapeHtml12(input.newLabel)}</code>`);
29607
- lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
29605
+ lines.push(`<code>${escapeHtml11(input.oldLabel)}</code> \u2192 <code>${escapeHtml11(input.newLabel)}</code>`);
29606
+ lines.push(`Triggered by: agent <b>${escapeHtml11(input.triggerAgent)}</b>`);
29608
29607
  lines.push("");
29609
29608
  if (input.oldQuota) {
29610
29609
  const recovery = recoveryAtFor2(input.oldQuota);
29611
29610
  if (recovery) {
29612
- lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
29611
+ lines.push(`<code>${escapeHtml11(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
29613
29612
  }
29614
29613
  }
29615
29614
  if (input.newQuota) {
@@ -29617,7 +29616,7 @@ function renderFallbackAnnouncement2(input) {
29617
29616
  const sevenStr = fmtPct2(input.newQuota.sevenDayUtilizationPct);
29618
29617
  const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT2 && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT2;
29619
29618
  const headroomStr = hasHeadroom ? "<i>(plenty of headroom)</i>" : "<i>(near limit \u2014 watch this)</i>";
29620
- lines.push(`<code>${escapeHtml12(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
29619
+ lines.push(`<code>${escapeHtml11(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
29621
29620
  } else {
29622
29621
  lines.push(`<i>(quota probe for new account is pending \u2014 will reflect on next /auth)</i>`);
29623
29622
  }
@@ -29676,7 +29675,7 @@ function switchPriority2(s) {
29676
29675
  return 2;
29677
29676
  return 3;
29678
29677
  }
29679
- function escapeHtml12(s) {
29678
+ function escapeHtml11(s) {
29680
29679
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29681
29680
  }
29682
29681
  function buildSnapshotsFromState2(state4, quotas) {
@@ -29741,12 +29740,12 @@ var init_flock = () => {};
29741
29740
  // ../src/vault/vault.ts
29742
29741
  import { randomBytes as randomBytes5, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
29743
29742
  import {
29744
- readFileSync as readFileSync35,
29745
- writeFileSync as writeFileSync23,
29746
- existsSync as existsSync37,
29747
- renameSync as renameSync12,
29743
+ readFileSync as readFileSync34,
29744
+ writeFileSync as writeFileSync22,
29745
+ existsSync as existsSync36,
29746
+ renameSync as renameSync11,
29748
29747
  mkdirSync as mkdirSync23,
29749
- unlinkSync as unlinkSync13,
29748
+ unlinkSync as unlinkSync12,
29750
29749
  lstatSync,
29751
29750
  realpathSync
29752
29751
  } from "node:fs";
@@ -29781,12 +29780,12 @@ function normalizeSecrets(raw) {
29781
29780
  return out;
29782
29781
  }
29783
29782
  function openVault(passphrase, vaultPath) {
29784
- if (!existsSync37(vaultPath)) {
29783
+ if (!existsSync36(vaultPath)) {
29785
29784
  throw new VaultError(`Vault file not found: ${vaultPath}`);
29786
29785
  }
29787
29786
  let vaultFile;
29788
29787
  try {
29789
- vaultFile = JSON.parse(readFileSync35(vaultPath, "utf8"));
29788
+ vaultFile = JSON.parse(readFileSync34(vaultPath, "utf8"));
29790
29789
  } catch {
29791
29790
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
29792
29791
  }
@@ -29840,7 +29839,7 @@ import {
29840
29839
  statSync as statSync11,
29841
29840
  writeSync as writeSync2
29842
29841
  } from "node:fs";
29843
- import { join as join35 } from "node:path";
29842
+ import { join as join34 } from "node:path";
29844
29843
  import { tmpdir } from "node:os";
29845
29844
  import { constants as fsConstants } from "node:fs";
29846
29845
  function isVaultReference(value) {
@@ -29892,11 +29891,11 @@ function materializationRoot() {
29892
29891
  return cachedRoot;
29893
29892
  const xdg = process.env.XDG_RUNTIME_DIR;
29894
29893
  if (xdg) {
29895
- const base = join35(xdg, "switchroom", "vault");
29894
+ const base = join34(xdg, "switchroom", "vault");
29896
29895
  mkdirSync24(base, { recursive: true, mode: 448 });
29897
- cachedRoot = mkdtempSync2(join35(base, "run-"));
29896
+ cachedRoot = mkdtempSync2(join34(base, "run-"));
29898
29897
  } else {
29899
- cachedRoot = mkdtempSync2(join35(tmpdir(), "switchroom-vault-"));
29898
+ cachedRoot = mkdtempSync2(join34(tmpdir(), "switchroom-vault-"));
29900
29899
  }
29901
29900
  chmodSync5(cachedRoot, 448);
29902
29901
  return cachedRoot;
@@ -29911,7 +29910,7 @@ function writeFileExclusive(filePath, content) {
29911
29910
  }
29912
29911
  }
29913
29912
  function materializeFilesEntry(key, files) {
29914
- const dir = join35(materializationRoot(), key);
29913
+ const dir = join34(materializationRoot(), key);
29915
29914
  if (materializedDirs.has(dir)) {
29916
29915
  try {
29917
29916
  rmSync3(dir, { recursive: true, force: true });
@@ -29927,7 +29926,7 @@ function materializeFilesEntry(key, files) {
29927
29926
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
29928
29927
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
29929
29928
  }
29930
- const filePath = join35(dir, filename);
29929
+ const filePath = join34(dir, filename);
29931
29930
  const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
29932
29931
  writeFileExclusive(filePath, content);
29933
29932
  }
@@ -30060,7 +30059,7 @@ __export(exports_materialize_bot_token, {
30060
30059
  materializeBotToken: () => materializeBotToken,
30061
30060
  BotTokenMaterializeError: () => BotTokenMaterializeError
30062
30061
  });
30063
- import { existsSync as existsSync38 } from "node:fs";
30062
+ import { existsSync as existsSync37 } from "node:fs";
30064
30063
  function pickConfiguredToken(config, agentName3) {
30065
30064
  if (agentName3) {
30066
30065
  const agent = config.agents?.[agentName3];
@@ -30074,7 +30073,7 @@ function tryDirectVaultRead(ref, config, passphrase) {
30074
30073
  if (!passphrase)
30075
30074
  return null;
30076
30075
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
30077
- if (!existsSync38(vaultPath))
30076
+ if (!existsSync37(vaultPath))
30078
30077
  return null;
30079
30078
  try {
30080
30079
  const secrets = openVault(passphrase, vaultPath);
@@ -30166,7 +30165,7 @@ __export(exports_tmux, {
30166
30165
  captureAgentPane: () => captureAgentPane
30167
30166
  });
30168
30167
  import { execFileSync as execFileSync4 } from "node:child_process";
30169
- import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync14, writeFileSync as writeFileSync24 } from "node:fs";
30168
+ import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync23 } from "node:fs";
30170
30169
  import { resolve as resolve7 } from "node:path";
30171
30170
  function captureAgentPane(opts) {
30172
30171
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -30212,7 +30211,7 @@ function captureAgentPane(opts) {
30212
30211
  ` + `
30213
30212
  `;
30214
30213
  try {
30215
- writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30214
+ writeFileSync23(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30216
30215
  mode: 420
30217
30216
  });
30218
30217
  } catch (err) {
@@ -30280,7 +30279,7 @@ function pruneOldReports(dir, retain) {
30280
30279
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
30281
30280
  for (const stale of files.slice(retain)) {
30282
30281
  try {
30283
- unlinkSync14(stale.full);
30282
+ unlinkSync13(stale.full);
30284
30283
  } catch {}
30285
30284
  }
30286
30285
  }
@@ -30322,7 +30321,7 @@ function truncateDiffForCard(unifiedDiff, maxLines = 50, maxChars = 3000) {
30322
30321
  }
30323
30322
  return out === unifiedDiff ? out : out + sentinel;
30324
30323
  }
30325
- function escapeHtml13(s) {
30324
+ function escapeHtml12(s) {
30326
30325
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
30327
30326
  }
30328
30327
  function clipReason(reason) {
@@ -30333,10 +30332,10 @@ function clipReason(reason) {
30333
30332
  function buildConfigApprovalCardBody(args) {
30334
30333
  const safeReason = clipReason(args.reason);
30335
30334
  const render = (diff) => `\uD83D\uDEE0 <b>Config edit proposed</b>
30336
- ` + `Agent: <code>${escapeHtml13(args.agentName)}</code>
30337
- ` + `Reason: ${escapeHtml13(safeReason)}
30335
+ ` + `Agent: <code>${escapeHtml12(args.agentName)}</code>
30336
+ ` + `Reason: ${escapeHtml12(safeReason)}
30338
30337
 
30339
- ` + `<pre>${escapeHtml13(diff)}</pre>`;
30338
+ ` + `<pre>${escapeHtml12(diff)}</pre>`;
30340
30339
  return truncateRawToFit({
30341
30340
  raw: args.unifiedDiff,
30342
30341
  render,
@@ -30448,8 +30447,8 @@ async function handleRequestConfigFinalize(_client, msg, deps) {
30448
30447
  }
30449
30448
  pending.delete(msg.requestId);
30450
30449
  const body = msg.outcome === "applied" ? `\u2705 <b>Applied</b>${msg.detail ? `
30451
- ${escapeHtml13(msg.detail)}` : ""}` : `\u26a0\ufe0f <b>Reconcile failed; rolled back</b>${msg.detail ? `
30452
- ${escapeHtml13(msg.detail)}` : ""}`;
30450
+ ${escapeHtml12(msg.detail)}` : ""}` : `\u26a0\ufe0f <b>Reconcile failed; rolled back</b>${msg.detail ? `
30451
+ ${escapeHtml12(msg.detail)}` : ""}`;
30453
30452
  try {
30454
30453
  await deps.editCard({
30455
30454
  chatId: entry.chatId,
@@ -30589,17 +30588,17 @@ function registerApprovalsCommands(bot, opts) {
30589
30588
  return;
30590
30589
  }
30591
30590
  if (decisions.length === 0) {
30592
- await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml14(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
30591
+ await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml13(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
30593
30592
  return;
30594
30593
  }
30595
30594
  const byAgent = new Map;
30596
30595
  for (const d of decisions)
30597
30596
  byAgent.set(d.agent_unit, (byAgent.get(d.agent_unit) ?? 0) + 1);
30598
- const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml14(a)}</b>: ${n}`).join(`
30597
+ const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml13(a)}</b>: ${n}`).join(`
30599
30598
  `);
30600
30599
  const detail = decisions.slice(0, 20).map((d) => {
30601
30600
  const ttl = d.ttl_expires_at === null ? "always" : `until ${new Date(d.ttl_expires_at).toISOString().slice(0, 16).replace("T", " ")}`;
30602
- return `<code>${escapeHtml14(d.id.slice(0, 8))}</code> ` + `${escapeHtml14(d.agent_unit)} \u2192 ` + `<code>${escapeHtml14(d.scope)}</code> ` + `(${escapeHtml14(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml14(d.id)}`;
30601
+ return `<code>${escapeHtml13(d.id.slice(0, 8))}</code> ` + `${escapeHtml13(d.agent_unit)} \u2192 ` + `<code>${escapeHtml13(d.scope)}</code> ` + `(${escapeHtml13(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml13(d.id)}`;
30603
30602
  }).join(`
30604
30603
  `);
30605
30604
  await ctx.reply(`<b>Active approvals</b>
@@ -30625,13 +30624,13 @@ ${detail}`, {
30625
30624
  await ctx.reply("Approval kernel unreachable.");
30626
30625
  return;
30627
30626
  }
30628
- await ctx.reply(ok ? `Revoked <code>${escapeHtml14(id)}</code>.` : `No such active decision <code>${escapeHtml14(id)}</code>.`, { parse_mode: "HTML" });
30627
+ await ctx.reply(ok ? `Revoked <code>${escapeHtml13(id)}</code>.` : `No such active decision <code>${escapeHtml13(id)}</code>.`, { parse_mode: "HTML" });
30629
30628
  return;
30630
30629
  }
30631
- await ctx.reply(`Unknown subcommand <code>${escapeHtml14(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke &lt;id&gt;</code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
30630
+ await ctx.reply(`Unknown subcommand <code>${escapeHtml13(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke &lt;id&gt;</code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
30632
30631
  });
30633
30632
  }
30634
- function escapeHtml14(s) {
30633
+ function escapeHtml13(s) {
30635
30634
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
30636
30635
  }
30637
30636
  var init_approvals_commands = __esm(() => {
@@ -30772,23 +30771,23 @@ var import_runner2 = __toESM(require_mod3(), 1);
30772
30771
  import { randomBytes as randomBytes6 } from "crypto";
30773
30772
  import { execFileSync as execFileSync5, execSync as execSync2, spawn as spawn2 } from "child_process";
30774
30773
  import {
30775
- readFileSync as readFileSync36,
30776
- writeFileSync as writeFileSync25,
30774
+ readFileSync as readFileSync35,
30775
+ writeFileSync as writeFileSync24,
30777
30776
  mkdirSync as mkdirSync26,
30778
30777
  readdirSync as readdirSync7,
30779
30778
  rmSync as rmSync4,
30780
30779
  statSync as statSync13,
30781
- renameSync as renameSync13,
30780
+ renameSync as renameSync12,
30782
30781
  realpathSync as realpathSync2,
30783
30782
  chmodSync as chmodSync6,
30784
30783
  openSync as openSync8,
30785
30784
  closeSync as closeSync8,
30786
- existsSync as existsSync39,
30787
- unlinkSync as unlinkSync15,
30785
+ existsSync as existsSync38,
30786
+ unlinkSync as unlinkSync14,
30788
30787
  appendFileSync as appendFileSync5
30789
30788
  } from "fs";
30790
30789
  import { homedir as homedir14 } from "os";
30791
- import { join as join36, extname, sep as sep3, basename as basename7 } from "path";
30790
+ import { join as join35, extname, sep as sep3, basename as basename7 } from "path";
30792
30791
 
30793
30792
  // plugin-logger.ts
30794
30793
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -32182,6 +32181,33 @@ function renderActivityFeed(lines) {
32182
32181
  return out.join(`
32183
32182
  `);
32184
32183
  }
32184
+ var NESTED_MAX_LINES = 4;
32185
+ var NESTED_LINE_MAX = 90;
32186
+ var NESTED_PREFIX = " \u21b3 ";
32187
+ function renderActivityFeedWithNested(lines, childLines) {
32188
+ const children = childLines.map((s) => s.trim()).filter((s) => s.length > 0);
32189
+ if (children.length === 0)
32190
+ return renderActivityFeed(lines);
32191
+ const out = [];
32192
+ const shownParent = lines.slice(-MIRROR_MAX_LINES);
32193
+ const hiddenParent = lines.length - shownParent.length;
32194
+ if (hiddenParent > 0)
32195
+ out.push(`<i>\u2713 +${hiddenParent} earlier\u2026</i>`);
32196
+ for (const l of shownParent)
32197
+ out.push(`<i>\u2713 ${escapeFeedHtml(l)}</i>`);
32198
+ const shownChild = children.slice(-NESTED_MAX_LINES);
32199
+ const hiddenChild = children.length - shownChild.length;
32200
+ if (hiddenChild > 0)
32201
+ out.push(`${NESTED_PREFIX}<i>+${hiddenChild} earlier\u2026</i>`);
32202
+ const lastChildIdx = shownChild.length - 1;
32203
+ shownChild.forEach((l, i) => {
32204
+ const t = l.length > NESTED_LINE_MAX ? l.slice(0, NESTED_LINE_MAX - 1) + "\u2026" : l;
32205
+ const esc = escapeFeedHtml(t);
32206
+ out.push(i === lastChildIdx ? `${NESTED_PREFIX}<b>\u2192 ${esc}</b>` : `${NESTED_PREFIX}<i>${esc}</i>`);
32207
+ });
32208
+ return out.length > 0 ? out.join(`
32209
+ `) : null;
32210
+ }
32185
32211
  function appendActivityLabel(lines, label) {
32186
32212
  const l = (label ?? "").trim();
32187
32213
  if (l.length === 0)
@@ -33198,11 +33224,6 @@ async function handleStreamReply(args, state, deps) {
33198
33224
  done,
33199
33225
  streamExisted
33200
33226
  });
33201
- if (!stream) {
33202
- const prefix = deps.takeHandoffPrefix(format === "html" ? "html" : format === "markdownv2" ? "markdownv2" : "text");
33203
- if (prefix.length > 0)
33204
- effectiveText = prefix + effectiveText;
33205
- }
33206
33227
  if (!stream) {
33207
33228
  let replyToMessageId;
33208
33229
  if (args.reply_to != null) {
@@ -42129,125 +42150,29 @@ function isTurnFlushSafetyEnabled(env = process.env) {
42129
42150
  return true;
42130
42151
  }
42131
42152
 
42132
- // handoff-continuity.ts
42133
- import { readFileSync as readFileSync9, unlinkSync as unlinkSync2, existsSync as existsSync13, writeFileSync as writeFileSync6, renameSync as renameSync2 } from "node:fs";
42134
- import { dirname as dirname7, join as join13 } from "node:path";
42135
- var TOPIC_DISPLAY_MAX = 117;
42136
- var HANDOFF_TOPIC_FILENAME = ".handoff-topic";
42137
- var LAST_TURN_SUMMARY_FILENAME = ".last-turn-summary";
42153
+ // agent-dir.ts
42154
+ import { dirname as dirname7 } from "node:path";
42138
42155
  function resolveAgentDirFromEnv() {
42139
42156
  const state3 = process.env.TELEGRAM_STATE_DIR;
42140
42157
  if (!state3 || state3.trim().length === 0)
42141
42158
  return null;
42142
42159
  return dirname7(state3);
42143
42160
  }
42144
- function readHandoffTopic(agentDir) {
42145
- const p = join13(agentDir, HANDOFF_TOPIC_FILENAME);
42146
- if (!existsSync13(p))
42147
- return null;
42148
- let raw;
42149
- try {
42150
- raw = readFileSync9(p, "utf-8");
42151
- } catch {
42152
- return null;
42153
- }
42154
- const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
42155
- if (lines.length === 0)
42156
- return null;
42157
- let topic = lines[0];
42158
- if (topic.length > TOPIC_DISPLAY_MAX) {
42159
- topic = topic.slice(0, TOPIC_DISPLAY_MAX) + "\u2026";
42160
- }
42161
- return topic;
42162
- }
42163
- function readLastTurnSummary(agentDir) {
42164
- const p = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
42165
- if (!existsSync13(p))
42166
- return null;
42167
- let raw;
42168
- try {
42169
- raw = readFileSync9(p, "utf-8");
42170
- } catch {
42171
- return null;
42172
- }
42173
- const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
42174
- if (lines.length === 0)
42175
- return null;
42176
- let topic = lines[0];
42177
- if (topic.length > TOPIC_DISPLAY_MAX) {
42178
- topic = topic.slice(0, TOPIC_DISPLAY_MAX) + "\u2026";
42179
- }
42180
- return topic;
42181
- }
42182
- function consumeHandoffTopic(agentDir) {
42183
- const primary = readHandoffTopic(agentDir);
42184
- const primaryPath = join13(agentDir, HANDOFF_TOPIC_FILENAME);
42185
- const fallbackPath = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
42186
- const removeFallback = () => {
42187
- try {
42188
- unlinkSync2(fallbackPath);
42189
- } catch {}
42190
- };
42191
- if (primary !== null) {
42192
- try {
42193
- unlinkSync2(primaryPath);
42194
- } catch {}
42195
- removeFallback();
42196
- return primary;
42197
- }
42198
- const fallback = readLastTurnSummary(agentDir);
42199
- if (fallback !== null) {
42200
- removeFallback();
42201
- return fallback;
42202
- }
42203
- return null;
42204
- }
42205
- function shouldShowHandoffLine() {
42206
- const v = process.env.SWITCHROOM_HANDOFF_SHOW_LINE;
42207
- if (v === undefined)
42208
- return true;
42209
- return v.toLowerCase() !== "false";
42210
- }
42211
- function formatHandoffLine(topic, format) {
42212
- const prefix = "\u21a9\ufe0f Picked up where we left off, ";
42213
- if (format === "html") {
42214
- return `<i>${prefix}${escapeHtml7(topic)}</i>
42215
-
42216
- `;
42217
- }
42218
- if (format === "markdownv2") {
42219
- const escaped = escapeMarkdownV2(topic);
42220
- const prefixEsc = escapeMarkdownV2(prefix);
42221
- return `_${prefixEsc}${escaped}_
42222
-
42223
- `;
42224
- }
42225
- return `${prefix}${topic}
42226
-
42227
- `;
42228
- }
42229
- function escapeHtml7(s) {
42230
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42231
- }
42232
- var MDV2_SPECIALS = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
42233
- function escapeMarkdownV2(s) {
42234
- return s.replace(MDV2_SPECIALS, (m) => "\\" + m);
42235
- }
42236
42161
 
42237
42162
  // active-reactions.ts
42238
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync3, existsSync as existsSync14, unlinkSync as unlinkSync3 } from "node:fs";
42239
- import { join as join14 } from "node:path";
42163
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, unlinkSync as unlinkSync2 } from "node:fs";
42164
+ import { join as join13 } from "node:path";
42240
42165
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
42241
42166
  function reactionsPath(agentDir) {
42242
- return join14(agentDir, ACTIVE_REACTIONS_FILENAME);
42167
+ return join13(agentDir, ACTIVE_REACTIONS_FILENAME);
42243
42168
  }
42244
42169
  function readActiveReactions(agentDir) {
42245
42170
  const p = reactionsPath(agentDir);
42246
- if (!existsSync14(p))
42171
+ if (!existsSync13(p))
42247
42172
  return [];
42248
42173
  let raw;
42249
42174
  try {
42250
- raw = readFileSync10(p, "utf-8");
42175
+ raw = readFileSync9(p, "utf-8");
42251
42176
  } catch {
42252
42177
  return [];
42253
42178
  }
@@ -42273,15 +42198,15 @@ function writeActiveReactions(agentDir, reactions) {
42273
42198
  const p = reactionsPath(agentDir);
42274
42199
  if (reactions.length === 0) {
42275
42200
  try {
42276
- unlinkSync3(p);
42201
+ unlinkSync2(p);
42277
42202
  } catch {}
42278
42203
  return;
42279
42204
  }
42280
42205
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
42281
42206
  try {
42282
- writeFileSync7(tmp, JSON.stringify(reactions) + `
42207
+ writeFileSync6(tmp, JSON.stringify(reactions) + `
42283
42208
  `, "utf-8");
42284
- renameSync3(tmp, p);
42209
+ renameSync2(tmp, p);
42285
42210
  } catch {}
42286
42211
  }
42287
42212
  function addActiveReaction(agentDir, reaction) {
@@ -42298,24 +42223,24 @@ function removeActiveReaction(agentDir, chatId, messageId) {
42298
42223
  }
42299
42224
  function clearActiveReactions(agentDir) {
42300
42225
  try {
42301
- unlinkSync3(reactionsPath(agentDir));
42226
+ unlinkSync2(reactionsPath(agentDir));
42302
42227
  } catch {}
42303
42228
  }
42304
42229
 
42305
42230
  // active-reactions.ts
42306
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync4, existsSync as existsSync15, unlinkSync as unlinkSync4 } from "node:fs";
42307
- import { join as join15 } from "node:path";
42231
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync3, existsSync as existsSync14, unlinkSync as unlinkSync3 } from "node:fs";
42232
+ import { join as join14 } from "node:path";
42308
42233
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
42309
42234
  function reactionsPath2(agentDir) {
42310
- return join15(agentDir, ACTIVE_REACTIONS_FILENAME2);
42235
+ return join14(agentDir, ACTIVE_REACTIONS_FILENAME2);
42311
42236
  }
42312
42237
  function readActiveReactions2(agentDir) {
42313
42238
  const p = reactionsPath2(agentDir);
42314
- if (!existsSync15(p))
42239
+ if (!existsSync14(p))
42315
42240
  return [];
42316
42241
  let raw;
42317
42242
  try {
42318
- raw = readFileSync11(p, "utf-8");
42243
+ raw = readFileSync10(p, "utf-8");
42319
42244
  } catch {
42320
42245
  return [];
42321
42246
  }
@@ -42339,7 +42264,7 @@ function readActiveReactions2(agentDir) {
42339
42264
  }
42340
42265
  function clearActiveReactions2(agentDir) {
42341
42266
  try {
42342
- unlinkSync4(reactionsPath2(agentDir));
42267
+ unlinkSync3(reactionsPath2(agentDir));
42343
42268
  } catch {}
42344
42269
  }
42345
42270
 
@@ -43192,17 +43117,17 @@ async function approvalRecord(args, opts) {
43192
43117
  }
43193
43118
 
43194
43119
  // quota-check.ts
43195
- import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
43196
- import { join as join17 } from "path";
43120
+ import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
43121
+ import { join as join16 } from "path";
43197
43122
  var OAUTH_BETA2 = "oauth-2025-04-20";
43198
43123
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
43199
43124
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
43200
43125
  function readOauthToken2(claudeConfigDir) {
43201
- const tokenFile = join17(claudeConfigDir, ".oauth-token");
43202
- if (!existsSync17(tokenFile))
43126
+ const tokenFile = join16(claudeConfigDir, ".oauth-token");
43127
+ if (!existsSync16(tokenFile))
43203
43128
  return null;
43204
43129
  try {
43205
- const raw = readFileSync13(tokenFile, "utf-8").trim();
43130
+ const raw = readFileSync12(tokenFile, "utf-8").trim();
43206
43131
  return raw.length > 0 ? raw : null;
43207
43132
  } catch {
43208
43133
  return null;
@@ -43798,7 +43723,7 @@ init_schema();
43798
43723
  init_paths();
43799
43724
  init_overlay_loader();
43800
43725
  init_merge();
43801
- import { readFileSync as readFileSync14, existsSync as existsSync18 } from "node:fs";
43726
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "node:fs";
43802
43727
  import { homedir as homedir8 } from "node:os";
43803
43728
  import { resolve as resolve5 } from "node:path";
43804
43729
 
@@ -43874,7 +43799,7 @@ function findConfigFile2(startDir) {
43874
43799
  resolve5(userDir, "clerk.yml")
43875
43800
  ].filter(Boolean);
43876
43801
  for (const path of searchPaths) {
43877
- if (existsSync18(path)) {
43802
+ if (existsSync17(path)) {
43878
43803
  return path;
43879
43804
  }
43880
43805
  }
@@ -43882,12 +43807,12 @@ function findConfigFile2(startDir) {
43882
43807
  }
43883
43808
  function loadConfig2(configPath) {
43884
43809
  const filePath = configPath ?? findConfigFile2();
43885
- if (!existsSync18(filePath)) {
43810
+ if (!existsSync17(filePath)) {
43886
43811
  throw new ConfigError2(`Config file not found: ${filePath}`);
43887
43812
  }
43888
43813
  let raw;
43889
43814
  try {
43890
- raw = readFileSync14(filePath, "utf-8");
43815
+ raw = readFileSync13(filePath, "utf-8");
43891
43816
  } catch (err) {
43892
43817
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
43893
43818
  ` ${err.message}`
@@ -44327,15 +44252,15 @@ function resolveOutboundTopic(config, event) {
44327
44252
  }
44328
44253
 
44329
44254
  // ../src/agents/perf.ts
44330
- import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
44255
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
44331
44256
  function readTurnUsages(jsonlPath, lastN) {
44332
- if (!existsSync19(jsonlPath))
44257
+ if (!existsSync18(jsonlPath))
44333
44258
  return [];
44334
44259
  if (lastN <= 0)
44335
44260
  return [];
44336
44261
  let raw;
44337
44262
  try {
44338
- raw = readFileSync15(jsonlPath, "utf-8");
44263
+ raw = readFileSync14(jsonlPath, "utf-8");
44339
44264
  } catch {
44340
44265
  return [];
44341
44266
  }
@@ -44473,7 +44398,7 @@ function nextCompactNotify(state3, ev) {
44473
44398
  }
44474
44399
 
44475
44400
  // gateway/hostd-dispatch.ts
44476
- import { existsSync as existsSync20 } from "node:fs";
44401
+ import { existsSync as existsSync19 } from "node:fs";
44477
44402
  import { randomBytes as randomBytes3 } from "node:crypto";
44478
44403
 
44479
44404
  // ../src/host-control/client.ts
@@ -44764,13 +44689,13 @@ function hostdSocketPath(agentName3) {
44764
44689
  function hostdWillBeUsed(agentName3) {
44765
44690
  if (!isHostdEnabled())
44766
44691
  return false;
44767
- return existsSync20(hostdSocketPath(agentName3));
44692
+ return existsSync19(hostdSocketPath(agentName3));
44768
44693
  }
44769
44694
  async function tryHostdDispatch(agentName3, req, timeoutMs = 5000) {
44770
44695
  if (!isHostdEnabled())
44771
44696
  return "not-configured";
44772
44697
  const sockPath = hostdSocketPath(agentName3);
44773
- if (!existsSync20(sockPath))
44698
+ if (!existsSync19(sockPath))
44774
44699
  return "not-configured";
44775
44700
  try {
44776
44701
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -44794,7 +44719,7 @@ async function hostdGetStatusOnce(agentName3, targetRequestId) {
44794
44719
  if (!isHostdEnabled())
44795
44720
  return "not-configured";
44796
44721
  const sockPath = hostdSocketPath(agentName3);
44797
- if (!existsSync20(sockPath))
44722
+ if (!existsSync19(sockPath))
44798
44723
  return "not-configured";
44799
44724
  try {
44800
44725
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -44815,7 +44740,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
44815
44740
  if (!isHostdEnabled())
44816
44741
  return "not-configured";
44817
44742
  const sockPath = hostdSocketPath(agentName3);
44818
- if (!existsSync20(sockPath))
44743
+ if (!existsSync19(sockPath))
44819
44744
  return "not-configured";
44820
44745
  const now = opts.now ?? Date.now;
44821
44746
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -44894,7 +44819,7 @@ function shouldSweepChatAtBoot(chatId) {
44894
44819
 
44895
44820
  // gateway/webhook-ingest-server.ts
44896
44821
  import net4 from "node:net";
44897
- import { chmodSync as chmodSync3, existsSync as existsSync21, unlinkSync as unlinkSync5 } from "node:fs";
44822
+ import { chmodSync as chmodSync3, existsSync as existsSync20, unlinkSync as unlinkSync4 } from "node:fs";
44898
44823
  var MAX_REQUEST_BYTES = 1024 * 1024;
44899
44824
  function fdOf(conn) {
44900
44825
  const handle = conn._handle;
@@ -44906,8 +44831,8 @@ function startWebhookIngestServer(opts) {
44906
44831
  const log = opts.log ?? ((s) => process.stderr.write(s));
44907
44832
  const allowed = new Set(opts.allowedUids);
44908
44833
  try {
44909
- if (existsSync21(opts.socketPath))
44910
- unlinkSync5(opts.socketPath);
44834
+ if (existsSync20(opts.socketPath))
44835
+ unlinkSync4(opts.socketPath);
44911
44836
  } catch (err) {
44912
44837
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
44913
44838
  `);
@@ -44999,8 +44924,8 @@ function startWebhookIngestServer(opts) {
44999
44924
  server.close();
45000
44925
  } catch {}
45001
44926
  try {
45002
- if (existsSync21(opts.socketPath))
45003
- unlinkSync5(opts.socketPath);
44927
+ if (existsSync20(opts.socketPath))
44928
+ unlinkSync4(opts.socketPath);
45004
44929
  } catch {}
45005
44930
  }
45006
44931
  };
@@ -45008,19 +44933,19 @@ function startWebhookIngestServer(opts) {
45008
44933
 
45009
44934
  // ../src/web/webhook-gateway-record.ts
45010
44935
  import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync12 } from "fs";
45011
- import { join as join20 } from "path";
44936
+ import { join as join19 } from "path";
45012
44937
  import { homedir as homedir10 } from "os";
45013
44938
 
45014
44939
  // ../src/web/webhook-handler.ts
45015
- import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
45016
- import { join as join18 } from "path";
44940
+ import { appendFileSync as appendFileSync3, existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
44941
+ import { join as join17 } from "path";
45017
44942
  var DEDUP_MAX = 1000;
45018
44943
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
45019
44944
  function loadDedupFile(path) {
45020
44945
  try {
45021
- if (!existsSync22(path))
44946
+ if (!existsSync21(path))
45022
44947
  return {};
45023
- const raw = JSON.parse(readFileSync16(path, "utf-8"));
44948
+ const raw = JSON.parse(readFileSync15(path, "utf-8"));
45024
44949
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
45025
44950
  } catch {
45026
44951
  return {};
@@ -45034,7 +44959,7 @@ function saveDedupFile(path, deliveries, now) {
45034
44959
  }
45035
44960
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
45036
44961
  const final = Object.fromEntries(sorted);
45037
- writeFileSync9(path, JSON.stringify({ deliveries: final }), {
44962
+ writeFileSync8(path, JSON.stringify({ deliveries: final }), {
45038
44963
  mode: 384
45039
44964
  });
45040
44965
  }
@@ -45042,8 +44967,8 @@ var agentDedupCache = new Map;
45042
44967
  function createFileDedupStore(resolveAgentDir) {
45043
44968
  return {
45044
44969
  check(agent, deliveryId, now) {
45045
- const telegramDir = join18(resolveAgentDir(agent), "telegram");
45046
- const filePath = join18(telegramDir, "webhook-dedup.json");
44970
+ const telegramDir = join17(resolveAgentDir(agent), "telegram");
44971
+ const filePath = join17(telegramDir, "webhook-dedup.json");
45047
44972
  if (!agentDedupCache.has(agent)) {
45048
44973
  agentDedupCache.set(agent, loadDedupFile(filePath));
45049
44974
  }
@@ -45064,8 +44989,8 @@ var tokenBuckets = new Map;
45064
44989
  var throttleIssueWindow = new Map;
45065
44990
 
45066
44991
  // ../src/web/webhook-dispatch.ts
45067
- import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
45068
- import { join as join19 } from "path";
44992
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
44993
+ import { join as join18 } from "path";
45069
44994
  import { homedir as homedir9 } from "os";
45070
44995
 
45071
44996
  // ../src/agent-scheduler/ipc-client.ts
@@ -45276,9 +45201,9 @@ function cooldownKey(eventType, repo, number, ruleIndex) {
45276
45201
  }
45277
45202
  function loadCooldownFile(path) {
45278
45203
  try {
45279
- if (!existsSync23(path))
45204
+ if (!existsSync22(path))
45280
45205
  return {};
45281
- const raw = JSON.parse(readFileSync17(path, "utf-8"));
45206
+ const raw = JSON.parse(readFileSync16(path, "utf-8"));
45282
45207
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
45283
45208
  } catch {
45284
45209
  return {};
@@ -45286,7 +45211,7 @@ function loadCooldownFile(path) {
45286
45211
  }
45287
45212
  function saveCooldownFile(path, dispatches) {
45288
45213
  try {
45289
- writeFileSync10(path, JSON.stringify({ dispatches }), {
45214
+ writeFileSync9(path, JSON.stringify({ dispatches }), {
45290
45215
  mode: 384
45291
45216
  });
45292
45217
  } catch {}
@@ -45297,8 +45222,8 @@ function createFileCooldownStore(resolveAgentDir) {
45297
45222
  isCoolingDown(agent, key, cooldownMs, now) {
45298
45223
  if (cooldownMs <= 0)
45299
45224
  return false;
45300
- const telegramDir = join19(resolveAgentDir(agent), "telegram");
45301
- const filePath = join19(telegramDir, "webhook-cooldown.json");
45225
+ const telegramDir = join18(resolveAgentDir(agent), "telegram");
45226
+ const filePath = join18(telegramDir, "webhook-cooldown.json");
45302
45227
  if (!cache.has(agent)) {
45303
45228
  cache.set(agent, loadCooldownFile(filePath));
45304
45229
  }
@@ -45355,9 +45280,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
45355
45280
  }
45356
45281
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
45357
45282
  const log = deps.log ?? ((s) => process.stderr.write(s));
45358
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45283
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join18(homedir9(), ".switchroom", "agents", a));
45359
45284
  const now = (deps.now ?? Date.now)();
45360
- const socketPath = join19(resolveAgentDir(agent), "telegram", "gateway.sock");
45285
+ const socketPath = join18(resolveAgentDir(agent), "telegram", "gateway.sock");
45361
45286
  const inbound = {
45362
45287
  type: "inbound",
45363
45288
  chatId: ctx.chatId,
@@ -45382,7 +45307,7 @@ function evaluateDispatch(args, deps = {}) {
45382
45307
  const log = deps.log ?? ((s) => process.stderr.write(s));
45383
45308
  const now = (deps.now ?? Date.now)();
45384
45309
  const nowDate = deps.nowDate ?? (() => new Date(now));
45385
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45310
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join18(homedir9(), ".switchroom", "agents", a));
45386
45311
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
45387
45312
  if (args.source !== "github")
45388
45313
  return 0;
@@ -45458,10 +45383,10 @@ function resolveChannelTarget(config, agentName3) {
45458
45383
  function recordWebhookEvent(rec, deps = {}) {
45459
45384
  const log = deps.log ?? ((s) => process.stderr.write(s));
45460
45385
  const now = rec.ts || (deps.now ?? Date.now)();
45461
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir10(), ".switchroom", "agents", a));
45386
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir10(), ".switchroom", "agents", a));
45462
45387
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
45463
45388
  const agent = rec.agent;
45464
- const telegramDir = join20(resolveAgentDir(agent), "telegram");
45389
+ const telegramDir = join19(resolveAgentDir(agent), "telegram");
45465
45390
  if (rec.source === "github" && rec.delivery_id) {
45466
45391
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
45467
45392
  if (originalTs !== undefined) {
@@ -45470,7 +45395,7 @@ function recordWebhookEvent(rec, deps = {}) {
45470
45395
  return { status: "deduped", ts: originalTs };
45471
45396
  }
45472
45397
  }
45473
- const logPath = join20(telegramDir, "webhook-events.jsonl");
45398
+ const logPath = join19(telegramDir, "webhook-events.jsonl");
45474
45399
  try {
45475
45400
  mkdirSync12(telegramDir, { recursive: true });
45476
45401
  const record = {
@@ -45528,7 +45453,7 @@ function recordWebhookEvent(rec, deps = {}) {
45528
45453
  }
45529
45454
 
45530
45455
  // gateway/ipc-server.ts
45531
- import { renameSync as renameSync5, unlinkSync as unlinkSync6 } from "fs";
45456
+ import { renameSync as renameSync4, unlinkSync as unlinkSync5 } from "fs";
45532
45457
  var MAX_BUFFER_SIZE = 1024 * 1024;
45533
45458
  var VALID_OPERATOR_KINDS = new Set([
45534
45459
  "credentials-expired",
@@ -45643,10 +45568,10 @@ function createIpcServer(options) {
45643
45568
  heartbeatTimeoutMs = 30000
45644
45569
  } = options;
45645
45570
  try {
45646
- renameSync5(socketPath, socketPath + ".bak");
45571
+ renameSync4(socketPath, socketPath + ".bak");
45647
45572
  } catch {}
45648
45573
  try {
45649
- unlinkSync6(socketPath + ".bak");
45574
+ unlinkSync5(socketPath + ".bak");
45650
45575
  } catch {}
45651
45576
  const clients = new Set;
45652
45577
  const agentIndex = new Map;
@@ -45968,7 +45893,7 @@ function createIpcServer(options) {
45968
45893
  clientBySocketId.clear();
45969
45894
  server.stop(true);
45970
45895
  try {
45971
- renameSync5(socketPath, socketPath + ".bak");
45896
+ renameSync4(socketPath, socketPath + ".bak");
45972
45897
  } catch {}
45973
45898
  }
45974
45899
  };
@@ -46394,9 +46319,9 @@ function buildDiffPreviewCard(input) {
46394
46319
  }
46395
46320
  const preview = input.preview;
46396
46321
  const bodyLines = [];
46397
- bodyLines.push(`<b>${escapeHtml8(preview.title)}</b>`);
46322
+ bodyLines.push(`<b>${escapeHtml7(preview.title)}</b>`);
46398
46323
  for (const line of preview.lines) {
46399
- bodyLines.push(escapeHtml8(line.text));
46324
+ bodyLines.push(escapeHtml7(line.text));
46400
46325
  }
46401
46326
  const text = bodyLines.join(`
46402
46327
  `);
@@ -46449,7 +46374,7 @@ function buildDiffPreviewCard(input) {
46449
46374
  }
46450
46375
  return { text, reply_markup: kb };
46451
46376
  }
46452
- function escapeHtml8(s) {
46377
+ function escapeHtml7(s) {
46453
46378
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
46454
46379
  }
46455
46380
 
@@ -46595,6 +46520,9 @@ function spoolId(msg) {
46595
46520
  if (msg.meta?.source === "subagent_progress" && typeof msg.meta?.subagent_jsonl_id === "string" && msg.meta.subagent_jsonl_id.length > 0 && typeof msg.meta?.bucket_idx === "string" && msg.meta.bucket_idx.length > 0) {
46596
46521
  return `s:progress:${msg.meta.subagent_jsonl_id}:${msg.meta.bucket_idx}`;
46597
46522
  }
46523
+ if ((msg.meta?.source === "resume_interrupted" || msg.meta?.source === "resume_watchdog_timeout") && typeof msg.meta?.resume_turn_key === "string" && msg.meta.resume_turn_key.length > 0) {
46524
+ return `s:resume:${msg.meta.resume_turn_key}`;
46525
+ }
46598
46526
  if (typeof msg.messageId === "number" && msg.messageId > 0) {
46599
46527
  return `m:${msg.chatId}:${msg.messageId}`;
46600
46528
  }
@@ -47828,15 +47756,15 @@ function escapeBody(s) {
47828
47756
  }
47829
47757
 
47830
47758
  // gateway/pid-file.ts
47831
- import { writeFileSync as writeFileSync11, readFileSync as readFileSync18, unlinkSync as unlinkSync7, renameSync as renameSync6 } from "node:fs";
47759
+ import { writeFileSync as writeFileSync10, readFileSync as readFileSync17, unlinkSync as unlinkSync6, renameSync as renameSync5 } from "node:fs";
47832
47760
  function writePidFile(path, record) {
47833
47761
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
47834
- writeFileSync11(tmp, JSON.stringify(record), "utf-8");
47835
- renameSync6(tmp, path);
47762
+ writeFileSync10(tmp, JSON.stringify(record), "utf-8");
47763
+ renameSync5(tmp, path);
47836
47764
  }
47837
47765
  function clearPidFile(path) {
47838
47766
  try {
47839
- unlinkSync7(path);
47767
+ unlinkSync6(path);
47840
47768
  } catch {}
47841
47769
  }
47842
47770
 
@@ -47847,10 +47775,10 @@ import {
47847
47775
  writeFile as writeFileAsync,
47848
47776
  readFile as readFileAsync
47849
47777
  } from "node:fs/promises";
47850
- import { readFileSync as readFileSync19 } from "node:fs";
47778
+ import { readFileSync as readFileSync18 } from "node:fs";
47851
47779
  function readCurrentBootId() {
47852
47780
  try {
47853
- const stat = readFileSync19("/proc/1/stat", "utf-8");
47781
+ const stat = readFileSync18("/proc/1/stat", "utf-8");
47854
47782
  const lastParen = stat.lastIndexOf(")");
47855
47783
  if (lastParen < 0)
47856
47784
  return null;
@@ -48053,15 +47981,15 @@ function safeCount(fn) {
48053
47981
  }
48054
47982
 
48055
47983
  // gateway/session-marker.ts
48056
- import { writeFileSync as writeFileSync12, readFileSync as readFileSync20, renameSync as renameSync7, unlinkSync as unlinkSync8 } from "node:fs";
47984
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync19, renameSync as renameSync6, unlinkSync as unlinkSync7 } from "node:fs";
48057
47985
  function writeSessionMarker(path, marker) {
48058
47986
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
48059
- writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
48060
- renameSync7(tmp, path);
47987
+ writeFileSync11(tmp, JSON.stringify(marker), "utf-8");
47988
+ renameSync6(tmp, path);
48061
47989
  }
48062
47990
  function readSessionMarker(path) {
48063
47991
  try {
48064
- const raw = readFileSync20(path, "utf-8");
47992
+ const raw = readFileSync19(path, "utf-8");
48065
47993
  const parsed = JSON.parse(raw);
48066
47994
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
48067
47995
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -48083,16 +48011,16 @@ function shouldFireRestartBanner(input) {
48083
48011
  }
48084
48012
 
48085
48013
  // gateway/clean-shutdown-marker.ts
48086
- import { writeFileSync as writeFileSync13, readFileSync as readFileSync21, renameSync as renameSync8, unlinkSync as unlinkSync9 } from "node:fs";
48014
+ import { writeFileSync as writeFileSync12, readFileSync as readFileSync20, renameSync as renameSync7, unlinkSync as unlinkSync8 } from "node:fs";
48087
48015
  var DEFAULT_MAX_AGE_MS = 60000;
48088
48016
  function writeCleanShutdownMarker(path, marker) {
48089
48017
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
48090
- writeFileSync13(tmp, JSON.stringify(marker), "utf-8");
48091
- renameSync8(tmp, path);
48018
+ writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
48019
+ renameSync7(tmp, path);
48092
48020
  }
48093
48021
  function readCleanShutdownMarker(path) {
48094
48022
  try {
48095
- const raw = readFileSync21(path, "utf-8");
48023
+ const raw = readFileSync20(path, "utf-8");
48096
48024
  const parsed = JSON.parse(raw);
48097
48025
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
48098
48026
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -48107,7 +48035,7 @@ function readCleanShutdownMarker(path) {
48107
48035
  }
48108
48036
  function clearCleanShutdownMarker(path) {
48109
48037
  try {
48110
- unlinkSync9(path);
48038
+ unlinkSync8(path);
48111
48039
  } catch {}
48112
48040
  }
48113
48041
  function shouldSuppressRecoveryBanner(marker, now, maxAgeMs = DEFAULT_MAX_AGE_MS) {
@@ -48887,16 +48815,16 @@ function classifyAdminGate(text, myAgentName) {
48887
48815
 
48888
48816
  // subagent-watcher.ts
48889
48817
  import {
48890
- existsSync as existsSync25,
48818
+ existsSync as existsSync24,
48891
48819
  openSync as openSync2,
48892
48820
  readSync,
48893
48821
  statSync as statSync6,
48894
48822
  closeSync as closeSync2,
48895
48823
  watch,
48896
48824
  readdirSync as readdirSync3,
48897
- readFileSync as readFileSync23
48825
+ readFileSync as readFileSync22
48898
48826
  } from "fs";
48899
- import { join as join22 } from "path";
48827
+ import { join as join21 } from "path";
48900
48828
 
48901
48829
  // operator-events.ts
48902
48830
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
@@ -49139,20 +49067,20 @@ function bumpSubagentActivity(db2, args) {
49139
49067
  // gateway/turn-active-marker.ts
49140
49068
  import {
49141
49069
  closeSync,
49142
- existsSync as existsSync24,
49070
+ existsSync as existsSync23,
49143
49071
  mkdirSync as mkdirSync13,
49144
49072
  openSync,
49145
- readFileSync as readFileSync22,
49073
+ readFileSync as readFileSync21,
49146
49074
  statSync as statSync5,
49147
- unlinkSync as unlinkSync10,
49075
+ unlinkSync as unlinkSync9,
49148
49076
  utimesSync,
49149
- writeFileSync as writeFileSync14
49077
+ writeFileSync as writeFileSync13
49150
49078
  } from "node:fs";
49151
- import { join as join21 } from "node:path";
49079
+ import { join as join20 } from "node:path";
49152
49080
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
49153
49081
  function touchTurnActiveMarker(stateDir) {
49154
- const path = join21(stateDir, TURN_ACTIVE_MARKER_FILE);
49155
- if (!existsSync24(path))
49082
+ const path = join20(stateDir, TURN_ACTIVE_MARKER_FILE);
49083
+ if (!existsSync23(path))
49156
49084
  return;
49157
49085
  const now = new Date;
49158
49086
  try {
@@ -49187,7 +49115,7 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
49187
49115
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
49188
49116
  let meta;
49189
49117
  try {
49190
- const raw = readFileSync23(metaPath, "utf8");
49118
+ const raw = readFileSync22(metaPath, "utf8");
49191
49119
  meta = JSON.parse(raw);
49192
49120
  } catch {
49193
49121
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -49376,7 +49304,7 @@ function startSubagentWatcher(config) {
49376
49304
  clearTimeout(ref.ref);
49377
49305
  });
49378
49306
  const fs2 = config.fs ?? {
49379
- existsSync: existsSync25,
49307
+ existsSync: existsSync24,
49380
49308
  readdirSync: readdirSync3,
49381
49309
  statSync: statSync6,
49382
49310
  openSync: openSync2,
@@ -49610,8 +49538,8 @@ function startSubagentWatcher(config) {
49610
49538
  function rescanSubagentDirs() {
49611
49539
  if (stopped)
49612
49540
  return;
49613
- const claudeHome = join22(agentDir, ".claude");
49614
- const projectsRoot = join22(claudeHome, "projects");
49541
+ const claudeHome = join21(agentDir, ".claude");
49542
+ const projectsRoot = join21(claudeHome, "projects");
49615
49543
  if (!fs2.existsSync(projectsRoot))
49616
49544
  return;
49617
49545
  let projectDirs;
@@ -49628,7 +49556,7 @@ function startSubagentWatcher(config) {
49628
49556
  }
49629
49557
  continue;
49630
49558
  }
49631
- const projectPath = join22(projectsRoot, pDir);
49559
+ const projectPath = join21(projectsRoot, pDir);
49632
49560
  let sessionDirs;
49633
49561
  try {
49634
49562
  sessionDirs = fs2.readdirSync(projectPath);
@@ -49638,7 +49566,7 @@ function startSubagentWatcher(config) {
49638
49566
  for (const sDir of sessionDirs) {
49639
49567
  if (sDir.endsWith(".jsonl"))
49640
49568
  continue;
49641
- const subagentsPath = join22(projectPath, sDir, "subagents");
49569
+ const subagentsPath = join21(projectPath, sDir, "subagents");
49642
49570
  if (!fs2.existsSync(subagentsPath))
49643
49571
  continue;
49644
49572
  if (!dirWatchers.has(subagentsPath)) {
@@ -49646,7 +49574,7 @@ function startSubagentWatcher(config) {
49646
49574
  const w = fs2.watch(subagentsPath, (_event, filename) => {
49647
49575
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
49648
49576
  return;
49649
- const filePath = join22(subagentsPath, filename.toString());
49577
+ const filePath = join21(subagentsPath, filename.toString());
49650
49578
  if (!knownFiles.has(filePath)) {
49651
49579
  scanSubagentsDir(subagentsPath);
49652
49580
  }
@@ -49671,7 +49599,7 @@ function startSubagentWatcher(config) {
49671
49599
  for (const e of entries) {
49672
49600
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
49673
49601
  continue;
49674
- const filePath = join22(subagentsPath, e);
49602
+ const filePath = join21(subagentsPath, e);
49675
49603
  if (knownFiles.has(filePath))
49676
49604
  continue;
49677
49605
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -49788,15 +49716,15 @@ function determineRestartReason(opts) {
49788
49716
  init_boot_card();
49789
49717
 
49790
49718
  // gateway/update-announce.ts
49791
- import { existsSync as existsSync30, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync28 } from "node:fs";
49792
- import { join as join27 } from "node:path";
49719
+ import { existsSync as existsSync29, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync27 } from "node:fs";
49720
+ import { join as join26 } from "node:path";
49793
49721
  import { homedir as homedir12 } from "node:os";
49794
49722
 
49795
49723
  // ../src/host-control/audit-reader.ts
49796
49724
  import { homedir as homedir11 } from "node:os";
49797
- import { join as join26 } from "node:path";
49725
+ import { join as join25 } from "node:path";
49798
49726
  function defaultAuditLogPath(home2 = homedir11()) {
49799
- return join26(home2, ".switchroom", "host-control-audit.log");
49727
+ return join25(home2, ".switchroom", "host-control-audit.log");
49800
49728
  }
49801
49729
  function parseAuditLine(line) {
49802
49730
  const trimmed = line.trim();
@@ -49902,8 +49830,8 @@ function readAndFilter(raw, filters, limit) {
49902
49830
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
49903
49831
  function readLastTerminalUpdateAudit(opts = {}) {
49904
49832
  const path = opts.auditLogPath ?? defaultAuditLogPath();
49905
- const exists = opts.exists ?? existsSync30;
49906
- const readFile = opts.readFile ?? ((p) => readFileSync28(p, "utf-8"));
49833
+ const exists = opts.exists ?? existsSync29;
49834
+ const readFile = opts.readFile ?? ((p) => readFileSync27(p, "utf-8"));
49907
49835
  if (!exists(path))
49908
49836
  return null;
49909
49837
  let raw;
@@ -49964,15 +49892,15 @@ function renderUpdateOutcomeLine(entry) {
49964
49892
  `);
49965
49893
  }
49966
49894
  function claimUpdateAnnouncement(requestId, opts = {}) {
49967
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join27(homedir12(), ".switchroom");
49968
- const dir = join27(stateDir, "update-announced");
49895
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join26(homedir12(), ".switchroom");
49896
+ const dir = join26(stateDir, "update-announced");
49969
49897
  try {
49970
49898
  mkdirSync17(dir, { recursive: true });
49971
49899
  } catch {
49972
49900
  return false;
49973
49901
  }
49974
49902
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
49975
- const path = join27(dir, safeId);
49903
+ const path = join26(dir, safeId);
49976
49904
  try {
49977
49905
  const fd = openSync3(path, "wx");
49978
49906
  closeSync3(fd);
@@ -49991,7 +49919,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
49991
49919
  }
49992
49920
 
49993
49921
  // issues-card.ts
49994
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
49922
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "node:fs";
49995
49923
  var SEVERITY_EMOJI = {
49996
49924
  info: "\u2139\ufe0f",
49997
49925
  warn: "\u26a0\ufe0f",
@@ -50083,7 +50011,7 @@ function extractRetryAfterSecs2(err) {
50083
50011
  var COOLDOWN_JITTER_MS2 = 500;
50084
50012
  function readPersistedMessageId(path, log) {
50085
50013
  try {
50086
- const raw = readFileSync29(path, "utf8");
50014
+ const raw = readFileSync28(path, "utf8");
50087
50015
  const parsed = JSON.parse(raw);
50088
50016
  const v = parsed.messageId;
50089
50017
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -50099,7 +50027,7 @@ function readPersistedMessageId(path, log) {
50099
50027
  }
50100
50028
  function writePersistedMessageId(path, messageId, log) {
50101
50029
  try {
50102
- writeFileSync18(path, JSON.stringify({ messageId }) + `
50030
+ writeFileSync17(path, JSON.stringify({ messageId }) + `
50103
50031
  `, { mode: 384 });
50104
50032
  } catch (err) {
50105
50033
  log(`issues-card: persist write failed (${err.message})`);
@@ -50192,24 +50120,24 @@ function createIssuesCardHandle(opts) {
50192
50120
  }
50193
50121
 
50194
50122
  // issues-watcher.ts
50195
- import { existsSync as existsSync32, statSync as statSync8 } from "node:fs";
50196
- import { join as join29 } from "node:path";
50123
+ import { existsSync as existsSync31, statSync as statSync8 } from "node:fs";
50124
+ import { join as join28 } from "node:path";
50197
50125
 
50198
50126
  // ../src/issues/store.ts
50199
50127
  import {
50200
50128
  closeSync as closeSync4,
50201
- existsSync as existsSync31,
50129
+ existsSync as existsSync30,
50202
50130
  mkdirSync as mkdirSync18,
50203
50131
  openSync as openSync4,
50204
50132
  readdirSync as readdirSync5,
50205
- readFileSync as readFileSync30,
50206
- renameSync as renameSync11,
50133
+ readFileSync as readFileSync29,
50134
+ renameSync as renameSync10,
50207
50135
  statSync as statSync7,
50208
- unlinkSync as unlinkSync11,
50209
- writeFileSync as writeFileSync19,
50136
+ unlinkSync as unlinkSync10,
50137
+ writeFileSync as writeFileSync18,
50210
50138
  writeSync
50211
50139
  } from "node:fs";
50212
- import { join as join28 } from "node:path";
50140
+ import { join as join27 } from "node:path";
50213
50141
  import { randomBytes as randomBytes4 } from "node:crypto";
50214
50142
  import { execSync } from "node:child_process";
50215
50143
 
@@ -50225,12 +50153,12 @@ var SEVERITY_RANK2 = {
50225
50153
  var ISSUES_FILE = "issues.jsonl";
50226
50154
  var ISSUES_LOCK = "issues.lock";
50227
50155
  function readAll(stateDir) {
50228
- const path = join28(stateDir, ISSUES_FILE);
50229
- if (!existsSync31(path))
50156
+ const path = join27(stateDir, ISSUES_FILE);
50157
+ if (!existsSync30(path))
50230
50158
  return [];
50231
50159
  let raw;
50232
50160
  try {
50233
- raw = readFileSync30(path, "utf-8");
50161
+ raw = readFileSync29(path, "utf-8");
50234
50162
  } catch {
50235
50163
  return [];
50236
50164
  }
@@ -50262,7 +50190,7 @@ function list(stateDir, opts = {}) {
50262
50190
  });
50263
50191
  }
50264
50192
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50265
- if (!existsSync31(join28(stateDir, ISSUES_FILE)))
50193
+ if (!existsSync30(join27(stateDir, ISSUES_FILE)))
50266
50194
  return 0;
50267
50195
  return withLock(stateDir, () => {
50268
50196
  const all = readAll(stateDir);
@@ -50280,14 +50208,14 @@ function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50280
50208
  });
50281
50209
  }
50282
50210
  function writeAll(stateDir, events) {
50283
- const path = join28(stateDir, ISSUES_FILE);
50211
+ const path = join27(stateDir, ISSUES_FILE);
50284
50212
  sweepOrphanTmpFiles(stateDir);
50285
50213
  const tmp = `${path}.tmp-${process.pid}-${randomBytes4(4).toString("hex")}`;
50286
50214
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
50287
50215
  `) + `
50288
50216
  `;
50289
- writeFileSync19(tmp, body, "utf-8");
50290
- renameSync11(tmp, path);
50217
+ writeFileSync18(tmp, body, "utf-8");
50218
+ renameSync10(tmp, path);
50291
50219
  }
50292
50220
  var ORPHAN_TMP_TTL_MS = 60000;
50293
50221
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -50302,11 +50230,11 @@ function sweepOrphanTmpFiles(stateDir) {
50302
50230
  for (const entry of entries) {
50303
50231
  if (!entry.startsWith(TMP_PREFIX))
50304
50232
  continue;
50305
- const tmpPath2 = join28(stateDir, entry);
50233
+ const tmpPath2 = join27(stateDir, entry);
50306
50234
  try {
50307
50235
  const stat = statSync7(tmpPath2);
50308
50236
  if (stat.mtimeMs < cutoff) {
50309
- unlinkSync11(tmpPath2);
50237
+ unlinkSync10(tmpPath2);
50310
50238
  }
50311
50239
  } catch {}
50312
50240
  }
@@ -50314,7 +50242,7 @@ function sweepOrphanTmpFiles(stateDir) {
50314
50242
  var LOCK_RETRY_MS = 25;
50315
50243
  var LOCK_TIMEOUT_MS = 1e4;
50316
50244
  function withLock(stateDir, fn) {
50317
- const lockPath = join28(stateDir, ISSUES_LOCK);
50245
+ const lockPath = join27(stateDir, ISSUES_LOCK);
50318
50246
  const startedAt = Date.now();
50319
50247
  let fd = null;
50320
50248
  while (fd === null) {
@@ -50342,27 +50270,27 @@ function withLock(stateDir, fn) {
50342
50270
  closeSync4(fd);
50343
50271
  } catch {}
50344
50272
  try {
50345
- unlinkSync11(lockPath);
50273
+ unlinkSync10(lockPath);
50346
50274
  } catch {}
50347
50275
  }
50348
50276
  }
50349
50277
  function tryStealStaleLock(lockPath) {
50350
50278
  let pidStr;
50351
50279
  try {
50352
- pidStr = readFileSync30(lockPath, "utf-8").trim();
50280
+ pidStr = readFileSync29(lockPath, "utf-8").trim();
50353
50281
  } catch {
50354
50282
  return true;
50355
50283
  }
50356
50284
  const pid = Number(pidStr);
50357
50285
  if (!Number.isFinite(pid) || pid <= 0) {
50358
50286
  try {
50359
- unlinkSync11(lockPath);
50287
+ unlinkSync10(lockPath);
50360
50288
  } catch {}
50361
50289
  return true;
50362
50290
  }
50363
50291
  if (pid === process.pid) {
50364
50292
  try {
50365
- unlinkSync11(lockPath);
50293
+ unlinkSync10(lockPath);
50366
50294
  } catch {}
50367
50295
  return true;
50368
50296
  }
@@ -50377,7 +50305,7 @@ function tryStealStaleLock(lockPath) {
50377
50305
  return false;
50378
50306
  }
50379
50307
  try {
50380
- unlinkSync11(lockPath);
50308
+ unlinkSync10(lockPath);
50381
50309
  } catch {}
50382
50310
  return true;
50383
50311
  }
@@ -50399,7 +50327,7 @@ function isIssueEvent(v) {
50399
50327
  // issues-watcher.ts
50400
50328
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
50401
50329
  function startIssuesWatcher(opts) {
50402
- const path = join29(opts.stateDir, ISSUES_FILE);
50330
+ const path = join28(opts.stateDir, ISSUES_FILE);
50403
50331
  const log = opts.log ?? (() => {});
50404
50332
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
50405
50333
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -50447,7 +50375,7 @@ function startIssuesWatcher(opts) {
50447
50375
  };
50448
50376
  }
50449
50377
  function defaultSignatureProvider(path) {
50450
- if (!existsSync32(path))
50378
+ if (!existsSync31(path))
50451
50379
  return null;
50452
50380
  try {
50453
50381
  const stat = statSync8(path);
@@ -51052,8 +50980,8 @@ function extractFlowItems(line) {
51052
50980
  }
51053
50981
 
51054
50982
  // credits-watch.ts
51055
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync19 } from "fs";
51056
- import { join as join30 } from "path";
50983
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync32, mkdirSync as mkdirSync19 } from "fs";
50984
+ import { join as join29 } from "path";
51057
50985
  var STATE_FILE = "credits-watch.json";
51058
50986
  var FATAL_REASONS = new Set([
51059
50987
  "out_of_credits",
@@ -51065,12 +50993,12 @@ function emptyCreditState() {
51065
50993
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
51066
50994
  }
51067
50995
  function readClaudeJsonOverage(claudeConfigDir) {
51068
- const path = join30(claudeConfigDir, ".claude.json");
51069
- if (!existsSync33(path))
50996
+ const path = join29(claudeConfigDir, ".claude.json");
50997
+ if (!existsSync32(path))
51070
50998
  return null;
51071
50999
  let raw;
51072
51000
  try {
51073
- raw = readFileSync31(path, "utf-8");
51001
+ raw = readFileSync30(path, "utf-8");
51074
51002
  } catch {
51075
51003
  return null;
51076
51004
  }
@@ -51094,7 +51022,7 @@ function evaluateCreditState(args) {
51094
51022
  if (!currentIsFatal && prevIsFatal) {
51095
51023
  return {
51096
51024
  kind: "notify",
51097
- message: `\u2705 <b>${escapeHtml10(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51025
+ message: `\u2705 <b>${escapeHtml9(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51098
51026
  newState: { lastNotifiedReason: null, lastNotifiedAt: now },
51099
51027
  transition: "exited"
51100
51028
  };
@@ -51123,12 +51051,12 @@ function evaluateCreditState(args) {
51123
51051
  function buildEntryMessage(agentName3, reason) {
51124
51052
  const desc = humanizeReason(reason);
51125
51053
  return [
51126
- `\u26a0\ufe0f <b>${escapeHtml10(agentName3)}</b>: ${desc}`,
51054
+ `\u26a0\ufe0f <b>${escapeHtml9(agentName3)}</b>: ${desc}`,
51127
51055
  ``,
51128
51056
  `Cron tasks and inbound replies will fail until this is resolved. Check`,
51129
51057
  `your subscription or pre-paid usage at <a href="https://console.anthropic.com">console.anthropic.com</a>.`,
51130
51058
  ``,
51131
- `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml10(reason)})</i>`
51059
+ `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml9(reason)})</i>`
51132
51060
  ].join(`
51133
51061
  `);
51134
51062
  }
@@ -51146,15 +51074,15 @@ function humanizeReason(reason) {
51146
51074
  return `usage disabled (${reason})`;
51147
51075
  }
51148
51076
  }
51149
- function escapeHtml10(s) {
51077
+ function escapeHtml9(s) {
51150
51078
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51151
51079
  }
51152
51080
  function loadCreditState(stateDir) {
51153
- const path = join30(stateDir, STATE_FILE);
51154
- if (!existsSync33(path))
51081
+ const path = join29(stateDir, STATE_FILE);
51082
+ if (!existsSync32(path))
51155
51083
  return emptyCreditState();
51156
51084
  try {
51157
- const raw = readFileSync31(path, "utf-8");
51085
+ const raw = readFileSync30(path, "utf-8");
51158
51086
  const parsed = JSON.parse(raw);
51159
51087
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
51160
51088
  return {
@@ -51167,14 +51095,14 @@ function loadCreditState(stateDir) {
51167
51095
  }
51168
51096
  function saveCreditState(stateDir, state4) {
51169
51097
  mkdirSync19(stateDir, { recursive: true });
51170
- const path = join30(stateDir, STATE_FILE);
51171
- writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51098
+ const path = join29(stateDir, STATE_FILE);
51099
+ writeFileSync19(path, JSON.stringify(state4, null, 2) + `
51172
51100
  `, { mode: 384 });
51173
51101
  }
51174
51102
 
51175
51103
  // quota-watch.ts
51176
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync21, existsSync as existsSync34, mkdirSync as mkdirSync20 } from "fs";
51177
- import { join as join31 } from "path";
51104
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync20 } from "fs";
51105
+ import { join as join30 } from "path";
51178
51106
  var STATE_FILE2 = "quota-watch.json";
51179
51107
  function emptyQuotaWatchState() {
51180
51108
  return {};
@@ -51231,11 +51159,11 @@ function buildThrottlingMessage(agentName3, snap) {
51231
51159
  const resetAt = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
51232
51160
  const resetStr = resetAt ? ` \u00b7 refills in ${formatRelative(resetAt, new Date)}` : "";
51233
51161
  const activeNote = snap.isActive ? "" : `
51234
- This is a non-active account. Consider <code>/auth use ${escapeHtml11(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
51162
+ This is a non-active account. Consider <code>/auth use ${escapeHtml10(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
51235
51163
  const altNote = snap.isActive ? `
51236
51164
  Consider <code>/auth use &lt;other-account&gt;</code> if you have a healthier account, or wait for the ${winLabel} window to refill${resetStr}.` : "";
51237
51165
  return [
51238
- `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51166
+ `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51239
51167
  ``,
51240
51168
  `${fiveStr} of 5h \u00b7 ${sevenStr} of 7d`,
51241
51169
  `Binding window: ${winLabel}${resetStr}`,
@@ -51252,7 +51180,7 @@ function buildRecoveryMessage(agentName3, snap) {
51252
51180
  const q = snap.quota;
51253
51181
  const utilLine = q ? `Current: ${fmtPct(q.fiveHourUtilizationPct)} of 5h \u00b7 ${fmtPct(q.sevenDayUtilizationPct)} of 7d` : "Current quota data unavailable.";
51254
51182
  return [
51255
- `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51183
+ `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51256
51184
  ``,
51257
51185
  utilLine,
51258
51186
  ``,
@@ -51260,15 +51188,15 @@ function buildRecoveryMessage(agentName3, snap) {
51260
51188
  ].join(`
51261
51189
  `);
51262
51190
  }
51263
- function escapeHtml11(s) {
51191
+ function escapeHtml10(s) {
51264
51192
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51265
51193
  }
51266
51194
  function loadQuotaWatchState(stateDir) {
51267
- const path = join31(stateDir, STATE_FILE2);
51268
- if (!existsSync34(path))
51195
+ const path = join30(stateDir, STATE_FILE2);
51196
+ if (!existsSync33(path))
51269
51197
  return emptyQuotaWatchState();
51270
51198
  try {
51271
- const raw = readFileSync32(path, "utf-8");
51199
+ const raw = readFileSync31(path, "utf-8");
51272
51200
  const parsed = JSON.parse(raw);
51273
51201
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
51274
51202
  return emptyQuotaWatchState();
@@ -51286,8 +51214,8 @@ function loadQuotaWatchState(stateDir) {
51286
51214
  }
51287
51215
  function saveQuotaWatchState(stateDir, state4) {
51288
51216
  mkdirSync20(stateDir, { recursive: true });
51289
- const path = join31(stateDir, STATE_FILE2);
51290
- writeFileSync21(path, JSON.stringify(state4, null, 2) + `
51217
+ const path = join30(stateDir, STATE_FILE2);
51218
+ writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51291
51219
  `, { mode: 384 });
51292
51220
  }
51293
51221
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -51300,27 +51228,27 @@ init_auth_snapshot_format();
51300
51228
  // gateway/turn-active-marker.ts
51301
51229
  import {
51302
51230
  closeSync as closeSync5,
51303
- existsSync as existsSync35,
51231
+ existsSync as existsSync34,
51304
51232
  mkdirSync as mkdirSync21,
51305
51233
  openSync as openSync5,
51306
- readFileSync as readFileSync33,
51234
+ readFileSync as readFileSync32,
51307
51235
  statSync as statSync9,
51308
- unlinkSync as unlinkSync12,
51236
+ unlinkSync as unlinkSync11,
51309
51237
  utimesSync as utimesSync2,
51310
- writeFileSync as writeFileSync22
51238
+ writeFileSync as writeFileSync21
51311
51239
  } from "node:fs";
51312
- import { join as join32 } from "node:path";
51240
+ import { join as join31 } from "node:path";
51313
51241
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
51314
51242
  function writeTurnActiveMarker(stateDir, marker) {
51315
51243
  try {
51316
51244
  mkdirSync21(stateDir, { recursive: true });
51317
- writeFileSync22(join32(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51245
+ writeFileSync21(join31(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51318
51246
  `, { mode: 384 });
51319
51247
  } catch {}
51320
51248
  }
51321
51249
  function touchTurnActiveMarker2(stateDir) {
51322
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51323
- if (!existsSync35(path))
51250
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51251
+ if (!existsSync34(path))
51324
51252
  return;
51325
51253
  const now = new Date;
51326
51254
  try {
@@ -51334,12 +51262,12 @@ function touchTurnActiveMarker2(stateDir) {
51334
51262
  }
51335
51263
  function removeTurnActiveMarker(stateDir) {
51336
51264
  try {
51337
- unlinkSync12(join32(stateDir, TURN_ACTIVE_MARKER_FILE2));
51265
+ unlinkSync11(join31(stateDir, TURN_ACTIVE_MARKER_FILE2));
51338
51266
  } catch {}
51339
51267
  }
51340
51268
  function sweepStaleTurnActiveMarker(stateDir, opts) {
51341
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51342
- if (!existsSync35(path))
51269
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51270
+ if (!existsSync34(path))
51343
51271
  return false;
51344
51272
  const now = opts.now ?? Date.now();
51345
51273
  try {
@@ -51351,9 +51279,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51351
51279
  return false;
51352
51280
  let payload = null;
51353
51281
  try {
51354
- payload = readFileSync33(path, "utf8");
51282
+ payload = readFileSync32(path, "utf8");
51355
51283
  } catch {}
51356
- unlinkSync12(path);
51284
+ unlinkSync11(path);
51357
51285
  if (opts.onRemove) {
51358
51286
  try {
51359
51287
  opts.onRemove({
@@ -51370,10 +51298,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51370
51298
  }
51371
51299
 
51372
51300
  // ../src/build-info.ts
51373
- var VERSION = "0.14.21";
51374
- var COMMIT_SHA = "62ddded0";
51375
- var COMMIT_DATE = "2026-05-31T02:54:29Z";
51376
- var LATEST_PR = 2024;
51301
+ var VERSION = "0.14.22";
51302
+ var COMMIT_SHA = "ab2692b9";
51303
+ var COMMIT_DATE = "2026-05-31T06:26:06Z";
51304
+ var LATEST_PR = 2028;
51377
51305
  var COMMITS_AHEAD_OF_TAG = 0;
51378
51306
 
51379
51307
  // gateway/boot-version.ts
@@ -51447,11 +51375,11 @@ init_peercred();
51447
51375
  import * as net5 from "node:net";
51448
51376
  import * as fs2 from "node:fs";
51449
51377
  import { homedir as homedir13 } from "node:os";
51450
- import { join as join33 } from "node:path";
51378
+ import { join as join32 } from "node:path";
51451
51379
  var DEFAULT_TIMEOUT_MS4 = 2000;
51452
51380
  var UNLOCK_TIMEOUT_MS = 30000;
51453
- var LEGACY_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "vault-broker.sock");
51454
- var OPERATOR_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "broker-operator", "sock");
51381
+ var LEGACY_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "vault-broker.sock");
51382
+ var OPERATOR_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "broker-operator", "sock");
51455
51383
  function defaultBrokerSocketPath2() {
51456
51384
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
51457
51385
  return OPERATOR_SOCKET_PATH2;
@@ -51674,7 +51602,7 @@ function resolveVaultApprovalPosture(broker) {
51674
51602
 
51675
51603
  // registry/turns-schema.ts
51676
51604
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync22 } from "fs";
51677
- import { join as join34 } from "path";
51605
+ import { join as join33 } from "path";
51678
51606
  var DatabaseClass2 = null;
51679
51607
  function loadDatabaseClass2() {
51680
51608
  if (DatabaseClass2 != null)
@@ -51707,6 +51635,7 @@ var SCHEMA_SQL = `
51707
51635
  user_prompt_preview TEXT,
51708
51636
  assistant_reply_preview TEXT,
51709
51637
  tool_call_count INTEGER,
51638
+ interrupt_reason TEXT,
51710
51639
  created_at INTEGER NOT NULL,
51711
51640
  updated_at INTEGER NOT NULL
51712
51641
  );
@@ -51717,11 +51646,14 @@ var PHASE1_MIGRATIONS = [
51717
51646
  `ALTER TABLE turns ADD COLUMN assistant_reply_preview TEXT`,
51718
51647
  `ALTER TABLE turns ADD COLUMN tool_call_count INTEGER`
51719
51648
  ];
51649
+ var PHASE2_MIGRATIONS = [
51650
+ `ALTER TABLE turns ADD COLUMN interrupt_reason TEXT`
51651
+ ];
51720
51652
  function applySchema(db2) {
51721
51653
  db2.exec("PRAGMA journal_mode = WAL");
51722
51654
  db2.exec("PRAGMA synchronous = NORMAL");
51723
51655
  db2.exec(SCHEMA_SQL);
51724
- for (const sql of PHASE1_MIGRATIONS) {
51656
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS]) {
51725
51657
  try {
51726
51658
  db2.exec(sql);
51727
51659
  } catch (err) {
@@ -51733,9 +51665,9 @@ function applySchema(db2) {
51733
51665
  }
51734
51666
  function openTurnsDb(agentDir) {
51735
51667
  const Database = loadDatabaseClass2();
51736
- const dir = join34(agentDir, "telegram");
51668
+ const dir = join33(agentDir, "telegram");
51737
51669
  mkdirSync22(dir, { recursive: true, mode: 448 });
51738
- const path = join34(dir, "registry.db");
51670
+ const path = join33(dir, "registry.db");
51739
51671
  const db2 = new Database(path, { create: true });
51740
51672
  applySchema(db2);
51741
51673
  try {
@@ -51757,6 +51689,7 @@ function mapRow(row) {
51757
51689
  user_prompt_preview: row.user_prompt_preview,
51758
51690
  assistant_reply_preview: row.assistant_reply_preview,
51759
51691
  tool_call_count: row.tool_call_count,
51692
+ interrupt_reason: row.interrupt_reason,
51760
51693
  created_at: row.created_at,
51761
51694
  updated_at: row.updated_at
51762
51695
  };
@@ -51785,26 +51718,143 @@ function recordTurnEnd(db2, args) {
51785
51718
  WHERE turn_key = ?
51786
51719
  `).run(now, args.endedVia, args.lastAssistantMsgId ?? null, args.lastAssistantDone !== undefined ? args.lastAssistantDone ? 1 : 0 : null, args.assistantReplyPreview ?? null, args.toolCallCount !== undefined ? args.toolCallCount : null, now, args.turnKey);
51787
51720
  }
51788
- function markOrphanedAsRestarted(db2) {
51789
- const now = Date.now();
51790
- const result = db2.prepare(`
51721
+ function markOrphanedWithTimeoutClassification(db2, opts) {
51722
+ const now = opts.now ?? Date.now();
51723
+ const isHang = opts.markerAgeMs != null && opts.markerAgeMs >= opts.hangThresholdMs && opts.markerTurnKey != null && opts.markerTurnKey.length > 0;
51724
+ let timeoutTurnKey = null;
51725
+ if (isHang) {
51726
+ const r = db2.prepare(`
51727
+ UPDATE turns
51728
+ SET ended_at = ?,
51729
+ ended_via = 'timeout',
51730
+ interrupt_reason = ?,
51731
+ updated_at = ?
51732
+ WHERE turn_key = ? AND ended_at IS NULL
51733
+ `).run(now, opts.reasonSnapshot ?? null, now, opts.markerTurnKey);
51734
+ if (r.changes > 0)
51735
+ timeoutTurnKey = opts.markerTurnKey ?? null;
51736
+ }
51737
+ const rest = db2.prepare(`
51791
51738
  UPDATE turns
51792
51739
  SET ended_at = ?,
51793
51740
  ended_via = 'restart',
51794
51741
  updated_at = ?
51795
51742
  WHERE ended_at IS NULL
51796
51743
  `).run(now, now);
51797
- return result.changes;
51744
+ return { reaped: (timeoutTurnKey ? 1 : 0) + rest.changes, timeoutTurnKey };
51798
51745
  }
51799
- function findMostRecentInterruptedTurn(db2) {
51746
+ var INTERRUPTED_VIA = new Set([
51747
+ "restart",
51748
+ "sigterm",
51749
+ "timeout",
51750
+ "unknown"
51751
+ ]);
51752
+ function findLatestTurnIfInterrupted(db2) {
51800
51753
  const row = db2.prepare(`
51801
51754
  SELECT * FROM turns
51802
- WHERE ended_at IS NULL
51803
- OR ended_via IN ('restart', 'sigterm', 'timeout')
51804
51755
  ORDER BY started_at DESC
51805
51756
  LIMIT 1
51806
51757
  `).get();
51807
- return row ? mapRow(row) : null;
51758
+ if (!row)
51759
+ return null;
51760
+ const turn = mapRow(row);
51761
+ if (turn.ended_at == null)
51762
+ return turn;
51763
+ if (turn.ended_via != null && INTERRUPTED_VIA.has(turn.ended_via))
51764
+ return turn;
51765
+ return null;
51766
+ }
51767
+
51768
+ // gateway/resume-inbound-builder.ts
51769
+ function humanizeElapsed(ms) {
51770
+ if (!Number.isFinite(ms) || ms < 0)
51771
+ return "an unknown amount of time";
51772
+ const sec = Math.round(ms / 1000);
51773
+ if (sec < 45)
51774
+ return "moments";
51775
+ const min = Math.round(sec / 60);
51776
+ if (min < 60)
51777
+ return `~${min} min`;
51778
+ const hr = Math.round(min / 60);
51779
+ if (hr < 24)
51780
+ return `~${hr}h`;
51781
+ const days = Math.round(hr / 24);
51782
+ return `~${days} day${days === 1 ? "" : "s"}`;
51783
+ }
51784
+ function threadIdNum(turn) {
51785
+ if (turn.thread_id == null)
51786
+ return;
51787
+ const n = Number(turn.thread_id);
51788
+ return Number.isFinite(n) ? n : undefined;
51789
+ }
51790
+ function promptClause(turn) {
51791
+ const p = turn.user_prompt_preview?.trim();
51792
+ if (!p)
51793
+ return "";
51794
+ const snippet = p.length > 160 ? p.slice(0, 160) + "\u2026" : p;
51795
+ return ` The request was: "${snippet}".`;
51796
+ }
51797
+ function buildResumeInterruptedInbound(ctx) {
51798
+ const ts = ctx.nowMs ?? Date.now();
51799
+ const elapsed = humanizeElapsed(ts - ctx.turn.started_at);
51800
+ const meta = {
51801
+ source: "resume_interrupted",
51802
+ resume_turn_key: ctx.turn.turn_key,
51803
+ interrupted_via: ctx.turn.ended_via ?? "restart",
51804
+ started_at: String(ctx.turn.started_at)
51805
+ };
51806
+ if (ctx.turn.user_prompt_preview)
51807
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51808
+ const threadId = threadIdNum(ctx.turn);
51809
+ return {
51810
+ type: "inbound",
51811
+ chatId: ctx.turn.chat_id,
51812
+ ...threadId != null ? { threadId } : {},
51813
+ messageId: ts,
51814
+ user: "switchroom",
51815
+ userId: 0,
51816
+ ts,
51817
+ text: `You just restarted. Your previous turn was interrupted ${elapsed} ago, ` + `before it finished \u2014 it was cut off by a restart, not completed.` + promptClause(ctx.turn) + ` Pick that work back up now and continue it through to completion. ` + `In your first message, briefly let the user know you're resuming what ` + `was interrupted (mention roughly how long ago in plain language) so ` + `they're not left wondering \u2014 then carry on with the actual task. Do ` + `not ask whether to resume; just resume. If you genuinely can't tell ` + `what the work was, say so and ask.`,
51818
+ meta
51819
+ };
51820
+ }
51821
+ function buildResumeWatchdogReportInbound(ctx) {
51822
+ const ts = ctx.nowMs ?? Date.now();
51823
+ const idle = humanizeElapsed(ctx.idleMs);
51824
+ const since = humanizeElapsed(ts - ctx.turn.started_at);
51825
+ const toolClause = ctx.turn.tool_call_count != null && ctx.turn.tool_call_count > 0 ? ` You'd run ${ctx.turn.tool_call_count} tool call${ctx.turn.tool_call_count === 1 ? "" : "s"} before it stalled.` : "";
51826
+ const meta = {
51827
+ source: "resume_watchdog_timeout",
51828
+ resume_turn_key: ctx.turn.turn_key,
51829
+ interrupted_via: "timeout",
51830
+ idle_ms: String(ctx.idleMs),
51831
+ started_at: String(ctx.turn.started_at)
51832
+ };
51833
+ if (ctx.turn.tool_call_count != null)
51834
+ meta.tool_call_count = String(ctx.turn.tool_call_count);
51835
+ if (ctx.turn.user_prompt_preview)
51836
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51837
+ const threadId = threadIdNum(ctx.turn);
51838
+ return {
51839
+ type: "inbound",
51840
+ chatId: ctx.turn.chat_id,
51841
+ ...threadId != null ? { threadId } : {},
51842
+ messageId: ts,
51843
+ user: "switchroom",
51844
+ userId: 0,
51845
+ ts,
51846
+ text: `You just restarted. Your previous turn (started ${since} ago) was ` + `killed by the hang-watchdog: it made no observable progress for ${idle} ` + `and the watchdog restarts a turn that goes that long without activity.` + toolClause + promptClause(ctx.turn) + ` Do NOT silently resume it \u2014 it may hang again the same way. Instead, ` + `tell the user plainly what happened: that your last turn was killed ` + `after ${idle} of no progress, and roughly what it was doing. Then ask ` + `whether they want you to retry it or take a different angle. Report ` + `only the honest cause \u2014 no observable progress for that long \u2014 don't ` + `speculate about a deeper root cause you can't see.`,
51847
+ meta
51848
+ };
51849
+ }
51850
+ function selectResumeBuilder(endedVia) {
51851
+ if (endedVia === "timeout")
51852
+ return "report";
51853
+ if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
51854
+ return "resume";
51855
+ if (endedVia == null)
51856
+ return "resume";
51857
+ return null;
51808
51858
  }
51809
51859
 
51810
51860
  // registry/subagents-schema.ts
@@ -51904,11 +51954,11 @@ installGlobalErrorHandlers();
51904
51954
  process.on("beforeExit", () => {
51905
51955
  shutdownAnalytics();
51906
51956
  });
51907
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join36(homedir14(), ".claude", "channels", "telegram");
51908
- var ACCESS_FILE = join36(STATE_DIR, "access.json");
51909
- var APPROVED_DIR = join36(STATE_DIR, "approved");
51910
- var ENV_FILE = join36(STATE_DIR, ".env");
51911
- var INBOX_DIR = join36(STATE_DIR, "inbox");
51957
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join35(homedir14(), ".claude", "channels", "telegram");
51958
+ var ACCESS_FILE = join35(STATE_DIR, "access.json");
51959
+ var APPROVED_DIR = join35(STATE_DIR, "approved");
51960
+ var ENV_FILE = join35(STATE_DIR, ".env");
51961
+ var INBOX_DIR = join35(STATE_DIR, "inbox");
51912
51962
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
51913
51963
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
51914
51964
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -51973,7 +52023,7 @@ function formatBootVersion() {
51973
52023
  }
51974
52024
  try {
51975
52025
  chmodSync6(ENV_FILE, 384);
51976
- for (const line of readFileSync36(ENV_FILE, "utf8").split(`
52026
+ for (const line of readFileSync35(ENV_FILE, "utf8").split(`
51977
52027
  `)) {
51978
52028
  const m = line.match(/^(\w+)=(.*)$/);
51979
52029
  if (m && process.env[m[1]] === undefined)
@@ -52026,7 +52076,7 @@ installTgPostLogger(bot);
52026
52076
  var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
52027
52077
  var GRAMMY_VERSION = (() => {
52028
52078
  try {
52029
- const raw = readFileSync36(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52079
+ const raw = readFileSync35(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52030
52080
  return JSON.parse(raw).version ?? "unknown";
52031
52081
  } catch {
52032
52082
  return "unknown";
@@ -52099,7 +52149,7 @@ function assertSendable(f) {
52099
52149
  } catch {
52100
52150
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
52101
52151
  }
52102
- const inbox = join36(stateReal, "inbox");
52152
+ const inbox = join35(stateReal, "inbox");
52103
52153
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
52104
52154
  throw new Error(`refusing to send channel state: ${f}`);
52105
52155
  }
@@ -52118,7 +52168,7 @@ function assertSendable(f) {
52118
52168
  }
52119
52169
  function readAccessFile() {
52120
52170
  try {
52121
- const raw = readFileSync36(ACCESS_FILE, "utf8");
52171
+ const raw = readFileSync35(ACCESS_FILE, "utf8");
52122
52172
  const parsed = JSON.parse(raw);
52123
52173
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
52124
52174
  const groups = {};
@@ -52155,7 +52205,7 @@ function readAccessFile() {
52155
52205
  if (err.code === "ENOENT")
52156
52206
  return defaultAccess();
52157
52207
  try {
52158
- renameSync13(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52208
+ renameSync12(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52159
52209
  } catch {}
52160
52210
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
52161
52211
  `);
@@ -52188,9 +52238,9 @@ function saveAccess(a) {
52188
52238
  return;
52189
52239
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
52190
52240
  const tmp = ACCESS_FILE + ".tmp";
52191
- writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
52241
+ writeFileSync24(tmp, JSON.stringify(a, null, 2) + `
52192
52242
  `, { mode: 384 });
52193
- renameSync13(tmp, ACCESS_FILE);
52243
+ renameSync12(tmp, ACCESS_FILE);
52194
52244
  }
52195
52245
  function pruneExpired(a) {
52196
52246
  const now = Date.now();
@@ -52208,7 +52258,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
52208
52258
  if (HISTORY_ENABLED) {
52209
52259
  try {
52210
52260
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
52211
- process.stderr.write(`telegram gateway: history capture enabled at ${join36(STATE_DIR, "history.db")}
52261
+ process.stderr.write(`telegram gateway: history capture enabled at ${join35(STATE_DIR, "history.db")}
52212
52262
  `);
52213
52263
  } catch (err) {
52214
52264
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -52216,21 +52266,71 @@ if (HISTORY_ENABLED) {
52216
52266
  }
52217
52267
  }
52218
52268
  var turnsDb = null;
52269
+ var bootResumeInbound = null;
52219
52270
  try {
52220
52271
  const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
52221
52272
  turnsDb = openTurnsDb(agentDir);
52222
52273
  applySubagentsSchema(turnsDb);
52223
- const reaped = markOrphanedAsRestarted(turnsDb);
52274
+ let markerTurnKey = null;
52275
+ let markerAgeMs = null;
52276
+ try {
52277
+ const markerPath = join35(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
52278
+ if (existsSync38(markerPath)) {
52279
+ const st = statSync13(markerPath);
52280
+ markerAgeMs = Date.now() - st.mtimeMs;
52281
+ try {
52282
+ const payload = JSON.parse(readFileSync35(markerPath, "utf8"));
52283
+ if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
52284
+ markerTurnKey = payload.turnKey;
52285
+ }
52286
+ } catch {}
52287
+ }
52288
+ } catch {}
52289
+ const hangSecs = Number(process.env.TURN_HANG_SECS);
52290
+ const hangThresholdMs = (Number.isFinite(hangSecs) && hangSecs > 0 ? hangSecs : 300) * 1000;
52291
+ const reasonSnapshot = markerAgeMs != null ? JSON.stringify({ idleMs: Math.round(markerAgeMs) }) : null;
52292
+ const { reaped, timeoutTurnKey } = markOrphanedWithTimeoutClassification(turnsDb, {
52293
+ markerTurnKey,
52294
+ markerAgeMs,
52295
+ hangThresholdMs,
52296
+ reasonSnapshot
52297
+ });
52224
52298
  if (reaped > 0) {
52225
- process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
52299
+ process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
52226
52300
  `);
52227
52301
  } else {
52228
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join36(agentDir, "telegram", "registry.db")}
52302
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join35(agentDir, "telegram", "registry.db")}
52303
+ `);
52304
+ }
52305
+ const pending2 = findLatestTurnIfInterrupted(turnsDb);
52306
+ const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
52307
+ if (pending2 != null && selfAgent) {
52308
+ const kind = selectResumeBuilder(pending2.ended_via);
52309
+ if (kind === "resume") {
52310
+ bootResumeInbound = { agent: selfAgent, msg: buildResumeInterruptedInbound({ turn: pending2 }) };
52311
+ } else if (kind === "report") {
52312
+ let idleMs = pending2.turn_key === timeoutTurnKey && markerAgeMs != null ? markerAgeMs : null;
52313
+ if (idleMs == null && pending2.interrupt_reason) {
52314
+ try {
52315
+ const parsed = JSON.parse(pending2.interrupt_reason);
52316
+ if (typeof parsed.idleMs === "number" && Number.isFinite(parsed.idleMs))
52317
+ idleMs = parsed.idleMs;
52318
+ } catch {}
52319
+ }
52320
+ if (idleMs == null)
52321
+ idleMs = Math.max(0, Date.now() - pending2.started_at);
52322
+ bootResumeInbound = {
52323
+ agent: selfAgent,
52324
+ msg: buildResumeWatchdogReportInbound({ turn: pending2, idleMs })
52325
+ };
52326
+ }
52327
+ if (bootResumeInbound != null) {
52328
+ process.stderr.write(`telegram gateway: boot-resume queued kind=${kind} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"} chat=${pending2.chat_id}
52229
52329
  `);
52330
+ }
52230
52331
  }
52231
- const pendingEnvPath = join36(agentDir, ".pending-turn.env");
52332
+ const pendingEnvPath = join35(agentDir, ".pending-turn.env");
52232
52333
  try {
52233
- const pending2 = findMostRecentInterruptedTurn(turnsDb);
52234
52334
  if (pending2 != null) {
52235
52335
  const lines = [
52236
52336
  `SWITCHROOM_PENDING_TURN=true`,
@@ -52239,22 +52339,23 @@ try {
52239
52339
  pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
52240
52340
  pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
52241
52341
  `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
52242
- `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
52342
+ `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`,
52343
+ pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
52243
52344
  ];
52244
52345
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
52245
- writeFileSync25(pendingEnvTmp, lines.join(`
52346
+ writeFileSync24(pendingEnvTmp, lines.join(`
52246
52347
  `) + `
52247
52348
  `, { mode: 384 });
52248
- renameSync13(pendingEnvTmp, pendingEnvPath);
52349
+ renameSync12(pendingEnvTmp, pendingEnvPath);
52249
52350
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
52250
52351
  `);
52251
- } else if (existsSync39(pendingEnvPath)) {
52352
+ } else if (existsSync38(pendingEnvPath)) {
52252
52353
  rmSync4(pendingEnvPath, { force: true });
52253
52354
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
52254
52355
  `);
52255
52356
  }
52256
52357
  } catch (err) {
52257
- process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message}) \u2014 resume protocol may not fire
52358
+ process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message})
52258
52359
  `);
52259
52360
  }
52260
52361
  } catch (err) {
@@ -52302,7 +52403,7 @@ function checkApprovals() {
52302
52403
  return;
52303
52404
  }
52304
52405
  for (const senderId of files) {
52305
- const file = join36(APPROVED_DIR, senderId);
52406
+ const file = join35(APPROVED_DIR, senderId);
52306
52407
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
52307
52408
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
52308
52409
  `);
@@ -52669,26 +52770,6 @@ function probeAvailableReactions(chatId) {
52669
52770
  }
52670
52771
  })();
52671
52772
  }
52672
- var pendingHandoffTopic = null;
52673
- function initHandoffContinuity() {
52674
- if (!shouldShowHandoffLine()) {
52675
- pendingHandoffTopic = null;
52676
- return;
52677
- }
52678
- const agentDir = resolveAgentDirFromEnv();
52679
- if (agentDir == null) {
52680
- pendingHandoffTopic = null;
52681
- return;
52682
- }
52683
- pendingHandoffTopic = consumeHandoffTopic(agentDir);
52684
- }
52685
- function takeHandoffPrefix(format) {
52686
- if (pendingHandoffTopic == null)
52687
- return "";
52688
- const line = formatHandoffLine(pendingHandoffTopic, format);
52689
- pendingHandoffTopic = null;
52690
- return line;
52691
- }
52692
52773
  var PHOTO_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"]);
52693
52774
  function chunk2(text, limit, mode) {
52694
52775
  if (text.length <= limit)
@@ -52713,7 +52794,7 @@ function chunk2(text, limit, mode) {
52713
52794
  out.push(rest);
52714
52795
  return out;
52715
52796
  }
52716
- function escapeMarkdownV22(text) {
52797
+ function escapeMarkdownV2(text) {
52717
52798
  const specialChars = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
52718
52799
  const parts = [];
52719
52800
  let last = 0;
@@ -53258,11 +53339,11 @@ var unpinProgressCardForChat = null;
53258
53339
  var getPinnedProgressCardMessageId = null;
53259
53340
  var completeProgressCardTurn = null;
53260
53341
  var subagentWatcher = null;
53261
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join36(STATE_DIR, "gateway.sock");
53342
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join35(STATE_DIR, "gateway.sock");
53262
53343
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
53263
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join36(STATE_DIR, "gateway.pid.json");
53264
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join36(STATE_DIR, "gateway-session.json");
53265
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join36(STATE_DIR, "clean-shutdown.json");
53344
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join35(STATE_DIR, "gateway.pid.json");
53345
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join35(STATE_DIR, "gateway-session.json");
53346
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join35(STATE_DIR, "clean-shutdown.json");
53266
53347
  var GATEWAY_STARTED_AT_MS = Date.now();
53267
53348
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
53268
53349
  var activeBootCard = null;
@@ -53291,7 +53372,7 @@ function ensureIssuesCard(chatId, threadId) {
53291
53372
  bot: botApi,
53292
53373
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
53293
53374
  `),
53294
- persistPath: join36(stateDir, "issues-card.json")
53375
+ persistPath: join35(stateDir, "issues-card.json")
53295
53376
  });
53296
53377
  activeIssuesWatcher = startIssuesWatcher({
53297
53378
  stateDir,
@@ -53450,17 +53531,24 @@ startTimer2({
53450
53531
  }
53451
53532
  });
53452
53533
  var inboundSpool = STATIC ? undefined : createInboundSpool({
53453
- path: join36(STATE_DIR, "inbound-spool.jsonl"),
53534
+ path: join35(STATE_DIR, "inbound-spool.jsonl"),
53454
53535
  fs: {
53455
53536
  appendFileSync: (p, d) => appendFileSync5(p, d),
53456
- readFileSync: (p) => readFileSync36(p, "utf8"),
53457
- writeFileSync: (p, d) => writeFileSync25(p, d),
53458
- renameSync: (a, b) => renameSync13(a, b),
53459
- existsSync: (p) => existsSync39(p),
53537
+ readFileSync: (p) => readFileSync35(p, "utf8"),
53538
+ writeFileSync: (p, d) => writeFileSync24(p, d),
53539
+ renameSync: (a, b) => renameSync12(a, b),
53540
+ existsSync: (p) => existsSync38(p),
53460
53541
  statSizeSync: (p) => statSync13(p).size
53461
53542
  }
53462
53543
  });
53463
53544
  var pendingInboundBuffer = createPendingInboundBuffer({ spool: inboundSpool });
53545
+ if (bootResumeInbound != null) {
53546
+ if (inboundSpool != null) {
53547
+ inboundSpool.put(bootResumeInbound.agent, bootResumeInbound.msg);
53548
+ } else {
53549
+ pendingInboundBuffer.push(bootResumeInbound.agent, bootResumeInbound.msg);
53550
+ }
53551
+ }
53464
53552
  if (inboundSpool != null) {
53465
53553
  const replay = inboundSpool.liveEntries();
53466
53554
  for (const e of replay)
@@ -53589,7 +53677,7 @@ var ipcServer = createIpcServer({
53589
53677
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
53590
53678
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
53591
53679
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
53592
- configSnapshotPath: join36(resolvedAgentDirForCard, ".config-snapshot.json"),
53680
+ configSnapshotPath: join35(resolvedAgentDirForCard, ".config-snapshot.json"),
53593
53681
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
53594
53682
  }, ackMsgId).then((handle) => {
53595
53683
  activeBootCard = handle;
@@ -54011,7 +54099,7 @@ var ipcServer = createIpcServer({
54011
54099
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
54012
54100
  if (Number.isInteger(receiverUid))
54013
54101
  allowedUids.push(receiverUid);
54014
- const socketPath = join36(STATE_DIR, "webhook.sock");
54102
+ const socketPath = join35(STATE_DIR, "webhook.sock");
54015
54103
  const webhookInject = (agentName3, inbound) => {
54016
54104
  const msg = inbound;
54017
54105
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -54261,16 +54349,11 @@ ${url}`;
54261
54349
  effectiveText = markdownToHtml(text);
54262
54350
  } else if (format === "markdownv2") {
54263
54351
  parseMode = "MarkdownV2";
54264
- effectiveText = escapeMarkdownV22(text);
54352
+ effectiveText = escapeMarkdownV2(text);
54265
54353
  } else {
54266
54354
  parseMode = undefined;
54267
54355
  effectiveText = text;
54268
54356
  }
54269
- {
54270
- const prefix = takeHandoffPrefix(format === "html" ? "html" : format === "markdownv2" ? "markdownv2" : "text");
54271
- if (prefix.length > 0)
54272
- effectiveText = prefix + effectiveText;
54273
- }
54274
54357
  assertAllowedChat(chat_id);
54275
54358
  let threadId = resolveThreadId(chat_id, args.message_thread_id);
54276
54359
  if (reply_to == null && quoteOptIn && HISTORY_ENABLED) {
@@ -54683,9 +54766,8 @@ async function executeStreamReply(args) {
54683
54766
  bot: lockedBot,
54684
54767
  retry: robustApiCall,
54685
54768
  markdownToHtml,
54686
- escapeMarkdownV2: escapeMarkdownV22,
54769
+ escapeMarkdownV2,
54687
54770
  repairEscapedWhitespace,
54688
- takeHandoffPrefix,
54689
54771
  assertAllowedChat,
54690
54772
  resolveThreadId,
54691
54773
  disableLinkPreview: access.disableLinkPreview !== false,
@@ -54715,8 +54797,8 @@ async function executeStreamReply(args) {
54715
54797
  progressDriver?.recordOutboundDelivered(args.chat_id, args.message_thread_id);
54716
54798
  } catch {}
54717
54799
  try {
54718
- const threadIdNum = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54719
- noteSignal(statusKey(args.chat_id, threadIdNum), Date.now());
54800
+ const threadIdNum2 = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54801
+ noteSignal(statusKey(args.chat_id, threadIdNum2), Date.now());
54720
54802
  } catch {}
54721
54803
  }
54722
54804
  if (args.done === true && result.messageId != null && streamButtonMeta != null && streamButtonMeta.size > 0) {
@@ -55008,11 +55090,11 @@ async function executeSendGif(rawArgs) {
55008
55090
  };
55009
55091
  }
55010
55092
  async function publishToTelegraph(text, shortName, authorName) {
55011
- const accountPath = join36(STATE_DIR, "telegraph-account.json");
55093
+ const accountPath = join35(STATE_DIR, "telegraph-account.json");
55012
55094
  let account = null;
55013
55095
  try {
55014
- if (existsSync39(accountPath)) {
55015
- const raw = readFileSync36(accountPath, "utf-8");
55096
+ if (existsSync38(accountPath)) {
55097
+ const raw = readFileSync35(accountPath, "utf-8");
55016
55098
  const parsed = JSON.parse(raw);
55017
55099
  if (parsed.shortName && parsed.accessToken) {
55018
55100
  account = parsed;
@@ -55032,7 +55114,7 @@ async function publishToTelegraph(text, shortName, authorName) {
55032
55114
  account = created.value;
55033
55115
  try {
55034
55116
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
55035
- writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55117
+ writeFileSync24(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55036
55118
  } catch (err) {
55037
55119
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
55038
55120
  `);
@@ -55276,7 +55358,7 @@ async function executeDownloadAttachment(args) {
55276
55358
  });
55277
55359
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
55278
55360
  assertInsideInbox(INBOX_DIR, dlPath);
55279
- writeFileSync25(dlPath, buf, { mode: 384 });
55361
+ writeFileSync24(dlPath, buf, { mode: 384 });
55280
55362
  return { content: [{ type: "text", text: dlPath }] };
55281
55363
  }
55282
55364
  async function executeEditMessage(args) {
@@ -55310,7 +55392,7 @@ async function executeEditMessage(args) {
55310
55392
  editText = markdownToHtml(editRawText);
55311
55393
  } else if (editFormat === "markdownv2") {
55312
55394
  editParseMode = "MarkdownV2";
55313
- editText = escapeMarkdownV22(editRawText);
55395
+ editText = escapeMarkdownV2(editRawText);
55314
55396
  } else {
55315
55397
  editParseMode = undefined;
55316
55398
  editText = editRawText;
@@ -55497,6 +55579,14 @@ function closeProgressLane(chatId, threadId) {
55497
55579
  }
55498
55580
  }
55499
55581
  }
55582
+ var FOREGROUND_SUBAGENT_ACCUM_MAX = 12;
55583
+ function composeTurnActivity(turn) {
55584
+ const childLines = [];
55585
+ for (const narrative of turn.foregroundSubAgents.values()) {
55586
+ childLines.push(...narrative);
55587
+ }
55588
+ return renderActivityFeedWithNested(turn.mirrorLines, childLines);
55589
+ }
55500
55590
  async function drainActivitySummary(turn) {
55501
55591
  try {
55502
55592
  while (turn.activityPendingRender !== turn.activityLastSentRender) {
@@ -55589,6 +55679,7 @@ function handleSessionEvent(ev) {
55589
55679
  activityPendingRender: null,
55590
55680
  activityLastSentRender: null,
55591
55681
  mirrorLines: [],
55682
+ foregroundSubAgents: new Map,
55592
55683
  answerStream: null,
55593
55684
  isDm: isDmChatId(ev.chatId)
55594
55685
  };
@@ -55683,7 +55774,7 @@ function handleSessionEvent(ev) {
55683
55774
  return;
55684
55775
  const rendered = appendActivityLabel(turn.mirrorLines, ev.label);
55685
55776
  if (rendered != null) {
55686
- turn.activityPendingRender = rendered;
55777
+ turn.activityPendingRender = composeTurnActivity(turn) ?? rendered;
55687
55778
  if (turn.activityInFlight == null) {
55688
55779
  turn.activityInFlight = drainActivitySummary(turn);
55689
55780
  }
@@ -57106,14 +57197,14 @@ function restartMarkerPath() {
57106
57197
  const agentDir = resolveAgentDirFromEnv();
57107
57198
  if (!agentDir)
57108
57199
  return null;
57109
- return join36(agentDir, "restart-pending.json");
57200
+ return join35(agentDir, "restart-pending.json");
57110
57201
  }
57111
57202
  function writeRestartMarker(marker) {
57112
57203
  const p = restartMarkerPath();
57113
57204
  if (!p)
57114
57205
  return;
57115
57206
  try {
57116
- writeFileSync25(p, JSON.stringify(marker));
57207
+ writeFileSync24(p, JSON.stringify(marker));
57117
57208
  lastPlannedRestartAt = Date.now();
57118
57209
  process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
57119
57210
  `);
@@ -57132,7 +57223,7 @@ function readRestartMarker() {
57132
57223
  if (!p)
57133
57224
  return null;
57134
57225
  try {
57135
- return JSON.parse(readFileSync36(p, "utf8"));
57226
+ return JSON.parse(readFileSync35(p, "utf8"));
57136
57227
  } catch {
57137
57228
  return null;
57138
57229
  }
@@ -57230,7 +57321,7 @@ var _dockerReachable;
57230
57321
  function isDockerReachable() {
57231
57322
  if (_dockerReachable !== undefined)
57232
57323
  return _dockerReachable;
57233
- if (!existsSync39("/var/run/docker.sock")) {
57324
+ if (!existsSync38("/var/run/docker.sock")) {
57234
57325
  _dockerReachable = false;
57235
57326
  return _dockerReachable;
57236
57327
  }
@@ -57247,12 +57338,12 @@ function _resetDockerReachableCache() {
57247
57338
  }
57248
57339
  function spawnSwitchroomDetached(args, onFailure) {
57249
57340
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
57250
- const logPath = join36(STATE_DIR, "detached-spawn.log");
57341
+ const logPath = join35(STATE_DIR, "detached-spawn.log");
57251
57342
  let outFd = null;
57252
57343
  try {
57253
57344
  mkdirSync26(STATE_DIR, { recursive: true });
57254
57345
  outFd = openSync8(logPath, "a");
57255
- writeFileSync25(logPath, `
57346
+ writeFileSync24(logPath, `
57256
57347
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
57257
57348
  `, { flag: "a" });
57258
57349
  } catch {}
@@ -57278,7 +57369,7 @@ function spawnSwitchroomDetached(args, onFailure) {
57278
57369
  return;
57279
57370
  let tail = "";
57280
57371
  try {
57281
- const full = readFileSync36(logPath, "utf8");
57372
+ const full = readFileSync35(logPath, "utf8");
57282
57373
  tail = full.split(`
57283
57374
  `).slice(-30).join(`
57284
57375
  `).trim();
@@ -57620,10 +57711,10 @@ bot.use(async (ctx, next) => {
57620
57711
  });
57621
57712
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
57622
57713
  try {
57623
- const auditPath = join36(homedir14(), ".switchroom", "vault-audit.log");
57624
- if (!existsSync39(auditPath))
57714
+ const auditPath = join35(homedir14(), ".switchroom", "vault-audit.log");
57715
+ if (!existsSync38(auditPath))
57625
57716
  return [];
57626
- const raw = readFileSync36(auditPath, "utf8");
57717
+ const raw = readFileSync35(auditPath, "utf8");
57627
57718
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
57628
57719
  } catch {
57629
57720
  return [];
@@ -57674,7 +57765,7 @@ async function buildAgentMetadata(agentName3) {
57674
57765
  try {
57675
57766
  const agentDir = resolveAgentDirFromEnv();
57676
57767
  if (agentDir) {
57677
- const raw = readFileSync36(join36(agentDir, ".claude", ".claude.json"), "utf8");
57768
+ const raw = readFileSync35(join35(agentDir, ".claude", ".claude.json"), "utf8");
57678
57769
  claudeJson = JSON.parse(raw);
57679
57770
  }
57680
57771
  } catch {}
@@ -57888,10 +57979,10 @@ bot.command("restart", async (ctx) => {
57888
57979
  function flushAgentHandoff(agentDir) {
57889
57980
  let removed = 0;
57890
57981
  for (const fname of [".handoff.md", ".handoff-topic"]) {
57891
- const p = join36(agentDir, fname);
57982
+ const p = join35(agentDir, fname);
57892
57983
  try {
57893
- if (existsSync39(p)) {
57894
- unlinkSync15(p);
57984
+ if (existsSync38(p)) {
57985
+ unlinkSync14(p);
57895
57986
  removed++;
57896
57987
  }
57897
57988
  } catch (err) {
@@ -57946,7 +58037,7 @@ async function handleNewOrResetCommand(ctx, kind) {
57946
58037
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
57947
58038
  if (agentDir != null) {
57948
58039
  try {
57949
- writeFileSync25(join36(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
58040
+ writeFileSync24(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
57950
58041
  `, "utf8");
57951
58042
  } catch (err) {
57952
58043
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -58306,16 +58397,16 @@ bot.command("interrupt", async (ctx) => {
58306
58397
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
58307
58398
  });
58308
58399
  var lockoutOps = {
58309
- readFileSync: (p, enc) => readFileSync36(p, enc),
58310
- writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
58311
- existsSync: (p) => existsSync39(p),
58400
+ readFileSync: (p, enc) => readFileSync35(p, enc),
58401
+ writeFileSync: (p, data, opts) => writeFileSync24(p, data, opts),
58402
+ existsSync: (p) => existsSync38(p),
58312
58403
  mkdirSync: (p, opts) => mkdirSync26(p, opts),
58313
- joinPath: (...parts) => join36(...parts)
58404
+ joinPath: (...parts) => join35(...parts)
58314
58405
  };
58315
58406
  var FLEET_FALLBACK_DEDUP_MS = 30000;
58316
58407
  function isAuthBrokerSocketReachable() {
58317
58408
  try {
58318
- return existsSync39(resolveAuthBrokerSocketPath2());
58409
+ return existsSync38(resolveAuthBrokerSocketPath2());
58319
58410
  } catch {
58320
58411
  return false;
58321
58412
  }
@@ -58376,7 +58467,7 @@ async function runCreditWatch() {
58376
58467
  if (!agentDir)
58377
58468
  return;
58378
58469
  const agentName3 = getMyAgentName();
58379
- const claudeConfigDir = join36(agentDir, ".claude");
58470
+ const claudeConfigDir = join35(agentDir, ".claude");
58380
58471
  const stateDir = STATE_DIR;
58381
58472
  const reason = readClaudeJsonOverage(claudeConfigDir);
58382
58473
  const prev = loadCreditState(stateDir);
@@ -58679,10 +58770,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
58679
58770
  return;
58680
58771
  }
58681
58772
  const { token, id } = result;
58682
- const tokenPath = join36(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58773
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58683
58774
  try {
58684
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58685
- writeFileSync25(tokenPath, token, { mode: 384 });
58775
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58776
+ writeFileSync24(tokenPath, token, { mode: 384 });
58686
58777
  } catch (err) {
58687
58778
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58688
58779
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -58758,10 +58849,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
58758
58849
  return;
58759
58850
  }
58760
58851
  const { token, id } = result;
58761
- const tokenPath = join36(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58852
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58762
58853
  try {
58763
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58764
- writeFileSync25(tokenPath, token, { mode: 384 });
58854
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58855
+ writeFileSync24(tokenPath, token, { mode: 384 });
58765
58856
  } catch (err) {
58766
58857
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58767
58858
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
@@ -59236,10 +59327,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
59236
59327
  return;
59237
59328
  }
59238
59329
  const { token, id } = result;
59239
- const tokenPath = join36(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59330
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59240
59331
  try {
59241
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59242
- writeFileSync25(tokenPath, token, { mode: 384 });
59332
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59333
+ writeFileSync24(tokenPath, token, { mode: 384 });
59243
59334
  } catch (err) {
59244
59335
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
59245
59336
  return;
@@ -60087,7 +60178,7 @@ bot.command("usage", async (ctx) => {
60087
60178
  await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
60088
60179
  return;
60089
60180
  }
60090
- const result = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
60181
+ const result = await fetchQuota2({ claudeConfigDir: join35(agentDir, ".claude") });
60091
60182
  if (!result.ok) {
60092
60183
  await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
60093
60184
  return;
@@ -60527,7 +60618,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
60527
60618
  const unifiedDiff = (() => {
60528
60619
  try {
60529
60620
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
60530
- const raw = readFileSync36(cfgPath, "utf8");
60621
+ const raw = readFileSync35(cfgPath, "utf8");
60531
60622
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
60532
60623
  } catch (err) {
60533
60624
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -60669,7 +60760,7 @@ bot.on("message:photo", async (ctx) => {
60669
60760
  });
60670
60761
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
60671
60762
  assertInsideInbox(INBOX_DIR, dlPath);
60672
- writeFileSync25(dlPath, buf, { mode: 384 });
60763
+ writeFileSync24(dlPath, buf, { mode: 384 });
60673
60764
  return dlPath;
60674
60765
  } catch (err) {
60675
60766
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -60709,8 +60800,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
60709
60800
  let apiKey = null;
60710
60801
  try {
60711
60802
  const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
60712
- if (existsSync39(path)) {
60713
- apiKey = readFileSync36(path, "utf-8").trim();
60803
+ if (existsSync38(path)) {
60804
+ apiKey = readFileSync35(path, "utf-8").trim();
60714
60805
  }
60715
60806
  } catch (err) {
60716
60807
  process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
@@ -61345,7 +61436,6 @@ process.on("SIGINT", () => void shutdown("SIGINT"));
61345
61436
  `) });
61346
61437
  }
61347
61438
  }
61348
- initHandoffContinuity();
61349
61439
  process.on("unhandledRejection", (err) => {
61350
61440
  const action = classifyRejection(err);
61351
61441
  process.stderr.write(`telegram gateway: unhandled rejection (${action}): ${err}
@@ -61566,7 +61656,7 @@ var didOneTimeSetup = false;
61566
61656
  return;
61567
61657
  }
61568
61658
  })();
61569
- const resolvedAgentDirForBootCard = agentDir ?? join36(homedir14(), ".switchroom", "agents", agentSlug);
61659
+ const resolvedAgentDirForBootCard = agentDir ?? join35(homedir14(), ".switchroom", "agents", agentSlug);
61570
61660
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
61571
61661
  agentName: agentDisplayName,
61572
61662
  agentSlug,
@@ -61580,7 +61670,7 @@ var didOneTimeSetup = false;
61580
61670
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
61581
61671
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
61582
61672
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
61583
- configSnapshotPath: join36(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61673
+ configSnapshotPath: join35(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61584
61674
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
61585
61675
  }, ackMsgId);
61586
61676
  activeBootCard = handle;
@@ -61665,6 +61755,7 @@ var didOneTimeSetup = false;
61665
61755
  const watcherAgentDir = resolveAgentDirFromEnv();
61666
61756
  if (watcherAgentDir != null) {
61667
61757
  const workerFeedEnabled = isWorkerActivityFeedEnabled(process.env.SWITCHROOM_WORKER_ACTIVITY_FEED);
61758
+ const foregroundNestingEnabled = process.env.SWITCHROOM_FOREGROUND_SUBAGENT_NESTING !== "0";
61668
61759
  const workerActivityFeed = createWorkerActivityFeed({
61669
61760
  bot: {
61670
61761
  sendMessage: async (cid, text, sendOpts) => {
@@ -61724,6 +61815,19 @@ var didOneTimeSetup = false;
61724
61815
  } catch {}
61725
61816
  }
61726
61817
  const isBackground = dispatch.isBackground;
61818
+ if (!isBackground) {
61819
+ const turn = currentTurn;
61820
+ if (turn != null && turn.foregroundSubAgents.delete(agentId) && !turn.replyCalled) {
61821
+ const rendered = composeTurnActivity(turn);
61822
+ if (rendered != null) {
61823
+ turn.activityPendingRender = rendered;
61824
+ if (turn.activityInFlight == null) {
61825
+ turn.activityInFlight = drainActivitySummary(turn);
61826
+ }
61827
+ }
61828
+ }
61829
+ return;
61830
+ }
61727
61831
  if (workerFeedEnabled) {
61728
61832
  workerActivityFeed.finish(agentId, {
61729
61833
  description: dispatch.feedDescription,
@@ -61784,8 +61888,35 @@ var didOneTimeSetup = false;
61784
61888
  } catch {}
61785
61889
  }
61786
61890
  const isBackground = dispatch.isBackground;
61787
- if (!isBackground)
61891
+ if (!isBackground) {
61892
+ if (!foregroundNestingEnabled)
61893
+ return;
61894
+ const turn = currentTurn;
61895
+ if (turn == null || turn.replyCalled)
61896
+ return;
61897
+ const child = latestSummary.trim().slice(0, 120);
61898
+ if (child.length === 0)
61899
+ return;
61900
+ let narrative = turn.foregroundSubAgents.get(agentId);
61901
+ if (narrative == null) {
61902
+ narrative = [];
61903
+ turn.foregroundSubAgents.set(agentId, narrative);
61904
+ }
61905
+ if (narrative[narrative.length - 1] !== child) {
61906
+ narrative.push(child);
61907
+ if (narrative.length > FOREGROUND_SUBAGENT_ACCUM_MAX) {
61908
+ narrative.splice(0, narrative.length - FOREGROUND_SUBAGENT_ACCUM_MAX);
61909
+ }
61910
+ }
61911
+ const rendered = composeTurnActivity(turn);
61912
+ if (rendered != null) {
61913
+ turn.activityPendingRender = rendered;
61914
+ if (turn.activityInFlight == null) {
61915
+ turn.activityInFlight = drainActivitySummary(turn);
61916
+ }
61917
+ }
61788
61918
  return;
61919
+ }
61789
61920
  if (workerFeedEnabled) {
61790
61921
  workerActivityFeed.update(agentId, fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
61791
61922
  description: dispatch.feedDescription,