switchroom 0.14.21 → 0.14.23

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 (43) 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 +788 -513
  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/subagent-watcher.ts +79 -5
  21. package/telegram-plugin/tests/agent-dir.test.ts +25 -0
  22. package/telegram-plugin/tests/e2e.test.ts +2 -77
  23. package/telegram-plugin/tests/inbound-spool.test.ts +45 -0
  24. package/telegram-plugin/tests/multi-turn-continuity.test.ts +0 -1
  25. package/telegram-plugin/tests/outbound-ordering.test.ts +0 -1
  26. package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -1
  27. package/telegram-plugin/tests/races.test.ts +0 -26
  28. package/telegram-plugin/tests/registry-turns.test.ts +106 -29
  29. package/telegram-plugin/tests/resume-inbound-builder.test.ts +182 -0
  30. package/telegram-plugin/tests/status-accent.test.ts +0 -1
  31. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +0 -1
  32. package/telegram-plugin/tests/stream-reply-handler.test.ts +0 -24
  33. package/telegram-plugin/tests/streaming-e2e.test.ts +0 -1
  34. package/telegram-plugin/tests/streaming-orchestration.test.ts +0 -1
  35. package/telegram-plugin/tests/subagent-registry-bugs.test.ts +7 -3
  36. package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +293 -0
  37. package/telegram-plugin/tests/subagent-watcher.test.ts +23 -15
  38. package/telegram-plugin/tests/tool-activity-summary.test.ts +44 -0
  39. package/telegram-plugin/tests/turns-writer.test.ts +16 -6
  40. package/telegram-plugin/tool-activity-summary.ts +55 -0
  41. package/telegram-plugin/uat/driver.ts +3 -1
  42. package/telegram-plugin/handoff-continuity.ts +0 -206
  43. 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,18 +48815,79 @@ 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
48830
+ function classifyClaudeError(raw) {
48831
+ try {
48832
+ return classifyInner(raw);
48833
+ } catch {
48834
+ return "unknown-4xx";
48835
+ }
48836
+ }
48837
+ function classifyInner(raw) {
48838
+ if (raw == null)
48839
+ return "unknown-4xx";
48840
+ const obj = typeof raw === "object" ? raw : {};
48841
+ const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
48842
+ const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
48843
+ const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
48844
+ const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
48845
+ const sdkCode = extractString(obj, "error_code") ?? "";
48846
+ if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
48847
+ const msg = message.toLowerCase();
48848
+ if (msg.includes("expired") || msg.includes("refresh")) {
48849
+ return "credentials-expired";
48850
+ }
48851
+ return "credentials-invalid";
48852
+ }
48853
+ if (errorType === "invalid_api_key" || errorCode === "invalid_api_key" || sdkCode === "invalid_api_key" || message.toLowerCase().includes("invalid_api_key") || message.toLowerCase().includes("invalid api key")) {
48854
+ return "credentials-invalid";
48855
+ }
48856
+ if (errorType === "credit_balance_too_low" || errorCode === "credit_balance_too_low" || sdkCode === "credit_balance_too_low" || message.toLowerCase().includes("credit_balance_too_low") || message.toLowerCase().includes("credit balance")) {
48857
+ return "credit-exhausted";
48858
+ }
48859
+ if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
48860
+ return "rate-limited";
48861
+ }
48862
+ if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
48863
+ return "rate-limited";
48864
+ }
48865
+ if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
48866
+ return "agent-crashed";
48867
+ }
48868
+ if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
48869
+ return "agent-restarted-unexpectedly";
48870
+ }
48871
+ if (status != null) {
48872
+ if (status >= 400 && status < 500)
48873
+ return "unknown-4xx";
48874
+ if (status >= 500 && status < 600)
48875
+ return "unknown-5xx";
48876
+ }
48877
+ return "unknown-4xx";
48878
+ }
48879
+ function extractString(obj, key) {
48880
+ const v = obj[key];
48881
+ return typeof v === "string" && v.length > 0 ? v : null;
48882
+ }
48883
+ function extractNumber(obj, key) {
48884
+ const v = obj[key];
48885
+ return typeof v === "number" ? v : null;
48886
+ }
48887
+ function getNestedObj(obj, key) {
48888
+ const v = obj[key];
48889
+ return typeof v === "object" && v != null ? v : {};
48890
+ }
48902
48891
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
48903
48892
  var cooldownMap2 = new Map;
48904
48893
 
@@ -49008,6 +48997,72 @@ function projectSubagentLine(line, agentId, state4) {
49008
48997
  }
49009
48998
  return [];
49010
48999
  }
49000
+ function extractRetryState(obj) {
49001
+ return {
49002
+ retryAttempt: typeof obj.retryAttempt === "number" ? obj.retryAttempt : null,
49003
+ maxRetries: typeof obj.maxRetries === "number" ? obj.maxRetries : null
49004
+ };
49005
+ }
49006
+ function detectErrorInTranscriptLine(line) {
49007
+ if (!line || line.length > 2 * 1024 * 1024)
49008
+ return null;
49009
+ let obj;
49010
+ try {
49011
+ obj = JSON.parse(line);
49012
+ } catch {
49013
+ return null;
49014
+ }
49015
+ if (typeof obj !== "object" || obj == null)
49016
+ return null;
49017
+ const type = obj.type;
49018
+ if (obj.isApiErrorMessage === true) {
49019
+ const status = typeof obj.apiErrorStatus === "number" ? obj.apiErrorStatus : null;
49020
+ const errStr = typeof obj.error === "string" ? obj.error : "";
49021
+ const text = extractAssistantText(obj);
49022
+ const kind2 = status === 429 ? "quota-exhausted" : classifyClaudeError({ type: errStr, status, message: text });
49023
+ return {
49024
+ kind: kind2,
49025
+ raw: obj,
49026
+ detail: text || errStr || "api error",
49027
+ transient: kind2 === "rate-limited",
49028
+ terminal: true
49029
+ };
49030
+ }
49031
+ const isErrorLine = type === "api_error" || type === "error";
49032
+ const embeddedError = typeof obj.error === "object" && obj.error != null ? obj.error : null;
49033
+ if (!isErrorLine && !embeddedError)
49034
+ return null;
49035
+ const raw = embeddedError ?? obj;
49036
+ const kind = classifyClaudeError(embeddedError ?? obj);
49037
+ const detail = extractDetailMessage(embeddedError) ?? extractDetailMessage(obj) ?? String(type ?? "");
49038
+ const transient = kind === "rate-limited";
49039
+ const retry = extractRetryState(obj);
49040
+ const terminal = !transient ? true : retry.retryAttempt != null && retry.maxRetries != null ? retry.retryAttempt >= retry.maxRetries : isErrorLine;
49041
+ return { kind, raw, detail, transient, terminal };
49042
+ }
49043
+ function extractDetailMessage(obj) {
49044
+ if (!obj)
49045
+ return null;
49046
+ const msg = obj.message;
49047
+ return typeof msg === "string" && msg.length > 0 ? msg : null;
49048
+ }
49049
+ function extractAssistantText(obj) {
49050
+ const message = obj.message;
49051
+ if (typeof message !== "object" || message == null)
49052
+ return "";
49053
+ const content = message.content;
49054
+ if (!Array.isArray(content))
49055
+ return "";
49056
+ const parts = [];
49057
+ for (const block of content) {
49058
+ if (typeof block === "object" && block != null && block.type === "text") {
49059
+ const t = block.text;
49060
+ if (typeof t === "string")
49061
+ parts.push(t);
49062
+ }
49063
+ }
49064
+ return parts.join(" ").trim();
49065
+ }
49011
49066
 
49012
49067
  // fleet-state.ts
49013
49068
  var SANITISE_MAX_LEN = 120;
@@ -49139,20 +49194,20 @@ function bumpSubagentActivity(db2, args) {
49139
49194
  // gateway/turn-active-marker.ts
49140
49195
  import {
49141
49196
  closeSync,
49142
- existsSync as existsSync24,
49197
+ existsSync as existsSync23,
49143
49198
  mkdirSync as mkdirSync13,
49144
49199
  openSync,
49145
- readFileSync as readFileSync22,
49200
+ readFileSync as readFileSync21,
49146
49201
  statSync as statSync5,
49147
- unlinkSync as unlinkSync10,
49202
+ unlinkSync as unlinkSync9,
49148
49203
  utimesSync,
49149
- writeFileSync as writeFileSync14
49204
+ writeFileSync as writeFileSync13
49150
49205
  } from "node:fs";
49151
- import { join as join21 } from "node:path";
49206
+ import { join as join20 } from "node:path";
49152
49207
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
49153
49208
  function touchTurnActiveMarker(stateDir) {
49154
- const path = join21(stateDir, TURN_ACTIVE_MARKER_FILE);
49155
- if (!existsSync24(path))
49209
+ const path = join20(stateDir, TURN_ACTIVE_MARKER_FILE);
49210
+ if (!existsSync23(path))
49156
49211
  return;
49157
49212
  const now = new Date;
49158
49213
  try {
@@ -49187,7 +49242,7 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
49187
49242
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
49188
49243
  let meta;
49189
49244
  try {
49190
- const raw = readFileSync23(metaPath, "utf8");
49245
+ const raw = readFileSync22(metaPath, "utf8");
49191
49246
  meta = JSON.parse(raw);
49192
49247
  } catch {
49193
49248
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -49261,6 +49316,12 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
49261
49316
  for (const line of lines) {
49262
49317
  if (!line)
49263
49318
  continue;
49319
+ const errInfo = detectErrorInTranscriptLine(line);
49320
+ if (errInfo?.terminal) {
49321
+ entry.errored = true;
49322
+ if (errInfo.detail)
49323
+ entry.errorDetail = errInfo.detail.slice(0, SUBAGENT_RESULT_TEXT_MAX);
49324
+ }
49264
49325
  const events = projectSubagentLine(line, entry.agentId, startState);
49265
49326
  for (const ev of events) {
49266
49327
  const idleSecBeforeBump = Math.round((now - entry.lastActivityAt) / 1000);
@@ -49325,7 +49386,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
49325
49386
  recordSubagentEnd(db2, {
49326
49387
  id: rowRef.id,
49327
49388
  endedAt: now,
49328
- status: "completed"
49389
+ status: entry.errored ? "failed" : "completed"
49329
49390
  });
49330
49391
  }
49331
49392
  } catch (dbErr) {
@@ -49376,7 +49437,7 @@ function startSubagentWatcher(config) {
49376
49437
  clearTimeout(ref.ref);
49377
49438
  });
49378
49439
  const fs2 = config.fs ?? {
49379
- existsSync: existsSync25,
49440
+ existsSync: existsSync24,
49380
49441
  readdirSync: readdirSync3,
49381
49442
  statSync: statSync6,
49382
49443
  openSync: openSync2,
@@ -49435,6 +49496,17 @@ function startSubagentWatcher(config) {
49435
49496
  readSubTail(entry, tail, n, (desc) => {
49436
49497
  log?.(`subagent-watcher: description updated for ${agentId}: ${desc}`);
49437
49498
  }, fs2, log, db2, parentStateDir, config.onUnstall, undefined, config.onProgress);
49499
+ if (isHistorical && entry.state === "running") {
49500
+ entry.historical = false;
49501
+ log?.(`subagent-watcher: ${agentId} was in-flight at boot \u2014 promoting to live (predates watcher; user still awaiting handback)`);
49502
+ if (db2 != null) {
49503
+ try {
49504
+ backfillJsonlAgentId(db2, filePath, agentId, log);
49505
+ } catch (err) {
49506
+ log?.(`subagent-watcher: backfill error for ${agentId}: ${err.message}`);
49507
+ }
49508
+ }
49509
+ }
49438
49510
  if (isHistorical && entry.state === "done") {
49439
49511
  entry.completionNotified = true;
49440
49512
  scheduleTerminalCleanup(agentId);
@@ -49469,11 +49541,11 @@ function startSubagentWatcher(config) {
49469
49541
  config.onFinish({
49470
49542
  agentId,
49471
49543
  state: entry.state,
49472
- outcome: entry.historical ? "orphan" : "completed",
49544
+ outcome: entry.errored ? "failed" : entry.historical ? "orphan" : "completed",
49473
49545
  toolCount: entry.toolCount,
49474
49546
  durationMs: nowFn() - entry.dispatchedAt,
49475
49547
  description: entry.description,
49476
- resultText: entry.lastResultText
49548
+ resultText: entry.errored ? entry.lastResultText || entry.errorDetail || "" : entry.lastResultText
49477
49549
  });
49478
49550
  } catch (cbErr) {
49479
49551
  log?.(`subagent-watcher: onFinish callback error ${agentId}: ${cbErr.message}`);
@@ -49590,7 +49662,7 @@ function startSubagentWatcher(config) {
49590
49662
  recordSubagentEnd(db2, {
49591
49663
  id: rowRef.id,
49592
49664
  endedAt: n,
49593
- status: "completed"
49665
+ status: entry.errored ? "failed" : "completed"
49594
49666
  });
49595
49667
  }
49596
49668
  } catch (dbErr) {
@@ -49610,8 +49682,8 @@ function startSubagentWatcher(config) {
49610
49682
  function rescanSubagentDirs() {
49611
49683
  if (stopped)
49612
49684
  return;
49613
- const claudeHome = join22(agentDir, ".claude");
49614
- const projectsRoot = join22(claudeHome, "projects");
49685
+ const claudeHome = join21(agentDir, ".claude");
49686
+ const projectsRoot = join21(claudeHome, "projects");
49615
49687
  if (!fs2.existsSync(projectsRoot))
49616
49688
  return;
49617
49689
  let projectDirs;
@@ -49628,7 +49700,7 @@ function startSubagentWatcher(config) {
49628
49700
  }
49629
49701
  continue;
49630
49702
  }
49631
- const projectPath = join22(projectsRoot, pDir);
49703
+ const projectPath = join21(projectsRoot, pDir);
49632
49704
  let sessionDirs;
49633
49705
  try {
49634
49706
  sessionDirs = fs2.readdirSync(projectPath);
@@ -49638,7 +49710,7 @@ function startSubagentWatcher(config) {
49638
49710
  for (const sDir of sessionDirs) {
49639
49711
  if (sDir.endsWith(".jsonl"))
49640
49712
  continue;
49641
- const subagentsPath = join22(projectPath, sDir, "subagents");
49713
+ const subagentsPath = join21(projectPath, sDir, "subagents");
49642
49714
  if (!fs2.existsSync(subagentsPath))
49643
49715
  continue;
49644
49716
  if (!dirWatchers.has(subagentsPath)) {
@@ -49646,7 +49718,7 @@ function startSubagentWatcher(config) {
49646
49718
  const w = fs2.watch(subagentsPath, (_event, filename) => {
49647
49719
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
49648
49720
  return;
49649
- const filePath = join22(subagentsPath, filename.toString());
49721
+ const filePath = join21(subagentsPath, filename.toString());
49650
49722
  if (!knownFiles.has(filePath)) {
49651
49723
  scanSubagentsDir(subagentsPath);
49652
49724
  }
@@ -49671,7 +49743,7 @@ function startSubagentWatcher(config) {
49671
49743
  for (const e of entries) {
49672
49744
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
49673
49745
  continue;
49674
- const filePath = join22(subagentsPath, e);
49746
+ const filePath = join21(subagentsPath, e);
49675
49747
  if (knownFiles.has(filePath))
49676
49748
  continue;
49677
49749
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -49788,15 +49860,15 @@ function determineRestartReason(opts) {
49788
49860
  init_boot_card();
49789
49861
 
49790
49862
  // 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";
49863
+ import { existsSync as existsSync29, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync27 } from "node:fs";
49864
+ import { join as join26 } from "node:path";
49793
49865
  import { homedir as homedir12 } from "node:os";
49794
49866
 
49795
49867
  // ../src/host-control/audit-reader.ts
49796
49868
  import { homedir as homedir11 } from "node:os";
49797
- import { join as join26 } from "node:path";
49869
+ import { join as join25 } from "node:path";
49798
49870
  function defaultAuditLogPath(home2 = homedir11()) {
49799
- return join26(home2, ".switchroom", "host-control-audit.log");
49871
+ return join25(home2, ".switchroom", "host-control-audit.log");
49800
49872
  }
49801
49873
  function parseAuditLine(line) {
49802
49874
  const trimmed = line.trim();
@@ -49902,8 +49974,8 @@ function readAndFilter(raw, filters, limit) {
49902
49974
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
49903
49975
  function readLastTerminalUpdateAudit(opts = {}) {
49904
49976
  const path = opts.auditLogPath ?? defaultAuditLogPath();
49905
- const exists = opts.exists ?? existsSync30;
49906
- const readFile = opts.readFile ?? ((p) => readFileSync28(p, "utf-8"));
49977
+ const exists = opts.exists ?? existsSync29;
49978
+ const readFile = opts.readFile ?? ((p) => readFileSync27(p, "utf-8"));
49907
49979
  if (!exists(path))
49908
49980
  return null;
49909
49981
  let raw;
@@ -49964,15 +50036,15 @@ function renderUpdateOutcomeLine(entry) {
49964
50036
  `);
49965
50037
  }
49966
50038
  function claimUpdateAnnouncement(requestId, opts = {}) {
49967
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join27(homedir12(), ".switchroom");
49968
- const dir = join27(stateDir, "update-announced");
50039
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join26(homedir12(), ".switchroom");
50040
+ const dir = join26(stateDir, "update-announced");
49969
50041
  try {
49970
50042
  mkdirSync17(dir, { recursive: true });
49971
50043
  } catch {
49972
50044
  return false;
49973
50045
  }
49974
50046
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
49975
- const path = join27(dir, safeId);
50047
+ const path = join26(dir, safeId);
49976
50048
  try {
49977
50049
  const fd = openSync3(path, "wx");
49978
50050
  closeSync3(fd);
@@ -49991,7 +50063,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
49991
50063
  }
49992
50064
 
49993
50065
  // issues-card.ts
49994
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
50066
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "node:fs";
49995
50067
  var SEVERITY_EMOJI = {
49996
50068
  info: "\u2139\ufe0f",
49997
50069
  warn: "\u26a0\ufe0f",
@@ -50083,7 +50155,7 @@ function extractRetryAfterSecs2(err) {
50083
50155
  var COOLDOWN_JITTER_MS2 = 500;
50084
50156
  function readPersistedMessageId(path, log) {
50085
50157
  try {
50086
- const raw = readFileSync29(path, "utf8");
50158
+ const raw = readFileSync28(path, "utf8");
50087
50159
  const parsed = JSON.parse(raw);
50088
50160
  const v = parsed.messageId;
50089
50161
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -50099,7 +50171,7 @@ function readPersistedMessageId(path, log) {
50099
50171
  }
50100
50172
  function writePersistedMessageId(path, messageId, log) {
50101
50173
  try {
50102
- writeFileSync18(path, JSON.stringify({ messageId }) + `
50174
+ writeFileSync17(path, JSON.stringify({ messageId }) + `
50103
50175
  `, { mode: 384 });
50104
50176
  } catch (err) {
50105
50177
  log(`issues-card: persist write failed (${err.message})`);
@@ -50192,24 +50264,24 @@ function createIssuesCardHandle(opts) {
50192
50264
  }
50193
50265
 
50194
50266
  // issues-watcher.ts
50195
- import { existsSync as existsSync32, statSync as statSync8 } from "node:fs";
50196
- import { join as join29 } from "node:path";
50267
+ import { existsSync as existsSync31, statSync as statSync8 } from "node:fs";
50268
+ import { join as join28 } from "node:path";
50197
50269
 
50198
50270
  // ../src/issues/store.ts
50199
50271
  import {
50200
50272
  closeSync as closeSync4,
50201
- existsSync as existsSync31,
50273
+ existsSync as existsSync30,
50202
50274
  mkdirSync as mkdirSync18,
50203
50275
  openSync as openSync4,
50204
50276
  readdirSync as readdirSync5,
50205
- readFileSync as readFileSync30,
50206
- renameSync as renameSync11,
50277
+ readFileSync as readFileSync29,
50278
+ renameSync as renameSync10,
50207
50279
  statSync as statSync7,
50208
- unlinkSync as unlinkSync11,
50209
- writeFileSync as writeFileSync19,
50280
+ unlinkSync as unlinkSync10,
50281
+ writeFileSync as writeFileSync18,
50210
50282
  writeSync
50211
50283
  } from "node:fs";
50212
- import { join as join28 } from "node:path";
50284
+ import { join as join27 } from "node:path";
50213
50285
  import { randomBytes as randomBytes4 } from "node:crypto";
50214
50286
  import { execSync } from "node:child_process";
50215
50287
 
@@ -50225,12 +50297,12 @@ var SEVERITY_RANK2 = {
50225
50297
  var ISSUES_FILE = "issues.jsonl";
50226
50298
  var ISSUES_LOCK = "issues.lock";
50227
50299
  function readAll(stateDir) {
50228
- const path = join28(stateDir, ISSUES_FILE);
50229
- if (!existsSync31(path))
50300
+ const path = join27(stateDir, ISSUES_FILE);
50301
+ if (!existsSync30(path))
50230
50302
  return [];
50231
50303
  let raw;
50232
50304
  try {
50233
- raw = readFileSync30(path, "utf-8");
50305
+ raw = readFileSync29(path, "utf-8");
50234
50306
  } catch {
50235
50307
  return [];
50236
50308
  }
@@ -50262,7 +50334,7 @@ function list(stateDir, opts = {}) {
50262
50334
  });
50263
50335
  }
50264
50336
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50265
- if (!existsSync31(join28(stateDir, ISSUES_FILE)))
50337
+ if (!existsSync30(join27(stateDir, ISSUES_FILE)))
50266
50338
  return 0;
50267
50339
  return withLock(stateDir, () => {
50268
50340
  const all = readAll(stateDir);
@@ -50280,14 +50352,14 @@ function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50280
50352
  });
50281
50353
  }
50282
50354
  function writeAll(stateDir, events) {
50283
- const path = join28(stateDir, ISSUES_FILE);
50355
+ const path = join27(stateDir, ISSUES_FILE);
50284
50356
  sweepOrphanTmpFiles(stateDir);
50285
50357
  const tmp = `${path}.tmp-${process.pid}-${randomBytes4(4).toString("hex")}`;
50286
50358
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
50287
50359
  `) + `
50288
50360
  `;
50289
- writeFileSync19(tmp, body, "utf-8");
50290
- renameSync11(tmp, path);
50361
+ writeFileSync18(tmp, body, "utf-8");
50362
+ renameSync10(tmp, path);
50291
50363
  }
50292
50364
  var ORPHAN_TMP_TTL_MS = 60000;
50293
50365
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -50302,11 +50374,11 @@ function sweepOrphanTmpFiles(stateDir) {
50302
50374
  for (const entry of entries) {
50303
50375
  if (!entry.startsWith(TMP_PREFIX))
50304
50376
  continue;
50305
- const tmpPath2 = join28(stateDir, entry);
50377
+ const tmpPath2 = join27(stateDir, entry);
50306
50378
  try {
50307
50379
  const stat = statSync7(tmpPath2);
50308
50380
  if (stat.mtimeMs < cutoff) {
50309
- unlinkSync11(tmpPath2);
50381
+ unlinkSync10(tmpPath2);
50310
50382
  }
50311
50383
  } catch {}
50312
50384
  }
@@ -50314,7 +50386,7 @@ function sweepOrphanTmpFiles(stateDir) {
50314
50386
  var LOCK_RETRY_MS = 25;
50315
50387
  var LOCK_TIMEOUT_MS = 1e4;
50316
50388
  function withLock(stateDir, fn) {
50317
- const lockPath = join28(stateDir, ISSUES_LOCK);
50389
+ const lockPath = join27(stateDir, ISSUES_LOCK);
50318
50390
  const startedAt = Date.now();
50319
50391
  let fd = null;
50320
50392
  while (fd === null) {
@@ -50342,27 +50414,27 @@ function withLock(stateDir, fn) {
50342
50414
  closeSync4(fd);
50343
50415
  } catch {}
50344
50416
  try {
50345
- unlinkSync11(lockPath);
50417
+ unlinkSync10(lockPath);
50346
50418
  } catch {}
50347
50419
  }
50348
50420
  }
50349
50421
  function tryStealStaleLock(lockPath) {
50350
50422
  let pidStr;
50351
50423
  try {
50352
- pidStr = readFileSync30(lockPath, "utf-8").trim();
50424
+ pidStr = readFileSync29(lockPath, "utf-8").trim();
50353
50425
  } catch {
50354
50426
  return true;
50355
50427
  }
50356
50428
  const pid = Number(pidStr);
50357
50429
  if (!Number.isFinite(pid) || pid <= 0) {
50358
50430
  try {
50359
- unlinkSync11(lockPath);
50431
+ unlinkSync10(lockPath);
50360
50432
  } catch {}
50361
50433
  return true;
50362
50434
  }
50363
50435
  if (pid === process.pid) {
50364
50436
  try {
50365
- unlinkSync11(lockPath);
50437
+ unlinkSync10(lockPath);
50366
50438
  } catch {}
50367
50439
  return true;
50368
50440
  }
@@ -50377,7 +50449,7 @@ function tryStealStaleLock(lockPath) {
50377
50449
  return false;
50378
50450
  }
50379
50451
  try {
50380
- unlinkSync11(lockPath);
50452
+ unlinkSync10(lockPath);
50381
50453
  } catch {}
50382
50454
  return true;
50383
50455
  }
@@ -50399,7 +50471,7 @@ function isIssueEvent(v) {
50399
50471
  // issues-watcher.ts
50400
50472
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
50401
50473
  function startIssuesWatcher(opts) {
50402
- const path = join29(opts.stateDir, ISSUES_FILE);
50474
+ const path = join28(opts.stateDir, ISSUES_FILE);
50403
50475
  const log = opts.log ?? (() => {});
50404
50476
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
50405
50477
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -50447,7 +50519,7 @@ function startIssuesWatcher(opts) {
50447
50519
  };
50448
50520
  }
50449
50521
  function defaultSignatureProvider(path) {
50450
- if (!existsSync32(path))
50522
+ if (!existsSync31(path))
50451
50523
  return null;
50452
50524
  try {
50453
50525
  const stat = statSync8(path);
@@ -51052,8 +51124,8 @@ function extractFlowItems(line) {
51052
51124
  }
51053
51125
 
51054
51126
  // 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";
51127
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync32, mkdirSync as mkdirSync19 } from "fs";
51128
+ import { join as join29 } from "path";
51057
51129
  var STATE_FILE = "credits-watch.json";
51058
51130
  var FATAL_REASONS = new Set([
51059
51131
  "out_of_credits",
@@ -51065,12 +51137,12 @@ function emptyCreditState() {
51065
51137
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
51066
51138
  }
51067
51139
  function readClaudeJsonOverage(claudeConfigDir) {
51068
- const path = join30(claudeConfigDir, ".claude.json");
51069
- if (!existsSync33(path))
51140
+ const path = join29(claudeConfigDir, ".claude.json");
51141
+ if (!existsSync32(path))
51070
51142
  return null;
51071
51143
  let raw;
51072
51144
  try {
51073
- raw = readFileSync31(path, "utf-8");
51145
+ raw = readFileSync30(path, "utf-8");
51074
51146
  } catch {
51075
51147
  return null;
51076
51148
  }
@@ -51094,7 +51166,7 @@ function evaluateCreditState(args) {
51094
51166
  if (!currentIsFatal && prevIsFatal) {
51095
51167
  return {
51096
51168
  kind: "notify",
51097
- message: `\u2705 <b>${escapeHtml10(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51169
+ message: `\u2705 <b>${escapeHtml9(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51098
51170
  newState: { lastNotifiedReason: null, lastNotifiedAt: now },
51099
51171
  transition: "exited"
51100
51172
  };
@@ -51123,12 +51195,12 @@ function evaluateCreditState(args) {
51123
51195
  function buildEntryMessage(agentName3, reason) {
51124
51196
  const desc = humanizeReason(reason);
51125
51197
  return [
51126
- `\u26a0\ufe0f <b>${escapeHtml10(agentName3)}</b>: ${desc}`,
51198
+ `\u26a0\ufe0f <b>${escapeHtml9(agentName3)}</b>: ${desc}`,
51127
51199
  ``,
51128
51200
  `Cron tasks and inbound replies will fail until this is resolved. Check`,
51129
51201
  `your subscription or pre-paid usage at <a href="https://console.anthropic.com">console.anthropic.com</a>.`,
51130
51202
  ``,
51131
- `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml10(reason)})</i>`
51203
+ `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml9(reason)})</i>`
51132
51204
  ].join(`
51133
51205
  `);
51134
51206
  }
@@ -51146,15 +51218,15 @@ function humanizeReason(reason) {
51146
51218
  return `usage disabled (${reason})`;
51147
51219
  }
51148
51220
  }
51149
- function escapeHtml10(s) {
51221
+ function escapeHtml9(s) {
51150
51222
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51151
51223
  }
51152
51224
  function loadCreditState(stateDir) {
51153
- const path = join30(stateDir, STATE_FILE);
51154
- if (!existsSync33(path))
51225
+ const path = join29(stateDir, STATE_FILE);
51226
+ if (!existsSync32(path))
51155
51227
  return emptyCreditState();
51156
51228
  try {
51157
- const raw = readFileSync31(path, "utf-8");
51229
+ const raw = readFileSync30(path, "utf-8");
51158
51230
  const parsed = JSON.parse(raw);
51159
51231
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
51160
51232
  return {
@@ -51167,14 +51239,14 @@ function loadCreditState(stateDir) {
51167
51239
  }
51168
51240
  function saveCreditState(stateDir, state4) {
51169
51241
  mkdirSync19(stateDir, { recursive: true });
51170
- const path = join30(stateDir, STATE_FILE);
51171
- writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51242
+ const path = join29(stateDir, STATE_FILE);
51243
+ writeFileSync19(path, JSON.stringify(state4, null, 2) + `
51172
51244
  `, { mode: 384 });
51173
51245
  }
51174
51246
 
51175
51247
  // 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";
51248
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync20 } from "fs";
51249
+ import { join as join30 } from "path";
51178
51250
  var STATE_FILE2 = "quota-watch.json";
51179
51251
  function emptyQuotaWatchState() {
51180
51252
  return {};
@@ -51231,11 +51303,11 @@ function buildThrottlingMessage(agentName3, snap) {
51231
51303
  const resetAt = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
51232
51304
  const resetStr = resetAt ? ` \u00b7 refills in ${formatRelative(resetAt, new Date)}` : "";
51233
51305
  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.`;
51306
+ This is a non-active account. Consider <code>/auth use ${escapeHtml10(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
51235
51307
  const altNote = snap.isActive ? `
51236
51308
  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
51309
  return [
51238
- `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51310
+ `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51239
51311
  ``,
51240
51312
  `${fiveStr} of 5h \u00b7 ${sevenStr} of 7d`,
51241
51313
  `Binding window: ${winLabel}${resetStr}`,
@@ -51252,7 +51324,7 @@ function buildRecoveryMessage(agentName3, snap) {
51252
51324
  const q = snap.quota;
51253
51325
  const utilLine = q ? `Current: ${fmtPct(q.fiveHourUtilizationPct)} of 5h \u00b7 ${fmtPct(q.sevenDayUtilizationPct)} of 7d` : "Current quota data unavailable.";
51254
51326
  return [
51255
- `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51327
+ `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51256
51328
  ``,
51257
51329
  utilLine,
51258
51330
  ``,
@@ -51260,15 +51332,15 @@ function buildRecoveryMessage(agentName3, snap) {
51260
51332
  ].join(`
51261
51333
  `);
51262
51334
  }
51263
- function escapeHtml11(s) {
51335
+ function escapeHtml10(s) {
51264
51336
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51265
51337
  }
51266
51338
  function loadQuotaWatchState(stateDir) {
51267
- const path = join31(stateDir, STATE_FILE2);
51268
- if (!existsSync34(path))
51339
+ const path = join30(stateDir, STATE_FILE2);
51340
+ if (!existsSync33(path))
51269
51341
  return emptyQuotaWatchState();
51270
51342
  try {
51271
- const raw = readFileSync32(path, "utf-8");
51343
+ const raw = readFileSync31(path, "utf-8");
51272
51344
  const parsed = JSON.parse(raw);
51273
51345
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
51274
51346
  return emptyQuotaWatchState();
@@ -51286,8 +51358,8 @@ function loadQuotaWatchState(stateDir) {
51286
51358
  }
51287
51359
  function saveQuotaWatchState(stateDir, state4) {
51288
51360
  mkdirSync20(stateDir, { recursive: true });
51289
- const path = join31(stateDir, STATE_FILE2);
51290
- writeFileSync21(path, JSON.stringify(state4, null, 2) + `
51361
+ const path = join30(stateDir, STATE_FILE2);
51362
+ writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51291
51363
  `, { mode: 384 });
51292
51364
  }
51293
51365
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -51300,27 +51372,27 @@ init_auth_snapshot_format();
51300
51372
  // gateway/turn-active-marker.ts
51301
51373
  import {
51302
51374
  closeSync as closeSync5,
51303
- existsSync as existsSync35,
51375
+ existsSync as existsSync34,
51304
51376
  mkdirSync as mkdirSync21,
51305
51377
  openSync as openSync5,
51306
- readFileSync as readFileSync33,
51378
+ readFileSync as readFileSync32,
51307
51379
  statSync as statSync9,
51308
- unlinkSync as unlinkSync12,
51380
+ unlinkSync as unlinkSync11,
51309
51381
  utimesSync as utimesSync2,
51310
- writeFileSync as writeFileSync22
51382
+ writeFileSync as writeFileSync21
51311
51383
  } from "node:fs";
51312
- import { join as join32 } from "node:path";
51384
+ import { join as join31 } from "node:path";
51313
51385
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
51314
51386
  function writeTurnActiveMarker(stateDir, marker) {
51315
51387
  try {
51316
51388
  mkdirSync21(stateDir, { recursive: true });
51317
- writeFileSync22(join32(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51389
+ writeFileSync21(join31(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51318
51390
  `, { mode: 384 });
51319
51391
  } catch {}
51320
51392
  }
51321
51393
  function touchTurnActiveMarker2(stateDir) {
51322
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51323
- if (!existsSync35(path))
51394
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51395
+ if (!existsSync34(path))
51324
51396
  return;
51325
51397
  const now = new Date;
51326
51398
  try {
@@ -51334,12 +51406,12 @@ function touchTurnActiveMarker2(stateDir) {
51334
51406
  }
51335
51407
  function removeTurnActiveMarker(stateDir) {
51336
51408
  try {
51337
- unlinkSync12(join32(stateDir, TURN_ACTIVE_MARKER_FILE2));
51409
+ unlinkSync11(join31(stateDir, TURN_ACTIVE_MARKER_FILE2));
51338
51410
  } catch {}
51339
51411
  }
51340
51412
  function sweepStaleTurnActiveMarker(stateDir, opts) {
51341
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51342
- if (!existsSync35(path))
51413
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51414
+ if (!existsSync34(path))
51343
51415
  return false;
51344
51416
  const now = opts.now ?? Date.now();
51345
51417
  try {
@@ -51351,9 +51423,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51351
51423
  return false;
51352
51424
  let payload = null;
51353
51425
  try {
51354
- payload = readFileSync33(path, "utf8");
51426
+ payload = readFileSync32(path, "utf8");
51355
51427
  } catch {}
51356
- unlinkSync12(path);
51428
+ unlinkSync11(path);
51357
51429
  if (opts.onRemove) {
51358
51430
  try {
51359
51431
  opts.onRemove({
@@ -51370,10 +51442,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51370
51442
  }
51371
51443
 
51372
51444
  // ../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;
51445
+ var VERSION = "0.14.23";
51446
+ var COMMIT_SHA = "8ac2987a";
51447
+ var COMMIT_DATE = "2026-05-31T22:03:26Z";
51448
+ var LATEST_PR = 2031;
51377
51449
  var COMMITS_AHEAD_OF_TAG = 0;
51378
51450
 
51379
51451
  // gateway/boot-version.ts
@@ -51447,11 +51519,11 @@ init_peercred();
51447
51519
  import * as net5 from "node:net";
51448
51520
  import * as fs2 from "node:fs";
51449
51521
  import { homedir as homedir13 } from "node:os";
51450
- import { join as join33 } from "node:path";
51522
+ import { join as join32 } from "node:path";
51451
51523
  var DEFAULT_TIMEOUT_MS4 = 2000;
51452
51524
  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");
51525
+ var LEGACY_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "vault-broker.sock");
51526
+ var OPERATOR_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "broker-operator", "sock");
51455
51527
  function defaultBrokerSocketPath2() {
51456
51528
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
51457
51529
  return OPERATOR_SOCKET_PATH2;
@@ -51674,7 +51746,7 @@ function resolveVaultApprovalPosture(broker) {
51674
51746
 
51675
51747
  // registry/turns-schema.ts
51676
51748
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync22 } from "fs";
51677
- import { join as join34 } from "path";
51749
+ import { join as join33 } from "path";
51678
51750
  var DatabaseClass2 = null;
51679
51751
  function loadDatabaseClass2() {
51680
51752
  if (DatabaseClass2 != null)
@@ -51707,6 +51779,7 @@ var SCHEMA_SQL = `
51707
51779
  user_prompt_preview TEXT,
51708
51780
  assistant_reply_preview TEXT,
51709
51781
  tool_call_count INTEGER,
51782
+ interrupt_reason TEXT,
51710
51783
  created_at INTEGER NOT NULL,
51711
51784
  updated_at INTEGER NOT NULL
51712
51785
  );
@@ -51717,11 +51790,14 @@ var PHASE1_MIGRATIONS = [
51717
51790
  `ALTER TABLE turns ADD COLUMN assistant_reply_preview TEXT`,
51718
51791
  `ALTER TABLE turns ADD COLUMN tool_call_count INTEGER`
51719
51792
  ];
51793
+ var PHASE2_MIGRATIONS = [
51794
+ `ALTER TABLE turns ADD COLUMN interrupt_reason TEXT`
51795
+ ];
51720
51796
  function applySchema(db2) {
51721
51797
  db2.exec("PRAGMA journal_mode = WAL");
51722
51798
  db2.exec("PRAGMA synchronous = NORMAL");
51723
51799
  db2.exec(SCHEMA_SQL);
51724
- for (const sql of PHASE1_MIGRATIONS) {
51800
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS]) {
51725
51801
  try {
51726
51802
  db2.exec(sql);
51727
51803
  } catch (err) {
@@ -51733,9 +51809,9 @@ function applySchema(db2) {
51733
51809
  }
51734
51810
  function openTurnsDb(agentDir) {
51735
51811
  const Database = loadDatabaseClass2();
51736
- const dir = join34(agentDir, "telegram");
51812
+ const dir = join33(agentDir, "telegram");
51737
51813
  mkdirSync22(dir, { recursive: true, mode: 448 });
51738
- const path = join34(dir, "registry.db");
51814
+ const path = join33(dir, "registry.db");
51739
51815
  const db2 = new Database(path, { create: true });
51740
51816
  applySchema(db2);
51741
51817
  try {
@@ -51757,6 +51833,7 @@ function mapRow(row) {
51757
51833
  user_prompt_preview: row.user_prompt_preview,
51758
51834
  assistant_reply_preview: row.assistant_reply_preview,
51759
51835
  tool_call_count: row.tool_call_count,
51836
+ interrupt_reason: row.interrupt_reason,
51760
51837
  created_at: row.created_at,
51761
51838
  updated_at: row.updated_at
51762
51839
  };
@@ -51785,26 +51862,143 @@ function recordTurnEnd(db2, args) {
51785
51862
  WHERE turn_key = ?
51786
51863
  `).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
51864
  }
51788
- function markOrphanedAsRestarted(db2) {
51789
- const now = Date.now();
51790
- const result = db2.prepare(`
51865
+ function markOrphanedWithTimeoutClassification(db2, opts) {
51866
+ const now = opts.now ?? Date.now();
51867
+ const isHang = opts.markerAgeMs != null && opts.markerAgeMs >= opts.hangThresholdMs && opts.markerTurnKey != null && opts.markerTurnKey.length > 0;
51868
+ let timeoutTurnKey = null;
51869
+ if (isHang) {
51870
+ const r = db2.prepare(`
51871
+ UPDATE turns
51872
+ SET ended_at = ?,
51873
+ ended_via = 'timeout',
51874
+ interrupt_reason = ?,
51875
+ updated_at = ?
51876
+ WHERE turn_key = ? AND ended_at IS NULL
51877
+ `).run(now, opts.reasonSnapshot ?? null, now, opts.markerTurnKey);
51878
+ if (r.changes > 0)
51879
+ timeoutTurnKey = opts.markerTurnKey ?? null;
51880
+ }
51881
+ const rest = db2.prepare(`
51791
51882
  UPDATE turns
51792
51883
  SET ended_at = ?,
51793
51884
  ended_via = 'restart',
51794
51885
  updated_at = ?
51795
51886
  WHERE ended_at IS NULL
51796
51887
  `).run(now, now);
51797
- return result.changes;
51888
+ return { reaped: (timeoutTurnKey ? 1 : 0) + rest.changes, timeoutTurnKey };
51798
51889
  }
51799
- function findMostRecentInterruptedTurn(db2) {
51890
+ var INTERRUPTED_VIA = new Set([
51891
+ "restart",
51892
+ "sigterm",
51893
+ "timeout",
51894
+ "unknown"
51895
+ ]);
51896
+ function findLatestTurnIfInterrupted(db2) {
51800
51897
  const row = db2.prepare(`
51801
51898
  SELECT * FROM turns
51802
- WHERE ended_at IS NULL
51803
- OR ended_via IN ('restart', 'sigterm', 'timeout')
51804
51899
  ORDER BY started_at DESC
51805
51900
  LIMIT 1
51806
51901
  `).get();
51807
- return row ? mapRow(row) : null;
51902
+ if (!row)
51903
+ return null;
51904
+ const turn = mapRow(row);
51905
+ if (turn.ended_at == null)
51906
+ return turn;
51907
+ if (turn.ended_via != null && INTERRUPTED_VIA.has(turn.ended_via))
51908
+ return turn;
51909
+ return null;
51910
+ }
51911
+
51912
+ // gateway/resume-inbound-builder.ts
51913
+ function humanizeElapsed(ms) {
51914
+ if (!Number.isFinite(ms) || ms < 0)
51915
+ return "an unknown amount of time";
51916
+ const sec = Math.round(ms / 1000);
51917
+ if (sec < 45)
51918
+ return "moments";
51919
+ const min = Math.round(sec / 60);
51920
+ if (min < 60)
51921
+ return `~${min} min`;
51922
+ const hr = Math.round(min / 60);
51923
+ if (hr < 24)
51924
+ return `~${hr}h`;
51925
+ const days = Math.round(hr / 24);
51926
+ return `~${days} day${days === 1 ? "" : "s"}`;
51927
+ }
51928
+ function threadIdNum(turn) {
51929
+ if (turn.thread_id == null)
51930
+ return;
51931
+ const n = Number(turn.thread_id);
51932
+ return Number.isFinite(n) ? n : undefined;
51933
+ }
51934
+ function promptClause(turn) {
51935
+ const p = turn.user_prompt_preview?.trim();
51936
+ if (!p)
51937
+ return "";
51938
+ const snippet = p.length > 160 ? p.slice(0, 160) + "\u2026" : p;
51939
+ return ` The request was: "${snippet}".`;
51940
+ }
51941
+ function buildResumeInterruptedInbound(ctx) {
51942
+ const ts = ctx.nowMs ?? Date.now();
51943
+ const elapsed = humanizeElapsed(ts - ctx.turn.started_at);
51944
+ const meta = {
51945
+ source: "resume_interrupted",
51946
+ resume_turn_key: ctx.turn.turn_key,
51947
+ interrupted_via: ctx.turn.ended_via ?? "restart",
51948
+ started_at: String(ctx.turn.started_at)
51949
+ };
51950
+ if (ctx.turn.user_prompt_preview)
51951
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51952
+ const threadId = threadIdNum(ctx.turn);
51953
+ return {
51954
+ type: "inbound",
51955
+ chatId: ctx.turn.chat_id,
51956
+ ...threadId != null ? { threadId } : {},
51957
+ messageId: ts,
51958
+ user: "switchroom",
51959
+ userId: 0,
51960
+ ts,
51961
+ 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.`,
51962
+ meta
51963
+ };
51964
+ }
51965
+ function buildResumeWatchdogReportInbound(ctx) {
51966
+ const ts = ctx.nowMs ?? Date.now();
51967
+ const idle = humanizeElapsed(ctx.idleMs);
51968
+ const since = humanizeElapsed(ts - ctx.turn.started_at);
51969
+ 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.` : "";
51970
+ const meta = {
51971
+ source: "resume_watchdog_timeout",
51972
+ resume_turn_key: ctx.turn.turn_key,
51973
+ interrupted_via: "timeout",
51974
+ idle_ms: String(ctx.idleMs),
51975
+ started_at: String(ctx.turn.started_at)
51976
+ };
51977
+ if (ctx.turn.tool_call_count != null)
51978
+ meta.tool_call_count = String(ctx.turn.tool_call_count);
51979
+ if (ctx.turn.user_prompt_preview)
51980
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51981
+ const threadId = threadIdNum(ctx.turn);
51982
+ return {
51983
+ type: "inbound",
51984
+ chatId: ctx.turn.chat_id,
51985
+ ...threadId != null ? { threadId } : {},
51986
+ messageId: ts,
51987
+ user: "switchroom",
51988
+ userId: 0,
51989
+ ts,
51990
+ 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.`,
51991
+ meta
51992
+ };
51993
+ }
51994
+ function selectResumeBuilder(endedVia) {
51995
+ if (endedVia === "timeout")
51996
+ return "report";
51997
+ if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
51998
+ return "resume";
51999
+ if (endedVia == null)
52000
+ return "resume";
52001
+ return null;
51808
52002
  }
51809
52003
 
51810
52004
  // registry/subagents-schema.ts
@@ -51904,11 +52098,11 @@ installGlobalErrorHandlers();
51904
52098
  process.on("beforeExit", () => {
51905
52099
  shutdownAnalytics();
51906
52100
  });
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");
52101
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join35(homedir14(), ".claude", "channels", "telegram");
52102
+ var ACCESS_FILE = join35(STATE_DIR, "access.json");
52103
+ var APPROVED_DIR = join35(STATE_DIR, "approved");
52104
+ var ENV_FILE = join35(STATE_DIR, ".env");
52105
+ var INBOX_DIR = join35(STATE_DIR, "inbox");
51912
52106
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
51913
52107
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
51914
52108
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -51973,7 +52167,7 @@ function formatBootVersion() {
51973
52167
  }
51974
52168
  try {
51975
52169
  chmodSync6(ENV_FILE, 384);
51976
- for (const line of readFileSync36(ENV_FILE, "utf8").split(`
52170
+ for (const line of readFileSync35(ENV_FILE, "utf8").split(`
51977
52171
  `)) {
51978
52172
  const m = line.match(/^(\w+)=(.*)$/);
51979
52173
  if (m && process.env[m[1]] === undefined)
@@ -52026,7 +52220,7 @@ installTgPostLogger(bot);
52026
52220
  var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
52027
52221
  var GRAMMY_VERSION = (() => {
52028
52222
  try {
52029
- const raw = readFileSync36(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52223
+ const raw = readFileSync35(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52030
52224
  return JSON.parse(raw).version ?? "unknown";
52031
52225
  } catch {
52032
52226
  return "unknown";
@@ -52099,7 +52293,7 @@ function assertSendable(f) {
52099
52293
  } catch {
52100
52294
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
52101
52295
  }
52102
- const inbox = join36(stateReal, "inbox");
52296
+ const inbox = join35(stateReal, "inbox");
52103
52297
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
52104
52298
  throw new Error(`refusing to send channel state: ${f}`);
52105
52299
  }
@@ -52118,7 +52312,7 @@ function assertSendable(f) {
52118
52312
  }
52119
52313
  function readAccessFile() {
52120
52314
  try {
52121
- const raw = readFileSync36(ACCESS_FILE, "utf8");
52315
+ const raw = readFileSync35(ACCESS_FILE, "utf8");
52122
52316
  const parsed = JSON.parse(raw);
52123
52317
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
52124
52318
  const groups = {};
@@ -52155,7 +52349,7 @@ function readAccessFile() {
52155
52349
  if (err.code === "ENOENT")
52156
52350
  return defaultAccess();
52157
52351
  try {
52158
- renameSync13(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52352
+ renameSync12(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52159
52353
  } catch {}
52160
52354
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
52161
52355
  `);
@@ -52188,9 +52382,9 @@ function saveAccess(a) {
52188
52382
  return;
52189
52383
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
52190
52384
  const tmp = ACCESS_FILE + ".tmp";
52191
- writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
52385
+ writeFileSync24(tmp, JSON.stringify(a, null, 2) + `
52192
52386
  `, { mode: 384 });
52193
- renameSync13(tmp, ACCESS_FILE);
52387
+ renameSync12(tmp, ACCESS_FILE);
52194
52388
  }
52195
52389
  function pruneExpired(a) {
52196
52390
  const now = Date.now();
@@ -52208,7 +52402,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
52208
52402
  if (HISTORY_ENABLED) {
52209
52403
  try {
52210
52404
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
52211
- process.stderr.write(`telegram gateway: history capture enabled at ${join36(STATE_DIR, "history.db")}
52405
+ process.stderr.write(`telegram gateway: history capture enabled at ${join35(STATE_DIR, "history.db")}
52212
52406
  `);
52213
52407
  } catch (err) {
52214
52408
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -52216,21 +52410,71 @@ if (HISTORY_ENABLED) {
52216
52410
  }
52217
52411
  }
52218
52412
  var turnsDb = null;
52413
+ var bootResumeInbound = null;
52219
52414
  try {
52220
52415
  const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
52221
52416
  turnsDb = openTurnsDb(agentDir);
52222
52417
  applySubagentsSchema(turnsDb);
52223
- const reaped = markOrphanedAsRestarted(turnsDb);
52418
+ let markerTurnKey = null;
52419
+ let markerAgeMs = null;
52420
+ try {
52421
+ const markerPath = join35(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
52422
+ if (existsSync38(markerPath)) {
52423
+ const st = statSync13(markerPath);
52424
+ markerAgeMs = Date.now() - st.mtimeMs;
52425
+ try {
52426
+ const payload = JSON.parse(readFileSync35(markerPath, "utf8"));
52427
+ if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
52428
+ markerTurnKey = payload.turnKey;
52429
+ }
52430
+ } catch {}
52431
+ }
52432
+ } catch {}
52433
+ const hangSecs = Number(process.env.TURN_HANG_SECS);
52434
+ const hangThresholdMs = (Number.isFinite(hangSecs) && hangSecs > 0 ? hangSecs : 300) * 1000;
52435
+ const reasonSnapshot = markerAgeMs != null ? JSON.stringify({ idleMs: Math.round(markerAgeMs) }) : null;
52436
+ const { reaped, timeoutTurnKey } = markOrphanedWithTimeoutClassification(turnsDb, {
52437
+ markerTurnKey,
52438
+ markerAgeMs,
52439
+ hangThresholdMs,
52440
+ reasonSnapshot
52441
+ });
52224
52442
  if (reaped > 0) {
52225
- process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
52443
+ process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
52226
52444
  `);
52227
52445
  } else {
52228
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join36(agentDir, "telegram", "registry.db")}
52446
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join35(agentDir, "telegram", "registry.db")}
52447
+ `);
52448
+ }
52449
+ const pending2 = findLatestTurnIfInterrupted(turnsDb);
52450
+ const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
52451
+ if (pending2 != null && selfAgent) {
52452
+ const kind = selectResumeBuilder(pending2.ended_via);
52453
+ if (kind === "resume") {
52454
+ bootResumeInbound = { agent: selfAgent, msg: buildResumeInterruptedInbound({ turn: pending2 }) };
52455
+ } else if (kind === "report") {
52456
+ let idleMs = pending2.turn_key === timeoutTurnKey && markerAgeMs != null ? markerAgeMs : null;
52457
+ if (idleMs == null && pending2.interrupt_reason) {
52458
+ try {
52459
+ const parsed = JSON.parse(pending2.interrupt_reason);
52460
+ if (typeof parsed.idleMs === "number" && Number.isFinite(parsed.idleMs))
52461
+ idleMs = parsed.idleMs;
52462
+ } catch {}
52463
+ }
52464
+ if (idleMs == null)
52465
+ idleMs = Math.max(0, Date.now() - pending2.started_at);
52466
+ bootResumeInbound = {
52467
+ agent: selfAgent,
52468
+ msg: buildResumeWatchdogReportInbound({ turn: pending2, idleMs })
52469
+ };
52470
+ }
52471
+ if (bootResumeInbound != null) {
52472
+ process.stderr.write(`telegram gateway: boot-resume queued kind=${kind} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"} chat=${pending2.chat_id}
52229
52473
  `);
52474
+ }
52230
52475
  }
52231
- const pendingEnvPath = join36(agentDir, ".pending-turn.env");
52476
+ const pendingEnvPath = join35(agentDir, ".pending-turn.env");
52232
52477
  try {
52233
- const pending2 = findMostRecentInterruptedTurn(turnsDb);
52234
52478
  if (pending2 != null) {
52235
52479
  const lines = [
52236
52480
  `SWITCHROOM_PENDING_TURN=true`,
@@ -52239,22 +52483,23 @@ try {
52239
52483
  pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
52240
52484
  pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
52241
52485
  `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
52242
- `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
52486
+ `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`,
52487
+ pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
52243
52488
  ];
52244
52489
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
52245
- writeFileSync25(pendingEnvTmp, lines.join(`
52490
+ writeFileSync24(pendingEnvTmp, lines.join(`
52246
52491
  `) + `
52247
52492
  `, { mode: 384 });
52248
- renameSync13(pendingEnvTmp, pendingEnvPath);
52493
+ renameSync12(pendingEnvTmp, pendingEnvPath);
52249
52494
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
52250
52495
  `);
52251
- } else if (existsSync39(pendingEnvPath)) {
52496
+ } else if (existsSync38(pendingEnvPath)) {
52252
52497
  rmSync4(pendingEnvPath, { force: true });
52253
52498
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
52254
52499
  `);
52255
52500
  }
52256
52501
  } catch (err) {
52257
- process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message}) \u2014 resume protocol may not fire
52502
+ process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message})
52258
52503
  `);
52259
52504
  }
52260
52505
  } catch (err) {
@@ -52302,7 +52547,7 @@ function checkApprovals() {
52302
52547
  return;
52303
52548
  }
52304
52549
  for (const senderId of files) {
52305
- const file = join36(APPROVED_DIR, senderId);
52550
+ const file = join35(APPROVED_DIR, senderId);
52306
52551
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
52307
52552
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
52308
52553
  `);
@@ -52669,26 +52914,6 @@ function probeAvailableReactions(chatId) {
52669
52914
  }
52670
52915
  })();
52671
52916
  }
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
52917
  var PHOTO_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"]);
52693
52918
  function chunk2(text, limit, mode) {
52694
52919
  if (text.length <= limit)
@@ -52713,7 +52938,7 @@ function chunk2(text, limit, mode) {
52713
52938
  out.push(rest);
52714
52939
  return out;
52715
52940
  }
52716
- function escapeMarkdownV22(text) {
52941
+ function escapeMarkdownV2(text) {
52717
52942
  const specialChars = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
52718
52943
  const parts = [];
52719
52944
  let last = 0;
@@ -53258,11 +53483,11 @@ var unpinProgressCardForChat = null;
53258
53483
  var getPinnedProgressCardMessageId = null;
53259
53484
  var completeProgressCardTurn = null;
53260
53485
  var subagentWatcher = null;
53261
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join36(STATE_DIR, "gateway.sock");
53486
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join35(STATE_DIR, "gateway.sock");
53262
53487
  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");
53488
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join35(STATE_DIR, "gateway.pid.json");
53489
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join35(STATE_DIR, "gateway-session.json");
53490
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join35(STATE_DIR, "clean-shutdown.json");
53266
53491
  var GATEWAY_STARTED_AT_MS = Date.now();
53267
53492
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
53268
53493
  var activeBootCard = null;
@@ -53291,7 +53516,7 @@ function ensureIssuesCard(chatId, threadId) {
53291
53516
  bot: botApi,
53292
53517
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
53293
53518
  `),
53294
- persistPath: join36(stateDir, "issues-card.json")
53519
+ persistPath: join35(stateDir, "issues-card.json")
53295
53520
  });
53296
53521
  activeIssuesWatcher = startIssuesWatcher({
53297
53522
  stateDir,
@@ -53450,17 +53675,24 @@ startTimer2({
53450
53675
  }
53451
53676
  });
53452
53677
  var inboundSpool = STATIC ? undefined : createInboundSpool({
53453
- path: join36(STATE_DIR, "inbound-spool.jsonl"),
53678
+ path: join35(STATE_DIR, "inbound-spool.jsonl"),
53454
53679
  fs: {
53455
53680
  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),
53681
+ readFileSync: (p) => readFileSync35(p, "utf8"),
53682
+ writeFileSync: (p, d) => writeFileSync24(p, d),
53683
+ renameSync: (a, b) => renameSync12(a, b),
53684
+ existsSync: (p) => existsSync38(p),
53460
53685
  statSizeSync: (p) => statSync13(p).size
53461
53686
  }
53462
53687
  });
53463
53688
  var pendingInboundBuffer = createPendingInboundBuffer({ spool: inboundSpool });
53689
+ if (bootResumeInbound != null) {
53690
+ if (inboundSpool != null) {
53691
+ inboundSpool.put(bootResumeInbound.agent, bootResumeInbound.msg);
53692
+ } else {
53693
+ pendingInboundBuffer.push(bootResumeInbound.agent, bootResumeInbound.msg);
53694
+ }
53695
+ }
53464
53696
  if (inboundSpool != null) {
53465
53697
  const replay = inboundSpool.liveEntries();
53466
53698
  for (const e of replay)
@@ -53589,7 +53821,7 @@ var ipcServer = createIpcServer({
53589
53821
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
53590
53822
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
53591
53823
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
53592
- configSnapshotPath: join36(resolvedAgentDirForCard, ".config-snapshot.json"),
53824
+ configSnapshotPath: join35(resolvedAgentDirForCard, ".config-snapshot.json"),
53593
53825
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
53594
53826
  }, ackMsgId).then((handle) => {
53595
53827
  activeBootCard = handle;
@@ -54011,7 +54243,7 @@ var ipcServer = createIpcServer({
54011
54243
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
54012
54244
  if (Number.isInteger(receiverUid))
54013
54245
  allowedUids.push(receiverUid);
54014
- const socketPath = join36(STATE_DIR, "webhook.sock");
54246
+ const socketPath = join35(STATE_DIR, "webhook.sock");
54015
54247
  const webhookInject = (agentName3, inbound) => {
54016
54248
  const msg = inbound;
54017
54249
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -54261,16 +54493,11 @@ ${url}`;
54261
54493
  effectiveText = markdownToHtml(text);
54262
54494
  } else if (format === "markdownv2") {
54263
54495
  parseMode = "MarkdownV2";
54264
- effectiveText = escapeMarkdownV22(text);
54496
+ effectiveText = escapeMarkdownV2(text);
54265
54497
  } else {
54266
54498
  parseMode = undefined;
54267
54499
  effectiveText = text;
54268
54500
  }
54269
- {
54270
- const prefix = takeHandoffPrefix(format === "html" ? "html" : format === "markdownv2" ? "markdownv2" : "text");
54271
- if (prefix.length > 0)
54272
- effectiveText = prefix + effectiveText;
54273
- }
54274
54501
  assertAllowedChat(chat_id);
54275
54502
  let threadId = resolveThreadId(chat_id, args.message_thread_id);
54276
54503
  if (reply_to == null && quoteOptIn && HISTORY_ENABLED) {
@@ -54683,9 +54910,8 @@ async function executeStreamReply(args) {
54683
54910
  bot: lockedBot,
54684
54911
  retry: robustApiCall,
54685
54912
  markdownToHtml,
54686
- escapeMarkdownV2: escapeMarkdownV22,
54913
+ escapeMarkdownV2,
54687
54914
  repairEscapedWhitespace,
54688
- takeHandoffPrefix,
54689
54915
  assertAllowedChat,
54690
54916
  resolveThreadId,
54691
54917
  disableLinkPreview: access.disableLinkPreview !== false,
@@ -54715,8 +54941,8 @@ async function executeStreamReply(args) {
54715
54941
  progressDriver?.recordOutboundDelivered(args.chat_id, args.message_thread_id);
54716
54942
  } catch {}
54717
54943
  try {
54718
- const threadIdNum = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54719
- noteSignal(statusKey(args.chat_id, threadIdNum), Date.now());
54944
+ const threadIdNum2 = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54945
+ noteSignal(statusKey(args.chat_id, threadIdNum2), Date.now());
54720
54946
  } catch {}
54721
54947
  }
54722
54948
  if (args.done === true && result.messageId != null && streamButtonMeta != null && streamButtonMeta.size > 0) {
@@ -55008,11 +55234,11 @@ async function executeSendGif(rawArgs) {
55008
55234
  };
55009
55235
  }
55010
55236
  async function publishToTelegraph(text, shortName, authorName) {
55011
- const accountPath = join36(STATE_DIR, "telegraph-account.json");
55237
+ const accountPath = join35(STATE_DIR, "telegraph-account.json");
55012
55238
  let account = null;
55013
55239
  try {
55014
- if (existsSync39(accountPath)) {
55015
- const raw = readFileSync36(accountPath, "utf-8");
55240
+ if (existsSync38(accountPath)) {
55241
+ const raw = readFileSync35(accountPath, "utf-8");
55016
55242
  const parsed = JSON.parse(raw);
55017
55243
  if (parsed.shortName && parsed.accessToken) {
55018
55244
  account = parsed;
@@ -55032,7 +55258,7 @@ async function publishToTelegraph(text, shortName, authorName) {
55032
55258
  account = created.value;
55033
55259
  try {
55034
55260
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
55035
- writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55261
+ writeFileSync24(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55036
55262
  } catch (err) {
55037
55263
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
55038
55264
  `);
@@ -55276,7 +55502,7 @@ async function executeDownloadAttachment(args) {
55276
55502
  });
55277
55503
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
55278
55504
  assertInsideInbox(INBOX_DIR, dlPath);
55279
- writeFileSync25(dlPath, buf, { mode: 384 });
55505
+ writeFileSync24(dlPath, buf, { mode: 384 });
55280
55506
  return { content: [{ type: "text", text: dlPath }] };
55281
55507
  }
55282
55508
  async function executeEditMessage(args) {
@@ -55310,7 +55536,7 @@ async function executeEditMessage(args) {
55310
55536
  editText = markdownToHtml(editRawText);
55311
55537
  } else if (editFormat === "markdownv2") {
55312
55538
  editParseMode = "MarkdownV2";
55313
- editText = escapeMarkdownV22(editRawText);
55539
+ editText = escapeMarkdownV2(editRawText);
55314
55540
  } else {
55315
55541
  editParseMode = undefined;
55316
55542
  editText = editRawText;
@@ -55497,6 +55723,14 @@ function closeProgressLane(chatId, threadId) {
55497
55723
  }
55498
55724
  }
55499
55725
  }
55726
+ var FOREGROUND_SUBAGENT_ACCUM_MAX = 12;
55727
+ function composeTurnActivity(turn) {
55728
+ const childLines = [];
55729
+ for (const narrative of turn.foregroundSubAgents.values()) {
55730
+ childLines.push(...narrative);
55731
+ }
55732
+ return renderActivityFeedWithNested(turn.mirrorLines, childLines);
55733
+ }
55500
55734
  async function drainActivitySummary(turn) {
55501
55735
  try {
55502
55736
  while (turn.activityPendingRender !== turn.activityLastSentRender) {
@@ -55589,6 +55823,7 @@ function handleSessionEvent(ev) {
55589
55823
  activityPendingRender: null,
55590
55824
  activityLastSentRender: null,
55591
55825
  mirrorLines: [],
55826
+ foregroundSubAgents: new Map,
55592
55827
  answerStream: null,
55593
55828
  isDm: isDmChatId(ev.chatId)
55594
55829
  };
@@ -55683,7 +55918,7 @@ function handleSessionEvent(ev) {
55683
55918
  return;
55684
55919
  const rendered = appendActivityLabel(turn.mirrorLines, ev.label);
55685
55920
  if (rendered != null) {
55686
- turn.activityPendingRender = rendered;
55921
+ turn.activityPendingRender = composeTurnActivity(turn) ?? rendered;
55687
55922
  if (turn.activityInFlight == null) {
55688
55923
  turn.activityInFlight = drainActivitySummary(turn);
55689
55924
  }
@@ -57106,14 +57341,14 @@ function restartMarkerPath() {
57106
57341
  const agentDir = resolveAgentDirFromEnv();
57107
57342
  if (!agentDir)
57108
57343
  return null;
57109
- return join36(agentDir, "restart-pending.json");
57344
+ return join35(agentDir, "restart-pending.json");
57110
57345
  }
57111
57346
  function writeRestartMarker(marker) {
57112
57347
  const p = restartMarkerPath();
57113
57348
  if (!p)
57114
57349
  return;
57115
57350
  try {
57116
- writeFileSync25(p, JSON.stringify(marker));
57351
+ writeFileSync24(p, JSON.stringify(marker));
57117
57352
  lastPlannedRestartAt = Date.now();
57118
57353
  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
57354
  `);
@@ -57132,7 +57367,7 @@ function readRestartMarker() {
57132
57367
  if (!p)
57133
57368
  return null;
57134
57369
  try {
57135
- return JSON.parse(readFileSync36(p, "utf8"));
57370
+ return JSON.parse(readFileSync35(p, "utf8"));
57136
57371
  } catch {
57137
57372
  return null;
57138
57373
  }
@@ -57230,7 +57465,7 @@ var _dockerReachable;
57230
57465
  function isDockerReachable() {
57231
57466
  if (_dockerReachable !== undefined)
57232
57467
  return _dockerReachable;
57233
- if (!existsSync39("/var/run/docker.sock")) {
57468
+ if (!existsSync38("/var/run/docker.sock")) {
57234
57469
  _dockerReachable = false;
57235
57470
  return _dockerReachable;
57236
57471
  }
@@ -57247,12 +57482,12 @@ function _resetDockerReachableCache() {
57247
57482
  }
57248
57483
  function spawnSwitchroomDetached(args, onFailure) {
57249
57484
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
57250
- const logPath = join36(STATE_DIR, "detached-spawn.log");
57485
+ const logPath = join35(STATE_DIR, "detached-spawn.log");
57251
57486
  let outFd = null;
57252
57487
  try {
57253
57488
  mkdirSync26(STATE_DIR, { recursive: true });
57254
57489
  outFd = openSync8(logPath, "a");
57255
- writeFileSync25(logPath, `
57490
+ writeFileSync24(logPath, `
57256
57491
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
57257
57492
  `, { flag: "a" });
57258
57493
  } catch {}
@@ -57278,7 +57513,7 @@ function spawnSwitchroomDetached(args, onFailure) {
57278
57513
  return;
57279
57514
  let tail = "";
57280
57515
  try {
57281
- const full = readFileSync36(logPath, "utf8");
57516
+ const full = readFileSync35(logPath, "utf8");
57282
57517
  tail = full.split(`
57283
57518
  `).slice(-30).join(`
57284
57519
  `).trim();
@@ -57620,10 +57855,10 @@ bot.use(async (ctx, next) => {
57620
57855
  });
57621
57856
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
57622
57857
  try {
57623
- const auditPath = join36(homedir14(), ".switchroom", "vault-audit.log");
57624
- if (!existsSync39(auditPath))
57858
+ const auditPath = join35(homedir14(), ".switchroom", "vault-audit.log");
57859
+ if (!existsSync38(auditPath))
57625
57860
  return [];
57626
- const raw = readFileSync36(auditPath, "utf8");
57861
+ const raw = readFileSync35(auditPath, "utf8");
57627
57862
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
57628
57863
  } catch {
57629
57864
  return [];
@@ -57674,7 +57909,7 @@ async function buildAgentMetadata(agentName3) {
57674
57909
  try {
57675
57910
  const agentDir = resolveAgentDirFromEnv();
57676
57911
  if (agentDir) {
57677
- const raw = readFileSync36(join36(agentDir, ".claude", ".claude.json"), "utf8");
57912
+ const raw = readFileSync35(join35(agentDir, ".claude", ".claude.json"), "utf8");
57678
57913
  claudeJson = JSON.parse(raw);
57679
57914
  }
57680
57915
  } catch {}
@@ -57888,10 +58123,10 @@ bot.command("restart", async (ctx) => {
57888
58123
  function flushAgentHandoff(agentDir) {
57889
58124
  let removed = 0;
57890
58125
  for (const fname of [".handoff.md", ".handoff-topic"]) {
57891
- const p = join36(agentDir, fname);
58126
+ const p = join35(agentDir, fname);
57892
58127
  try {
57893
- if (existsSync39(p)) {
57894
- unlinkSync15(p);
58128
+ if (existsSync38(p)) {
58129
+ unlinkSync14(p);
57895
58130
  removed++;
57896
58131
  }
57897
58132
  } catch (err) {
@@ -57946,7 +58181,7 @@ async function handleNewOrResetCommand(ctx, kind) {
57946
58181
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
57947
58182
  if (agentDir != null) {
57948
58183
  try {
57949
- writeFileSync25(join36(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
58184
+ writeFileSync24(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
57950
58185
  `, "utf8");
57951
58186
  } catch (err) {
57952
58187
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -58306,16 +58541,16 @@ bot.command("interrupt", async (ctx) => {
58306
58541
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
58307
58542
  });
58308
58543
  var lockoutOps = {
58309
- readFileSync: (p, enc) => readFileSync36(p, enc),
58310
- writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
58311
- existsSync: (p) => existsSync39(p),
58544
+ readFileSync: (p, enc) => readFileSync35(p, enc),
58545
+ writeFileSync: (p, data, opts) => writeFileSync24(p, data, opts),
58546
+ existsSync: (p) => existsSync38(p),
58312
58547
  mkdirSync: (p, opts) => mkdirSync26(p, opts),
58313
- joinPath: (...parts) => join36(...parts)
58548
+ joinPath: (...parts) => join35(...parts)
58314
58549
  };
58315
58550
  var FLEET_FALLBACK_DEDUP_MS = 30000;
58316
58551
  function isAuthBrokerSocketReachable() {
58317
58552
  try {
58318
- return existsSync39(resolveAuthBrokerSocketPath2());
58553
+ return existsSync38(resolveAuthBrokerSocketPath2());
58319
58554
  } catch {
58320
58555
  return false;
58321
58556
  }
@@ -58376,7 +58611,7 @@ async function runCreditWatch() {
58376
58611
  if (!agentDir)
58377
58612
  return;
58378
58613
  const agentName3 = getMyAgentName();
58379
- const claudeConfigDir = join36(agentDir, ".claude");
58614
+ const claudeConfigDir = join35(agentDir, ".claude");
58380
58615
  const stateDir = STATE_DIR;
58381
58616
  const reason = readClaudeJsonOverage(claudeConfigDir);
58382
58617
  const prev = loadCreditState(stateDir);
@@ -58679,10 +58914,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
58679
58914
  return;
58680
58915
  }
58681
58916
  const { token, id } = result;
58682
- const tokenPath = join36(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58917
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58683
58918
  try {
58684
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58685
- writeFileSync25(tokenPath, token, { mode: 384 });
58919
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58920
+ writeFileSync24(tokenPath, token, { mode: 384 });
58686
58921
  } catch (err) {
58687
58922
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58688
58923
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -58758,10 +58993,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
58758
58993
  return;
58759
58994
  }
58760
58995
  const { token, id } = result;
58761
- const tokenPath = join36(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58996
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58762
58997
  try {
58763
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58764
- writeFileSync25(tokenPath, token, { mode: 384 });
58998
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58999
+ writeFileSync24(tokenPath, token, { mode: 384 });
58765
59000
  } catch (err) {
58766
59001
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58767
59002
  <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 +59471,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
59236
59471
  return;
59237
59472
  }
59238
59473
  const { token, id } = result;
59239
- const tokenPath = join36(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59474
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59240
59475
  try {
59241
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59242
- writeFileSync25(tokenPath, token, { mode: 384 });
59476
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59477
+ writeFileSync24(tokenPath, token, { mode: 384 });
59243
59478
  } catch (err) {
59244
59479
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
59245
59480
  return;
@@ -60087,7 +60322,7 @@ bot.command("usage", async (ctx) => {
60087
60322
  await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
60088
60323
  return;
60089
60324
  }
60090
- const result = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
60325
+ const result = await fetchQuota2({ claudeConfigDir: join35(agentDir, ".claude") });
60091
60326
  if (!result.ok) {
60092
60327
  await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
60093
60328
  return;
@@ -60527,7 +60762,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
60527
60762
  const unifiedDiff = (() => {
60528
60763
  try {
60529
60764
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
60530
- const raw = readFileSync36(cfgPath, "utf8");
60765
+ const raw = readFileSync35(cfgPath, "utf8");
60531
60766
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
60532
60767
  } catch (err) {
60533
60768
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -60669,7 +60904,7 @@ bot.on("message:photo", async (ctx) => {
60669
60904
  });
60670
60905
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
60671
60906
  assertInsideInbox(INBOX_DIR, dlPath);
60672
- writeFileSync25(dlPath, buf, { mode: 384 });
60907
+ writeFileSync24(dlPath, buf, { mode: 384 });
60673
60908
  return dlPath;
60674
60909
  } catch (err) {
60675
60910
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -60709,8 +60944,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
60709
60944
  let apiKey = null;
60710
60945
  try {
60711
60946
  const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
60712
- if (existsSync39(path)) {
60713
- apiKey = readFileSync36(path, "utf-8").trim();
60947
+ if (existsSync38(path)) {
60948
+ apiKey = readFileSync35(path, "utf-8").trim();
60714
60949
  }
60715
60950
  } catch (err) {
60716
60951
  process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
@@ -61345,7 +61580,6 @@ process.on("SIGINT", () => void shutdown("SIGINT"));
61345
61580
  `) });
61346
61581
  }
61347
61582
  }
61348
- initHandoffContinuity();
61349
61583
  process.on("unhandledRejection", (err) => {
61350
61584
  const action = classifyRejection(err);
61351
61585
  process.stderr.write(`telegram gateway: unhandled rejection (${action}): ${err}
@@ -61566,7 +61800,7 @@ var didOneTimeSetup = false;
61566
61800
  return;
61567
61801
  }
61568
61802
  })();
61569
- const resolvedAgentDirForBootCard = agentDir ?? join36(homedir14(), ".switchroom", "agents", agentSlug);
61803
+ const resolvedAgentDirForBootCard = agentDir ?? join35(homedir14(), ".switchroom", "agents", agentSlug);
61570
61804
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
61571
61805
  agentName: agentDisplayName,
61572
61806
  agentSlug,
@@ -61580,7 +61814,7 @@ var didOneTimeSetup = false;
61580
61814
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
61581
61815
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
61582
61816
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
61583
- configSnapshotPath: join36(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61817
+ configSnapshotPath: join35(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61584
61818
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
61585
61819
  }, ackMsgId);
61586
61820
  activeBootCard = handle;
@@ -61665,6 +61899,7 @@ var didOneTimeSetup = false;
61665
61899
  const watcherAgentDir = resolveAgentDirFromEnv();
61666
61900
  if (watcherAgentDir != null) {
61667
61901
  const workerFeedEnabled = isWorkerActivityFeedEnabled(process.env.SWITCHROOM_WORKER_ACTIVITY_FEED);
61902
+ const foregroundNestingEnabled = process.env.SWITCHROOM_FOREGROUND_SUBAGENT_NESTING !== "0";
61668
61903
  const workerActivityFeed = createWorkerActivityFeed({
61669
61904
  bot: {
61670
61905
  sendMessage: async (cid, text, sendOpts) => {
@@ -61724,6 +61959,19 @@ var didOneTimeSetup = false;
61724
61959
  } catch {}
61725
61960
  }
61726
61961
  const isBackground = dispatch.isBackground;
61962
+ if (!isBackground) {
61963
+ const turn = currentTurn;
61964
+ if (turn != null && turn.foregroundSubAgents.delete(agentId) && !turn.replyCalled) {
61965
+ const rendered = composeTurnActivity(turn);
61966
+ if (rendered != null) {
61967
+ turn.activityPendingRender = rendered;
61968
+ if (turn.activityInFlight == null) {
61969
+ turn.activityInFlight = drainActivitySummary(turn);
61970
+ }
61971
+ }
61972
+ }
61973
+ return;
61974
+ }
61727
61975
  if (workerFeedEnabled) {
61728
61976
  workerActivityFeed.finish(agentId, {
61729
61977
  description: dispatch.feedDescription,
@@ -61784,8 +62032,35 @@ var didOneTimeSetup = false;
61784
62032
  } catch {}
61785
62033
  }
61786
62034
  const isBackground = dispatch.isBackground;
61787
- if (!isBackground)
62035
+ if (!isBackground) {
62036
+ if (!foregroundNestingEnabled)
62037
+ return;
62038
+ const turn = currentTurn;
62039
+ if (turn == null || turn.replyCalled)
62040
+ return;
62041
+ const child = latestSummary.trim().slice(0, 120);
62042
+ if (child.length === 0)
62043
+ return;
62044
+ let narrative = turn.foregroundSubAgents.get(agentId);
62045
+ if (narrative == null) {
62046
+ narrative = [];
62047
+ turn.foregroundSubAgents.set(agentId, narrative);
62048
+ }
62049
+ if (narrative[narrative.length - 1] !== child) {
62050
+ narrative.push(child);
62051
+ if (narrative.length > FOREGROUND_SUBAGENT_ACCUM_MAX) {
62052
+ narrative.splice(0, narrative.length - FOREGROUND_SUBAGENT_ACCUM_MAX);
62053
+ }
62054
+ }
62055
+ const rendered = composeTurnActivity(turn);
62056
+ if (rendered != null) {
62057
+ turn.activityPendingRender = rendered;
62058
+ if (turn.activityInFlight == null) {
62059
+ turn.activityInFlight = drainActivitySummary(turn);
62060
+ }
62061
+ }
61788
62062
  return;
62063
+ }
61789
62064
  if (workerFeedEnabled) {
61790
62065
  workerActivityFeed.update(agentId, fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
61791
62066
  description: dispatch.feedDescription,