switchroom 0.14.20 → 0.14.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/agent-scheduler/index.js +2 -3
  2. package/dist/auth-broker/index.js +2 -3
  3. package/dist/cli/notion-write-pretool.mjs +2 -3
  4. package/dist/cli/switchroom.js +16 -8
  5. package/dist/host-control/main.js +2 -3
  6. package/dist/vault/approvals/kernel-server.js +2 -3
  7. package/dist/vault/broker/server.js +2 -3
  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 +655 -514
  15. package/telegram-plugin/gateway/coalesce-attachments.ts +9 -0
  16. package/telegram-plugin/gateway/gateway.ts +246 -83
  17. package/telegram-plugin/gateway/inbound-spool.ts +15 -0
  18. package/telegram-plugin/gateway/interrupt-defer.ts +6 -0
  19. package/telegram-plugin/gateway/resume-inbound-builder.ts +180 -0
  20. package/telegram-plugin/registry/turns-schema.ts +138 -33
  21. package/telegram-plugin/stream-reply-handler.ts +1 -11
  22. package/telegram-plugin/tests/agent-dir.test.ts +25 -0
  23. package/telegram-plugin/tests/coalesce-attachments.test.ts +24 -6
  24. package/telegram-plugin/tests/e2e.test.ts +2 -77
  25. package/telegram-plugin/tests/inbound-spool.test.ts +45 -0
  26. package/telegram-plugin/tests/interrupt-defer.test.ts +13 -0
  27. package/telegram-plugin/tests/multi-turn-continuity.test.ts +0 -1
  28. package/telegram-plugin/tests/outbound-ordering.test.ts +0 -1
  29. package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -1
  30. package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +86 -0
  31. package/telegram-plugin/tests/races.test.ts +0 -26
  32. package/telegram-plugin/tests/registry-turns.test.ts +106 -29
  33. package/telegram-plugin/tests/resume-inbound-builder.test.ts +182 -0
  34. package/telegram-plugin/tests/status-accent.test.ts +0 -1
  35. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +0 -1
  36. package/telegram-plugin/tests/stream-reply-handler.test.ts +0 -24
  37. package/telegram-plugin/tests/streaming-e2e.test.ts +0 -1
  38. package/telegram-plugin/tests/streaming-orchestration.test.ts +0 -1
  39. package/telegram-plugin/tests/tool-activity-summary.test.ts +44 -0
  40. package/telegram-plugin/tests/turns-writer.test.ts +16 -6
  41. package/telegram-plugin/tests/worker-activity-feed.test.ts +14 -0
  42. package/telegram-plugin/tool-activity-summary.ts +55 -0
  43. package/telegram-plugin/uat/assertions.ts +53 -0
  44. package/telegram-plugin/uat/driver.ts +30 -0
  45. package/telegram-plugin/uat/feed-matcher.test.ts +80 -0
  46. package/telegram-plugin/uat/fixtures/album/blue.jpg +0 -0
  47. package/telegram-plugin/uat/fixtures/album/green.jpg +0 -0
  48. package/telegram-plugin/uat/fixtures/album/red.jpg +0 -0
  49. package/telegram-plugin/uat/scenarios/jtbd-album-coalescing-dm.test.ts +136 -0
  50. package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +17 -2
  51. package/telegram-plugin/worker-activity-feed.ts +11 -5
  52. package/telegram-plugin/handoff-continuity.ts +0 -206
  53. 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.")
@@ -23739,10 +23738,10 @@ var init_schema = __esm(() => {
23739
23738
  }).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default \u2014 content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
23740
23739
  coalesce: exports_external.object({
23741
23740
  window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
23742
- max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 1 \u2014 a second photo/document/voice within the " + "coalesce window (or an album / media_group_id) starts its own turn, " + "preserving the historical single-attachment behaviour. Raise to let " + "a forwarded album or a text+multi-image burst arrive as one turn; " + "the agent then sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Excess attachments beyond the cap spill into the " + "next turn. Each attachment is downloaded, so a high cap on a slow " + "link delays turn start.")
23741
+ max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 \u2014 a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
23743
23742
  }).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
23744
23743
  interrupt: exports_external.object({
23745
- safe_boundary: exports_external.boolean().optional().describe("When true, a `!`-prefix interrupt that arrives while the agent is " + "mid-tool-call is DEFERRED: the SIGINT and the replacement turn wait " + "until the in-flight tool call finishes (a clean boundary) instead of " + "C-c'ing the agent mid-write/mid-bash. If no tool is in flight the " + "interrupt still fires immediately. Bounded by max_wait_ms so a long " + "tool never strands the user. Default false \u2014 the interrupt fires " + "synchronously the moment `!` is received (historical behaviour). " + "Rapid repeated `!` while one is pending coalesce into a single " + "deferred interrupt carrying the latest body."),
23744
+ safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
23746
23745
  max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short \u2014 the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
23747
23746
  }).optional().describe("Interrupt timing \u2014 how a `!`-prefix interrupt behaves when it lands " + "mid-tool-call. Off by default (fire immediately). Cascades from " + "defaults.channels.telegram.interrupt."),
23748
23747
  webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default \u2014 webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 \u2014 see #577.)"),
@@ -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";
@@ -31074,6 +31073,9 @@ function resolveInterruptMaxWaitMs(configured) {
31074
31073
  return configured;
31075
31074
  return DEFAULT_INTERRUPT_MAX_WAIT_MS;
31076
31075
  }
31076
+ function resolveSafeBoundaryEnabled(configured) {
31077
+ return configured !== false;
31078
+ }
31077
31079
 
31078
31080
  // sticker-aliases.ts
31079
31081
  function looksLikeFileId(s) {
@@ -31613,6 +31615,10 @@ function inboundCoalesceKey(chatId, threadId, userId) {
31613
31615
  }
31614
31616
 
31615
31617
  // gateway/coalesce-attachments.ts
31618
+ var DEFAULT_MAX_ATTACHMENTS = 10;
31619
+ function resolveCoalesceMaxAttachments(configured) {
31620
+ return Math.max(1, configured ?? DEFAULT_MAX_ATTACHMENTS);
31621
+ }
31616
31622
  function splitCoalescedAttachments(entries, hasAttachment, maxAttachments) {
31617
31623
  const withAttachment = entries.filter(hasAttachment);
31618
31624
  const capped = withAttachment.slice(0, Math.max(1, maxAttachments));
@@ -31956,6 +31962,9 @@ class DeferredDoneReactions {
31956
31962
  }
31957
31963
 
31958
31964
  // worker-activity-feed.ts
31965
+ function isWorkerActivityFeedEnabled(envVal) {
31966
+ return envVal !== "0";
31967
+ }
31959
31968
  var DESC_MAX = 80;
31960
31969
  var TOOL_ARG_MAX = 64;
31961
31970
  var SUMMARY_MAX = 100;
@@ -32172,6 +32181,33 @@ function renderActivityFeed(lines) {
32172
32181
  return out.join(`
32173
32182
  `);
32174
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
+ }
32175
32211
  function appendActivityLabel(lines, label) {
32176
32212
  const l = (label ?? "").trim();
32177
32213
  if (l.length === 0)
@@ -33188,11 +33224,6 @@ async function handleStreamReply(args, state, deps) {
33188
33224
  done,
33189
33225
  streamExisted
33190
33226
  });
33191
- if (!stream) {
33192
- const prefix = deps.takeHandoffPrefix(format === "html" ? "html" : format === "markdownv2" ? "markdownv2" : "text");
33193
- if (prefix.length > 0)
33194
- effectiveText = prefix + effectiveText;
33195
- }
33196
33227
  if (!stream) {
33197
33228
  let replyToMessageId;
33198
33229
  if (args.reply_to != null) {
@@ -42119,125 +42150,29 @@ function isTurnFlushSafetyEnabled(env = process.env) {
42119
42150
  return true;
42120
42151
  }
42121
42152
 
42122
- // handoff-continuity.ts
42123
- import { readFileSync as readFileSync9, unlinkSync as unlinkSync2, existsSync as existsSync13, writeFileSync as writeFileSync6, renameSync as renameSync2 } from "node:fs";
42124
- import { dirname as dirname7, join as join13 } from "node:path";
42125
- var TOPIC_DISPLAY_MAX = 117;
42126
- var HANDOFF_TOPIC_FILENAME = ".handoff-topic";
42127
- var LAST_TURN_SUMMARY_FILENAME = ".last-turn-summary";
42153
+ // agent-dir.ts
42154
+ import { dirname as dirname7 } from "node:path";
42128
42155
  function resolveAgentDirFromEnv() {
42129
42156
  const state3 = process.env.TELEGRAM_STATE_DIR;
42130
42157
  if (!state3 || state3.trim().length === 0)
42131
42158
  return null;
42132
42159
  return dirname7(state3);
42133
42160
  }
42134
- function readHandoffTopic(agentDir) {
42135
- const p = join13(agentDir, HANDOFF_TOPIC_FILENAME);
42136
- if (!existsSync13(p))
42137
- return null;
42138
- let raw;
42139
- try {
42140
- raw = readFileSync9(p, "utf-8");
42141
- } catch {
42142
- return null;
42143
- }
42144
- const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
42145
- if (lines.length === 0)
42146
- return null;
42147
- let topic = lines[0];
42148
- if (topic.length > TOPIC_DISPLAY_MAX) {
42149
- topic = topic.slice(0, TOPIC_DISPLAY_MAX) + "\u2026";
42150
- }
42151
- return topic;
42152
- }
42153
- function readLastTurnSummary(agentDir) {
42154
- const p = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
42155
- if (!existsSync13(p))
42156
- return null;
42157
- let raw;
42158
- try {
42159
- raw = readFileSync9(p, "utf-8");
42160
- } catch {
42161
- return null;
42162
- }
42163
- const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
42164
- if (lines.length === 0)
42165
- return null;
42166
- let topic = lines[0];
42167
- if (topic.length > TOPIC_DISPLAY_MAX) {
42168
- topic = topic.slice(0, TOPIC_DISPLAY_MAX) + "\u2026";
42169
- }
42170
- return topic;
42171
- }
42172
- function consumeHandoffTopic(agentDir) {
42173
- const primary = readHandoffTopic(agentDir);
42174
- const primaryPath = join13(agentDir, HANDOFF_TOPIC_FILENAME);
42175
- const fallbackPath = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
42176
- const removeFallback = () => {
42177
- try {
42178
- unlinkSync2(fallbackPath);
42179
- } catch {}
42180
- };
42181
- if (primary !== null) {
42182
- try {
42183
- unlinkSync2(primaryPath);
42184
- } catch {}
42185
- removeFallback();
42186
- return primary;
42187
- }
42188
- const fallback = readLastTurnSummary(agentDir);
42189
- if (fallback !== null) {
42190
- removeFallback();
42191
- return fallback;
42192
- }
42193
- return null;
42194
- }
42195
- function shouldShowHandoffLine() {
42196
- const v = process.env.SWITCHROOM_HANDOFF_SHOW_LINE;
42197
- if (v === undefined)
42198
- return true;
42199
- return v.toLowerCase() !== "false";
42200
- }
42201
- function formatHandoffLine(topic, format) {
42202
- const prefix = "\u21a9\ufe0f Picked up where we left off, ";
42203
- if (format === "html") {
42204
- return `<i>${prefix}${escapeHtml7(topic)}</i>
42205
-
42206
- `;
42207
- }
42208
- if (format === "markdownv2") {
42209
- const escaped = escapeMarkdownV2(topic);
42210
- const prefixEsc = escapeMarkdownV2(prefix);
42211
- return `_${prefixEsc}${escaped}_
42212
-
42213
- `;
42214
- }
42215
- return `${prefix}${topic}
42216
-
42217
- `;
42218
- }
42219
- function escapeHtml7(s) {
42220
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42221
- }
42222
- var MDV2_SPECIALS = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
42223
- function escapeMarkdownV2(s) {
42224
- return s.replace(MDV2_SPECIALS, (m) => "\\" + m);
42225
- }
42226
42161
 
42227
42162
  // active-reactions.ts
42228
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync3, existsSync as existsSync14, unlinkSync as unlinkSync3 } from "node:fs";
42229
- 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";
42230
42165
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
42231
42166
  function reactionsPath(agentDir) {
42232
- return join14(agentDir, ACTIVE_REACTIONS_FILENAME);
42167
+ return join13(agentDir, ACTIVE_REACTIONS_FILENAME);
42233
42168
  }
42234
42169
  function readActiveReactions(agentDir) {
42235
42170
  const p = reactionsPath(agentDir);
42236
- if (!existsSync14(p))
42171
+ if (!existsSync13(p))
42237
42172
  return [];
42238
42173
  let raw;
42239
42174
  try {
42240
- raw = readFileSync10(p, "utf-8");
42175
+ raw = readFileSync9(p, "utf-8");
42241
42176
  } catch {
42242
42177
  return [];
42243
42178
  }
@@ -42263,15 +42198,15 @@ function writeActiveReactions(agentDir, reactions) {
42263
42198
  const p = reactionsPath(agentDir);
42264
42199
  if (reactions.length === 0) {
42265
42200
  try {
42266
- unlinkSync3(p);
42201
+ unlinkSync2(p);
42267
42202
  } catch {}
42268
42203
  return;
42269
42204
  }
42270
42205
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
42271
42206
  try {
42272
- writeFileSync7(tmp, JSON.stringify(reactions) + `
42207
+ writeFileSync6(tmp, JSON.stringify(reactions) + `
42273
42208
  `, "utf-8");
42274
- renameSync3(tmp, p);
42209
+ renameSync2(tmp, p);
42275
42210
  } catch {}
42276
42211
  }
42277
42212
  function addActiveReaction(agentDir, reaction) {
@@ -42288,24 +42223,24 @@ function removeActiveReaction(agentDir, chatId, messageId) {
42288
42223
  }
42289
42224
  function clearActiveReactions(agentDir) {
42290
42225
  try {
42291
- unlinkSync3(reactionsPath(agentDir));
42226
+ unlinkSync2(reactionsPath(agentDir));
42292
42227
  } catch {}
42293
42228
  }
42294
42229
 
42295
42230
  // active-reactions.ts
42296
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync4, existsSync as existsSync15, unlinkSync as unlinkSync4 } from "node:fs";
42297
- 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";
42298
42233
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
42299
42234
  function reactionsPath2(agentDir) {
42300
- return join15(agentDir, ACTIVE_REACTIONS_FILENAME2);
42235
+ return join14(agentDir, ACTIVE_REACTIONS_FILENAME2);
42301
42236
  }
42302
42237
  function readActiveReactions2(agentDir) {
42303
42238
  const p = reactionsPath2(agentDir);
42304
- if (!existsSync15(p))
42239
+ if (!existsSync14(p))
42305
42240
  return [];
42306
42241
  let raw;
42307
42242
  try {
42308
- raw = readFileSync11(p, "utf-8");
42243
+ raw = readFileSync10(p, "utf-8");
42309
42244
  } catch {
42310
42245
  return [];
42311
42246
  }
@@ -42329,7 +42264,7 @@ function readActiveReactions2(agentDir) {
42329
42264
  }
42330
42265
  function clearActiveReactions2(agentDir) {
42331
42266
  try {
42332
- unlinkSync4(reactionsPath2(agentDir));
42267
+ unlinkSync3(reactionsPath2(agentDir));
42333
42268
  } catch {}
42334
42269
  }
42335
42270
 
@@ -43182,17 +43117,17 @@ async function approvalRecord(args, opts) {
43182
43117
  }
43183
43118
 
43184
43119
  // quota-check.ts
43185
- import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
43186
- import { join as join17 } from "path";
43120
+ import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
43121
+ import { join as join16 } from "path";
43187
43122
  var OAUTH_BETA2 = "oauth-2025-04-20";
43188
43123
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
43189
43124
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
43190
43125
  function readOauthToken2(claudeConfigDir) {
43191
- const tokenFile = join17(claudeConfigDir, ".oauth-token");
43192
- if (!existsSync17(tokenFile))
43126
+ const tokenFile = join16(claudeConfigDir, ".oauth-token");
43127
+ if (!existsSync16(tokenFile))
43193
43128
  return null;
43194
43129
  try {
43195
- const raw = readFileSync13(tokenFile, "utf-8").trim();
43130
+ const raw = readFileSync12(tokenFile, "utf-8").trim();
43196
43131
  return raw.length > 0 ? raw : null;
43197
43132
  } catch {
43198
43133
  return null;
@@ -43788,7 +43723,7 @@ init_schema();
43788
43723
  init_paths();
43789
43724
  init_overlay_loader();
43790
43725
  init_merge();
43791
- import { readFileSync as readFileSync14, existsSync as existsSync18 } from "node:fs";
43726
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "node:fs";
43792
43727
  import { homedir as homedir8 } from "node:os";
43793
43728
  import { resolve as resolve5 } from "node:path";
43794
43729
 
@@ -43864,7 +43799,7 @@ function findConfigFile2(startDir) {
43864
43799
  resolve5(userDir, "clerk.yml")
43865
43800
  ].filter(Boolean);
43866
43801
  for (const path of searchPaths) {
43867
- if (existsSync18(path)) {
43802
+ if (existsSync17(path)) {
43868
43803
  return path;
43869
43804
  }
43870
43805
  }
@@ -43872,12 +43807,12 @@ function findConfigFile2(startDir) {
43872
43807
  }
43873
43808
  function loadConfig2(configPath) {
43874
43809
  const filePath = configPath ?? findConfigFile2();
43875
- if (!existsSync18(filePath)) {
43810
+ if (!existsSync17(filePath)) {
43876
43811
  throw new ConfigError2(`Config file not found: ${filePath}`);
43877
43812
  }
43878
43813
  let raw;
43879
43814
  try {
43880
- raw = readFileSync14(filePath, "utf-8");
43815
+ raw = readFileSync13(filePath, "utf-8");
43881
43816
  } catch (err) {
43882
43817
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
43883
43818
  ` ${err.message}`
@@ -44317,15 +44252,15 @@ function resolveOutboundTopic(config, event) {
44317
44252
  }
44318
44253
 
44319
44254
  // ../src/agents/perf.ts
44320
- import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
44255
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
44321
44256
  function readTurnUsages(jsonlPath, lastN) {
44322
- if (!existsSync19(jsonlPath))
44257
+ if (!existsSync18(jsonlPath))
44323
44258
  return [];
44324
44259
  if (lastN <= 0)
44325
44260
  return [];
44326
44261
  let raw;
44327
44262
  try {
44328
- raw = readFileSync15(jsonlPath, "utf-8");
44263
+ raw = readFileSync14(jsonlPath, "utf-8");
44329
44264
  } catch {
44330
44265
  return [];
44331
44266
  }
@@ -44463,7 +44398,7 @@ function nextCompactNotify(state3, ev) {
44463
44398
  }
44464
44399
 
44465
44400
  // gateway/hostd-dispatch.ts
44466
- import { existsSync as existsSync20 } from "node:fs";
44401
+ import { existsSync as existsSync19 } from "node:fs";
44467
44402
  import { randomBytes as randomBytes3 } from "node:crypto";
44468
44403
 
44469
44404
  // ../src/host-control/client.ts
@@ -44754,13 +44689,13 @@ function hostdSocketPath(agentName3) {
44754
44689
  function hostdWillBeUsed(agentName3) {
44755
44690
  if (!isHostdEnabled())
44756
44691
  return false;
44757
- return existsSync20(hostdSocketPath(agentName3));
44692
+ return existsSync19(hostdSocketPath(agentName3));
44758
44693
  }
44759
44694
  async function tryHostdDispatch(agentName3, req, timeoutMs = 5000) {
44760
44695
  if (!isHostdEnabled())
44761
44696
  return "not-configured";
44762
44697
  const sockPath = hostdSocketPath(agentName3);
44763
- if (!existsSync20(sockPath))
44698
+ if (!existsSync19(sockPath))
44764
44699
  return "not-configured";
44765
44700
  try {
44766
44701
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -44784,7 +44719,7 @@ async function hostdGetStatusOnce(agentName3, targetRequestId) {
44784
44719
  if (!isHostdEnabled())
44785
44720
  return "not-configured";
44786
44721
  const sockPath = hostdSocketPath(agentName3);
44787
- if (!existsSync20(sockPath))
44722
+ if (!existsSync19(sockPath))
44788
44723
  return "not-configured";
44789
44724
  try {
44790
44725
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -44805,7 +44740,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
44805
44740
  if (!isHostdEnabled())
44806
44741
  return "not-configured";
44807
44742
  const sockPath = hostdSocketPath(agentName3);
44808
- if (!existsSync20(sockPath))
44743
+ if (!existsSync19(sockPath))
44809
44744
  return "not-configured";
44810
44745
  const now = opts.now ?? Date.now;
44811
44746
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -44884,7 +44819,7 @@ function shouldSweepChatAtBoot(chatId) {
44884
44819
 
44885
44820
  // gateway/webhook-ingest-server.ts
44886
44821
  import net4 from "node:net";
44887
- 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";
44888
44823
  var MAX_REQUEST_BYTES = 1024 * 1024;
44889
44824
  function fdOf(conn) {
44890
44825
  const handle = conn._handle;
@@ -44896,8 +44831,8 @@ function startWebhookIngestServer(opts) {
44896
44831
  const log = opts.log ?? ((s) => process.stderr.write(s));
44897
44832
  const allowed = new Set(opts.allowedUids);
44898
44833
  try {
44899
- if (existsSync21(opts.socketPath))
44900
- unlinkSync5(opts.socketPath);
44834
+ if (existsSync20(opts.socketPath))
44835
+ unlinkSync4(opts.socketPath);
44901
44836
  } catch (err) {
44902
44837
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
44903
44838
  `);
@@ -44989,8 +44924,8 @@ function startWebhookIngestServer(opts) {
44989
44924
  server.close();
44990
44925
  } catch {}
44991
44926
  try {
44992
- if (existsSync21(opts.socketPath))
44993
- unlinkSync5(opts.socketPath);
44927
+ if (existsSync20(opts.socketPath))
44928
+ unlinkSync4(opts.socketPath);
44994
44929
  } catch {}
44995
44930
  }
44996
44931
  };
@@ -44998,19 +44933,19 @@ function startWebhookIngestServer(opts) {
44998
44933
 
44999
44934
  // ../src/web/webhook-gateway-record.ts
45000
44935
  import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync12 } from "fs";
45001
- import { join as join20 } from "path";
44936
+ import { join as join19 } from "path";
45002
44937
  import { homedir as homedir10 } from "os";
45003
44938
 
45004
44939
  // ../src/web/webhook-handler.ts
45005
- import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
45006
- 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";
45007
44942
  var DEDUP_MAX = 1000;
45008
44943
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
45009
44944
  function loadDedupFile(path) {
45010
44945
  try {
45011
- if (!existsSync22(path))
44946
+ if (!existsSync21(path))
45012
44947
  return {};
45013
- const raw = JSON.parse(readFileSync16(path, "utf-8"));
44948
+ const raw = JSON.parse(readFileSync15(path, "utf-8"));
45014
44949
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
45015
44950
  } catch {
45016
44951
  return {};
@@ -45024,7 +44959,7 @@ function saveDedupFile(path, deliveries, now) {
45024
44959
  }
45025
44960
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
45026
44961
  const final = Object.fromEntries(sorted);
45027
- writeFileSync9(path, JSON.stringify({ deliveries: final }), {
44962
+ writeFileSync8(path, JSON.stringify({ deliveries: final }), {
45028
44963
  mode: 384
45029
44964
  });
45030
44965
  }
@@ -45032,8 +44967,8 @@ var agentDedupCache = new Map;
45032
44967
  function createFileDedupStore(resolveAgentDir) {
45033
44968
  return {
45034
44969
  check(agent, deliveryId, now) {
45035
- const telegramDir = join18(resolveAgentDir(agent), "telegram");
45036
- const filePath = join18(telegramDir, "webhook-dedup.json");
44970
+ const telegramDir = join17(resolveAgentDir(agent), "telegram");
44971
+ const filePath = join17(telegramDir, "webhook-dedup.json");
45037
44972
  if (!agentDedupCache.has(agent)) {
45038
44973
  agentDedupCache.set(agent, loadDedupFile(filePath));
45039
44974
  }
@@ -45054,8 +44989,8 @@ var tokenBuckets = new Map;
45054
44989
  var throttleIssueWindow = new Map;
45055
44990
 
45056
44991
  // ../src/web/webhook-dispatch.ts
45057
- import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
45058
- 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";
45059
44994
  import { homedir as homedir9 } from "os";
45060
44995
 
45061
44996
  // ../src/agent-scheduler/ipc-client.ts
@@ -45266,9 +45201,9 @@ function cooldownKey(eventType, repo, number, ruleIndex) {
45266
45201
  }
45267
45202
  function loadCooldownFile(path) {
45268
45203
  try {
45269
- if (!existsSync23(path))
45204
+ if (!existsSync22(path))
45270
45205
  return {};
45271
- const raw = JSON.parse(readFileSync17(path, "utf-8"));
45206
+ const raw = JSON.parse(readFileSync16(path, "utf-8"));
45272
45207
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
45273
45208
  } catch {
45274
45209
  return {};
@@ -45276,7 +45211,7 @@ function loadCooldownFile(path) {
45276
45211
  }
45277
45212
  function saveCooldownFile(path, dispatches) {
45278
45213
  try {
45279
- writeFileSync10(path, JSON.stringify({ dispatches }), {
45214
+ writeFileSync9(path, JSON.stringify({ dispatches }), {
45280
45215
  mode: 384
45281
45216
  });
45282
45217
  } catch {}
@@ -45287,8 +45222,8 @@ function createFileCooldownStore(resolveAgentDir) {
45287
45222
  isCoolingDown(agent, key, cooldownMs, now) {
45288
45223
  if (cooldownMs <= 0)
45289
45224
  return false;
45290
- const telegramDir = join19(resolveAgentDir(agent), "telegram");
45291
- const filePath = join19(telegramDir, "webhook-cooldown.json");
45225
+ const telegramDir = join18(resolveAgentDir(agent), "telegram");
45226
+ const filePath = join18(telegramDir, "webhook-cooldown.json");
45292
45227
  if (!cache.has(agent)) {
45293
45228
  cache.set(agent, loadCooldownFile(filePath));
45294
45229
  }
@@ -45345,9 +45280,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
45345
45280
  }
45346
45281
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
45347
45282
  const log = deps.log ?? ((s) => process.stderr.write(s));
45348
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45283
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join18(homedir9(), ".switchroom", "agents", a));
45349
45284
  const now = (deps.now ?? Date.now)();
45350
- const socketPath = join19(resolveAgentDir(agent), "telegram", "gateway.sock");
45285
+ const socketPath = join18(resolveAgentDir(agent), "telegram", "gateway.sock");
45351
45286
  const inbound = {
45352
45287
  type: "inbound",
45353
45288
  chatId: ctx.chatId,
@@ -45372,7 +45307,7 @@ function evaluateDispatch(args, deps = {}) {
45372
45307
  const log = deps.log ?? ((s) => process.stderr.write(s));
45373
45308
  const now = (deps.now ?? Date.now)();
45374
45309
  const nowDate = deps.nowDate ?? (() => new Date(now));
45375
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45310
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join18(homedir9(), ".switchroom", "agents", a));
45376
45311
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
45377
45312
  if (args.source !== "github")
45378
45313
  return 0;
@@ -45448,10 +45383,10 @@ function resolveChannelTarget(config, agentName3) {
45448
45383
  function recordWebhookEvent(rec, deps = {}) {
45449
45384
  const log = deps.log ?? ((s) => process.stderr.write(s));
45450
45385
  const now = rec.ts || (deps.now ?? Date.now)();
45451
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir10(), ".switchroom", "agents", a));
45386
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir10(), ".switchroom", "agents", a));
45452
45387
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
45453
45388
  const agent = rec.agent;
45454
- const telegramDir = join20(resolveAgentDir(agent), "telegram");
45389
+ const telegramDir = join19(resolveAgentDir(agent), "telegram");
45455
45390
  if (rec.source === "github" && rec.delivery_id) {
45456
45391
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
45457
45392
  if (originalTs !== undefined) {
@@ -45460,7 +45395,7 @@ function recordWebhookEvent(rec, deps = {}) {
45460
45395
  return { status: "deduped", ts: originalTs };
45461
45396
  }
45462
45397
  }
45463
- const logPath = join20(telegramDir, "webhook-events.jsonl");
45398
+ const logPath = join19(telegramDir, "webhook-events.jsonl");
45464
45399
  try {
45465
45400
  mkdirSync12(telegramDir, { recursive: true });
45466
45401
  const record = {
@@ -45518,7 +45453,7 @@ function recordWebhookEvent(rec, deps = {}) {
45518
45453
  }
45519
45454
 
45520
45455
  // gateway/ipc-server.ts
45521
- import { renameSync as renameSync5, unlinkSync as unlinkSync6 } from "fs";
45456
+ import { renameSync as renameSync4, unlinkSync as unlinkSync5 } from "fs";
45522
45457
  var MAX_BUFFER_SIZE = 1024 * 1024;
45523
45458
  var VALID_OPERATOR_KINDS = new Set([
45524
45459
  "credentials-expired",
@@ -45633,10 +45568,10 @@ function createIpcServer(options) {
45633
45568
  heartbeatTimeoutMs = 30000
45634
45569
  } = options;
45635
45570
  try {
45636
- renameSync5(socketPath, socketPath + ".bak");
45571
+ renameSync4(socketPath, socketPath + ".bak");
45637
45572
  } catch {}
45638
45573
  try {
45639
- unlinkSync6(socketPath + ".bak");
45574
+ unlinkSync5(socketPath + ".bak");
45640
45575
  } catch {}
45641
45576
  const clients = new Set;
45642
45577
  const agentIndex = new Map;
@@ -45958,7 +45893,7 @@ function createIpcServer(options) {
45958
45893
  clientBySocketId.clear();
45959
45894
  server.stop(true);
45960
45895
  try {
45961
- renameSync5(socketPath, socketPath + ".bak");
45896
+ renameSync4(socketPath, socketPath + ".bak");
45962
45897
  } catch {}
45963
45898
  }
45964
45899
  };
@@ -46384,9 +46319,9 @@ function buildDiffPreviewCard(input) {
46384
46319
  }
46385
46320
  const preview = input.preview;
46386
46321
  const bodyLines = [];
46387
- bodyLines.push(`<b>${escapeHtml8(preview.title)}</b>`);
46322
+ bodyLines.push(`<b>${escapeHtml7(preview.title)}</b>`);
46388
46323
  for (const line of preview.lines) {
46389
- bodyLines.push(escapeHtml8(line.text));
46324
+ bodyLines.push(escapeHtml7(line.text));
46390
46325
  }
46391
46326
  const text = bodyLines.join(`
46392
46327
  `);
@@ -46439,7 +46374,7 @@ function buildDiffPreviewCard(input) {
46439
46374
  }
46440
46375
  return { text, reply_markup: kb };
46441
46376
  }
46442
- function escapeHtml8(s) {
46377
+ function escapeHtml7(s) {
46443
46378
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
46444
46379
  }
46445
46380
 
@@ -46585,6 +46520,9 @@ function spoolId(msg) {
46585
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) {
46586
46521
  return `s:progress:${msg.meta.subagent_jsonl_id}:${msg.meta.bucket_idx}`;
46587
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
+ }
46588
46526
  if (typeof msg.messageId === "number" && msg.messageId > 0) {
46589
46527
  return `m:${msg.chatId}:${msg.messageId}`;
46590
46528
  }
@@ -47818,15 +47756,15 @@ function escapeBody(s) {
47818
47756
  }
47819
47757
 
47820
47758
  // gateway/pid-file.ts
47821
- 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";
47822
47760
  function writePidFile(path, record) {
47823
47761
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
47824
- writeFileSync11(tmp, JSON.stringify(record), "utf-8");
47825
- renameSync6(tmp, path);
47762
+ writeFileSync10(tmp, JSON.stringify(record), "utf-8");
47763
+ renameSync5(tmp, path);
47826
47764
  }
47827
47765
  function clearPidFile(path) {
47828
47766
  try {
47829
- unlinkSync7(path);
47767
+ unlinkSync6(path);
47830
47768
  } catch {}
47831
47769
  }
47832
47770
 
@@ -47837,10 +47775,10 @@ import {
47837
47775
  writeFile as writeFileAsync,
47838
47776
  readFile as readFileAsync
47839
47777
  } from "node:fs/promises";
47840
- import { readFileSync as readFileSync19 } from "node:fs";
47778
+ import { readFileSync as readFileSync18 } from "node:fs";
47841
47779
  function readCurrentBootId() {
47842
47780
  try {
47843
- const stat = readFileSync19("/proc/1/stat", "utf-8");
47781
+ const stat = readFileSync18("/proc/1/stat", "utf-8");
47844
47782
  const lastParen = stat.lastIndexOf(")");
47845
47783
  if (lastParen < 0)
47846
47784
  return null;
@@ -48043,15 +47981,15 @@ function safeCount(fn) {
48043
47981
  }
48044
47982
 
48045
47983
  // gateway/session-marker.ts
48046
- 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";
48047
47985
  function writeSessionMarker(path, marker) {
48048
47986
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
48049
- writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
48050
- renameSync7(tmp, path);
47987
+ writeFileSync11(tmp, JSON.stringify(marker), "utf-8");
47988
+ renameSync6(tmp, path);
48051
47989
  }
48052
47990
  function readSessionMarker(path) {
48053
47991
  try {
48054
- const raw = readFileSync20(path, "utf-8");
47992
+ const raw = readFileSync19(path, "utf-8");
48055
47993
  const parsed = JSON.parse(raw);
48056
47994
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
48057
47995
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -48073,16 +48011,16 @@ function shouldFireRestartBanner(input) {
48073
48011
  }
48074
48012
 
48075
48013
  // gateway/clean-shutdown-marker.ts
48076
- 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";
48077
48015
  var DEFAULT_MAX_AGE_MS = 60000;
48078
48016
  function writeCleanShutdownMarker(path, marker) {
48079
48017
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
48080
- writeFileSync13(tmp, JSON.stringify(marker), "utf-8");
48081
- renameSync8(tmp, path);
48018
+ writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
48019
+ renameSync7(tmp, path);
48082
48020
  }
48083
48021
  function readCleanShutdownMarker(path) {
48084
48022
  try {
48085
- const raw = readFileSync21(path, "utf-8");
48023
+ const raw = readFileSync20(path, "utf-8");
48086
48024
  const parsed = JSON.parse(raw);
48087
48025
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
48088
48026
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -48097,7 +48035,7 @@ function readCleanShutdownMarker(path) {
48097
48035
  }
48098
48036
  function clearCleanShutdownMarker(path) {
48099
48037
  try {
48100
- unlinkSync9(path);
48038
+ unlinkSync8(path);
48101
48039
  } catch {}
48102
48040
  }
48103
48041
  function shouldSuppressRecoveryBanner(marker, now, maxAgeMs = DEFAULT_MAX_AGE_MS) {
@@ -48877,16 +48815,16 @@ function classifyAdminGate(text, myAgentName) {
48877
48815
 
48878
48816
  // subagent-watcher.ts
48879
48817
  import {
48880
- existsSync as existsSync25,
48818
+ existsSync as existsSync24,
48881
48819
  openSync as openSync2,
48882
48820
  readSync,
48883
48821
  statSync as statSync6,
48884
48822
  closeSync as closeSync2,
48885
48823
  watch,
48886
48824
  readdirSync as readdirSync3,
48887
- readFileSync as readFileSync23
48825
+ readFileSync as readFileSync22
48888
48826
  } from "fs";
48889
- import { join as join22 } from "path";
48827
+ import { join as join21 } from "path";
48890
48828
 
48891
48829
  // operator-events.ts
48892
48830
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
@@ -49129,20 +49067,20 @@ function bumpSubagentActivity(db2, args) {
49129
49067
  // gateway/turn-active-marker.ts
49130
49068
  import {
49131
49069
  closeSync,
49132
- existsSync as existsSync24,
49070
+ existsSync as existsSync23,
49133
49071
  mkdirSync as mkdirSync13,
49134
49072
  openSync,
49135
- readFileSync as readFileSync22,
49073
+ readFileSync as readFileSync21,
49136
49074
  statSync as statSync5,
49137
- unlinkSync as unlinkSync10,
49075
+ unlinkSync as unlinkSync9,
49138
49076
  utimesSync,
49139
- writeFileSync as writeFileSync14
49077
+ writeFileSync as writeFileSync13
49140
49078
  } from "node:fs";
49141
- import { join as join21 } from "node:path";
49079
+ import { join as join20 } from "node:path";
49142
49080
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
49143
49081
  function touchTurnActiveMarker(stateDir) {
49144
- const path = join21(stateDir, TURN_ACTIVE_MARKER_FILE);
49145
- if (!existsSync24(path))
49082
+ const path = join20(stateDir, TURN_ACTIVE_MARKER_FILE);
49083
+ if (!existsSync23(path))
49146
49084
  return;
49147
49085
  const now = new Date;
49148
49086
  try {
@@ -49177,7 +49115,7 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
49177
49115
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
49178
49116
  let meta;
49179
49117
  try {
49180
- const raw = readFileSync23(metaPath, "utf8");
49118
+ const raw = readFileSync22(metaPath, "utf8");
49181
49119
  meta = JSON.parse(raw);
49182
49120
  } catch {
49183
49121
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -49366,7 +49304,7 @@ function startSubagentWatcher(config) {
49366
49304
  clearTimeout(ref.ref);
49367
49305
  });
49368
49306
  const fs2 = config.fs ?? {
49369
- existsSync: existsSync25,
49307
+ existsSync: existsSync24,
49370
49308
  readdirSync: readdirSync3,
49371
49309
  statSync: statSync6,
49372
49310
  openSync: openSync2,
@@ -49600,8 +49538,8 @@ function startSubagentWatcher(config) {
49600
49538
  function rescanSubagentDirs() {
49601
49539
  if (stopped)
49602
49540
  return;
49603
- const claudeHome = join22(agentDir, ".claude");
49604
- const projectsRoot = join22(claudeHome, "projects");
49541
+ const claudeHome = join21(agentDir, ".claude");
49542
+ const projectsRoot = join21(claudeHome, "projects");
49605
49543
  if (!fs2.existsSync(projectsRoot))
49606
49544
  return;
49607
49545
  let projectDirs;
@@ -49618,7 +49556,7 @@ function startSubagentWatcher(config) {
49618
49556
  }
49619
49557
  continue;
49620
49558
  }
49621
- const projectPath = join22(projectsRoot, pDir);
49559
+ const projectPath = join21(projectsRoot, pDir);
49622
49560
  let sessionDirs;
49623
49561
  try {
49624
49562
  sessionDirs = fs2.readdirSync(projectPath);
@@ -49628,7 +49566,7 @@ function startSubagentWatcher(config) {
49628
49566
  for (const sDir of sessionDirs) {
49629
49567
  if (sDir.endsWith(".jsonl"))
49630
49568
  continue;
49631
- const subagentsPath = join22(projectPath, sDir, "subagents");
49569
+ const subagentsPath = join21(projectPath, sDir, "subagents");
49632
49570
  if (!fs2.existsSync(subagentsPath))
49633
49571
  continue;
49634
49572
  if (!dirWatchers.has(subagentsPath)) {
@@ -49636,7 +49574,7 @@ function startSubagentWatcher(config) {
49636
49574
  const w = fs2.watch(subagentsPath, (_event, filename) => {
49637
49575
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
49638
49576
  return;
49639
- const filePath = join22(subagentsPath, filename.toString());
49577
+ const filePath = join21(subagentsPath, filename.toString());
49640
49578
  if (!knownFiles.has(filePath)) {
49641
49579
  scanSubagentsDir(subagentsPath);
49642
49580
  }
@@ -49661,7 +49599,7 @@ function startSubagentWatcher(config) {
49661
49599
  for (const e of entries) {
49662
49600
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
49663
49601
  continue;
49664
- const filePath = join22(subagentsPath, e);
49602
+ const filePath = join21(subagentsPath, e);
49665
49603
  if (knownFiles.has(filePath))
49666
49604
  continue;
49667
49605
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -49778,15 +49716,15 @@ function determineRestartReason(opts) {
49778
49716
  init_boot_card();
49779
49717
 
49780
49718
  // gateway/update-announce.ts
49781
- import { existsSync as existsSync30, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync28 } from "node:fs";
49782
- import { join as join27 } from "node:path";
49719
+ import { existsSync as existsSync29, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync27 } from "node:fs";
49720
+ import { join as join26 } from "node:path";
49783
49721
  import { homedir as homedir12 } from "node:os";
49784
49722
 
49785
49723
  // ../src/host-control/audit-reader.ts
49786
49724
  import { homedir as homedir11 } from "node:os";
49787
- import { join as join26 } from "node:path";
49725
+ import { join as join25 } from "node:path";
49788
49726
  function defaultAuditLogPath(home2 = homedir11()) {
49789
- return join26(home2, ".switchroom", "host-control-audit.log");
49727
+ return join25(home2, ".switchroom", "host-control-audit.log");
49790
49728
  }
49791
49729
  function parseAuditLine(line) {
49792
49730
  const trimmed = line.trim();
@@ -49892,8 +49830,8 @@ function readAndFilter(raw, filters, limit) {
49892
49830
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
49893
49831
  function readLastTerminalUpdateAudit(opts = {}) {
49894
49832
  const path = opts.auditLogPath ?? defaultAuditLogPath();
49895
- const exists = opts.exists ?? existsSync30;
49896
- const readFile = opts.readFile ?? ((p) => readFileSync28(p, "utf-8"));
49833
+ const exists = opts.exists ?? existsSync29;
49834
+ const readFile = opts.readFile ?? ((p) => readFileSync27(p, "utf-8"));
49897
49835
  if (!exists(path))
49898
49836
  return null;
49899
49837
  let raw;
@@ -49954,15 +49892,15 @@ function renderUpdateOutcomeLine(entry) {
49954
49892
  `);
49955
49893
  }
49956
49894
  function claimUpdateAnnouncement(requestId, opts = {}) {
49957
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join27(homedir12(), ".switchroom");
49958
- const dir = join27(stateDir, "update-announced");
49895
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join26(homedir12(), ".switchroom");
49896
+ const dir = join26(stateDir, "update-announced");
49959
49897
  try {
49960
49898
  mkdirSync17(dir, { recursive: true });
49961
49899
  } catch {
49962
49900
  return false;
49963
49901
  }
49964
49902
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
49965
- const path = join27(dir, safeId);
49903
+ const path = join26(dir, safeId);
49966
49904
  try {
49967
49905
  const fd = openSync3(path, "wx");
49968
49906
  closeSync3(fd);
@@ -49981,7 +49919,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
49981
49919
  }
49982
49920
 
49983
49921
  // issues-card.ts
49984
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
49922
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "node:fs";
49985
49923
  var SEVERITY_EMOJI = {
49986
49924
  info: "\u2139\ufe0f",
49987
49925
  warn: "\u26a0\ufe0f",
@@ -50073,7 +50011,7 @@ function extractRetryAfterSecs2(err) {
50073
50011
  var COOLDOWN_JITTER_MS2 = 500;
50074
50012
  function readPersistedMessageId(path, log) {
50075
50013
  try {
50076
- const raw = readFileSync29(path, "utf8");
50014
+ const raw = readFileSync28(path, "utf8");
50077
50015
  const parsed = JSON.parse(raw);
50078
50016
  const v = parsed.messageId;
50079
50017
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -50089,7 +50027,7 @@ function readPersistedMessageId(path, log) {
50089
50027
  }
50090
50028
  function writePersistedMessageId(path, messageId, log) {
50091
50029
  try {
50092
- writeFileSync18(path, JSON.stringify({ messageId }) + `
50030
+ writeFileSync17(path, JSON.stringify({ messageId }) + `
50093
50031
  `, { mode: 384 });
50094
50032
  } catch (err) {
50095
50033
  log(`issues-card: persist write failed (${err.message})`);
@@ -50182,24 +50120,24 @@ function createIssuesCardHandle(opts) {
50182
50120
  }
50183
50121
 
50184
50122
  // issues-watcher.ts
50185
- import { existsSync as existsSync32, statSync as statSync8 } from "node:fs";
50186
- import { join as join29 } from "node:path";
50123
+ import { existsSync as existsSync31, statSync as statSync8 } from "node:fs";
50124
+ import { join as join28 } from "node:path";
50187
50125
 
50188
50126
  // ../src/issues/store.ts
50189
50127
  import {
50190
50128
  closeSync as closeSync4,
50191
- existsSync as existsSync31,
50129
+ existsSync as existsSync30,
50192
50130
  mkdirSync as mkdirSync18,
50193
50131
  openSync as openSync4,
50194
50132
  readdirSync as readdirSync5,
50195
- readFileSync as readFileSync30,
50196
- renameSync as renameSync11,
50133
+ readFileSync as readFileSync29,
50134
+ renameSync as renameSync10,
50197
50135
  statSync as statSync7,
50198
- unlinkSync as unlinkSync11,
50199
- writeFileSync as writeFileSync19,
50136
+ unlinkSync as unlinkSync10,
50137
+ writeFileSync as writeFileSync18,
50200
50138
  writeSync
50201
50139
  } from "node:fs";
50202
- import { join as join28 } from "node:path";
50140
+ import { join as join27 } from "node:path";
50203
50141
  import { randomBytes as randomBytes4 } from "node:crypto";
50204
50142
  import { execSync } from "node:child_process";
50205
50143
 
@@ -50215,12 +50153,12 @@ var SEVERITY_RANK2 = {
50215
50153
  var ISSUES_FILE = "issues.jsonl";
50216
50154
  var ISSUES_LOCK = "issues.lock";
50217
50155
  function readAll(stateDir) {
50218
- const path = join28(stateDir, ISSUES_FILE);
50219
- if (!existsSync31(path))
50156
+ const path = join27(stateDir, ISSUES_FILE);
50157
+ if (!existsSync30(path))
50220
50158
  return [];
50221
50159
  let raw;
50222
50160
  try {
50223
- raw = readFileSync30(path, "utf-8");
50161
+ raw = readFileSync29(path, "utf-8");
50224
50162
  } catch {
50225
50163
  return [];
50226
50164
  }
@@ -50252,7 +50190,7 @@ function list(stateDir, opts = {}) {
50252
50190
  });
50253
50191
  }
50254
50192
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50255
- if (!existsSync31(join28(stateDir, ISSUES_FILE)))
50193
+ if (!existsSync30(join27(stateDir, ISSUES_FILE)))
50256
50194
  return 0;
50257
50195
  return withLock(stateDir, () => {
50258
50196
  const all = readAll(stateDir);
@@ -50270,14 +50208,14 @@ function resolve6(stateDir, fingerprint, nowFn = Date.now) {
50270
50208
  });
50271
50209
  }
50272
50210
  function writeAll(stateDir, events) {
50273
- const path = join28(stateDir, ISSUES_FILE);
50211
+ const path = join27(stateDir, ISSUES_FILE);
50274
50212
  sweepOrphanTmpFiles(stateDir);
50275
50213
  const tmp = `${path}.tmp-${process.pid}-${randomBytes4(4).toString("hex")}`;
50276
50214
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
50277
50215
  `) + `
50278
50216
  `;
50279
- writeFileSync19(tmp, body, "utf-8");
50280
- renameSync11(tmp, path);
50217
+ writeFileSync18(tmp, body, "utf-8");
50218
+ renameSync10(tmp, path);
50281
50219
  }
50282
50220
  var ORPHAN_TMP_TTL_MS = 60000;
50283
50221
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -50292,11 +50230,11 @@ function sweepOrphanTmpFiles(stateDir) {
50292
50230
  for (const entry of entries) {
50293
50231
  if (!entry.startsWith(TMP_PREFIX))
50294
50232
  continue;
50295
- const tmpPath2 = join28(stateDir, entry);
50233
+ const tmpPath2 = join27(stateDir, entry);
50296
50234
  try {
50297
50235
  const stat = statSync7(tmpPath2);
50298
50236
  if (stat.mtimeMs < cutoff) {
50299
- unlinkSync11(tmpPath2);
50237
+ unlinkSync10(tmpPath2);
50300
50238
  }
50301
50239
  } catch {}
50302
50240
  }
@@ -50304,7 +50242,7 @@ function sweepOrphanTmpFiles(stateDir) {
50304
50242
  var LOCK_RETRY_MS = 25;
50305
50243
  var LOCK_TIMEOUT_MS = 1e4;
50306
50244
  function withLock(stateDir, fn) {
50307
- const lockPath = join28(stateDir, ISSUES_LOCK);
50245
+ const lockPath = join27(stateDir, ISSUES_LOCK);
50308
50246
  const startedAt = Date.now();
50309
50247
  let fd = null;
50310
50248
  while (fd === null) {
@@ -50332,27 +50270,27 @@ function withLock(stateDir, fn) {
50332
50270
  closeSync4(fd);
50333
50271
  } catch {}
50334
50272
  try {
50335
- unlinkSync11(lockPath);
50273
+ unlinkSync10(lockPath);
50336
50274
  } catch {}
50337
50275
  }
50338
50276
  }
50339
50277
  function tryStealStaleLock(lockPath) {
50340
50278
  let pidStr;
50341
50279
  try {
50342
- pidStr = readFileSync30(lockPath, "utf-8").trim();
50280
+ pidStr = readFileSync29(lockPath, "utf-8").trim();
50343
50281
  } catch {
50344
50282
  return true;
50345
50283
  }
50346
50284
  const pid = Number(pidStr);
50347
50285
  if (!Number.isFinite(pid) || pid <= 0) {
50348
50286
  try {
50349
- unlinkSync11(lockPath);
50287
+ unlinkSync10(lockPath);
50350
50288
  } catch {}
50351
50289
  return true;
50352
50290
  }
50353
50291
  if (pid === process.pid) {
50354
50292
  try {
50355
- unlinkSync11(lockPath);
50293
+ unlinkSync10(lockPath);
50356
50294
  } catch {}
50357
50295
  return true;
50358
50296
  }
@@ -50367,7 +50305,7 @@ function tryStealStaleLock(lockPath) {
50367
50305
  return false;
50368
50306
  }
50369
50307
  try {
50370
- unlinkSync11(lockPath);
50308
+ unlinkSync10(lockPath);
50371
50309
  } catch {}
50372
50310
  return true;
50373
50311
  }
@@ -50389,7 +50327,7 @@ function isIssueEvent(v) {
50389
50327
  // issues-watcher.ts
50390
50328
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
50391
50329
  function startIssuesWatcher(opts) {
50392
- const path = join29(opts.stateDir, ISSUES_FILE);
50330
+ const path = join28(opts.stateDir, ISSUES_FILE);
50393
50331
  const log = opts.log ?? (() => {});
50394
50332
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
50395
50333
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -50437,7 +50375,7 @@ function startIssuesWatcher(opts) {
50437
50375
  };
50438
50376
  }
50439
50377
  function defaultSignatureProvider(path) {
50440
- if (!existsSync32(path))
50378
+ if (!existsSync31(path))
50441
50379
  return null;
50442
50380
  try {
50443
50381
  const stat = statSync8(path);
@@ -51042,8 +50980,8 @@ function extractFlowItems(line) {
51042
50980
  }
51043
50981
 
51044
50982
  // credits-watch.ts
51045
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync19 } from "fs";
51046
- import { join as join30 } from "path";
50983
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync32, mkdirSync as mkdirSync19 } from "fs";
50984
+ import { join as join29 } from "path";
51047
50985
  var STATE_FILE = "credits-watch.json";
51048
50986
  var FATAL_REASONS = new Set([
51049
50987
  "out_of_credits",
@@ -51055,12 +50993,12 @@ function emptyCreditState() {
51055
50993
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
51056
50994
  }
51057
50995
  function readClaudeJsonOverage(claudeConfigDir) {
51058
- const path = join30(claudeConfigDir, ".claude.json");
51059
- if (!existsSync33(path))
50996
+ const path = join29(claudeConfigDir, ".claude.json");
50997
+ if (!existsSync32(path))
51060
50998
  return null;
51061
50999
  let raw;
51062
51000
  try {
51063
- raw = readFileSync31(path, "utf-8");
51001
+ raw = readFileSync30(path, "utf-8");
51064
51002
  } catch {
51065
51003
  return null;
51066
51004
  }
@@ -51084,7 +51022,7 @@ function evaluateCreditState(args) {
51084
51022
  if (!currentIsFatal && prevIsFatal) {
51085
51023
  return {
51086
51024
  kind: "notify",
51087
- message: `\u2705 <b>${escapeHtml10(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51025
+ message: `\u2705 <b>${escapeHtml9(agentName3)}</b>: credits restored \u2014 agent should resume normal operation.`,
51088
51026
  newState: { lastNotifiedReason: null, lastNotifiedAt: now },
51089
51027
  transition: "exited"
51090
51028
  };
@@ -51113,12 +51051,12 @@ function evaluateCreditState(args) {
51113
51051
  function buildEntryMessage(agentName3, reason) {
51114
51052
  const desc = humanizeReason(reason);
51115
51053
  return [
51116
- `\u26a0\ufe0f <b>${escapeHtml10(agentName3)}</b>: ${desc}`,
51054
+ `\u26a0\ufe0f <b>${escapeHtml9(agentName3)}</b>: ${desc}`,
51117
51055
  ``,
51118
51056
  `Cron tasks and inbound replies will fail until this is resolved. Check`,
51119
51057
  `your subscription or pre-paid usage at <a href="https://console.anthropic.com">console.anthropic.com</a>.`,
51120
51058
  ``,
51121
- `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml10(reason)})</i>`
51059
+ `<i>Source: Claude CLI cache (cachedExtraUsageDisabledReason=${escapeHtml9(reason)})</i>`
51122
51060
  ].join(`
51123
51061
  `);
51124
51062
  }
@@ -51136,15 +51074,15 @@ function humanizeReason(reason) {
51136
51074
  return `usage disabled (${reason})`;
51137
51075
  }
51138
51076
  }
51139
- function escapeHtml10(s) {
51077
+ function escapeHtml9(s) {
51140
51078
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51141
51079
  }
51142
51080
  function loadCreditState(stateDir) {
51143
- const path = join30(stateDir, STATE_FILE);
51144
- if (!existsSync33(path))
51081
+ const path = join29(stateDir, STATE_FILE);
51082
+ if (!existsSync32(path))
51145
51083
  return emptyCreditState();
51146
51084
  try {
51147
- const raw = readFileSync31(path, "utf-8");
51085
+ const raw = readFileSync30(path, "utf-8");
51148
51086
  const parsed = JSON.parse(raw);
51149
51087
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
51150
51088
  return {
@@ -51157,14 +51095,14 @@ function loadCreditState(stateDir) {
51157
51095
  }
51158
51096
  function saveCreditState(stateDir, state4) {
51159
51097
  mkdirSync19(stateDir, { recursive: true });
51160
- const path = join30(stateDir, STATE_FILE);
51161
- writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51098
+ const path = join29(stateDir, STATE_FILE);
51099
+ writeFileSync19(path, JSON.stringify(state4, null, 2) + `
51162
51100
  `, { mode: 384 });
51163
51101
  }
51164
51102
 
51165
51103
  // quota-watch.ts
51166
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync21, existsSync as existsSync34, mkdirSync as mkdirSync20 } from "fs";
51167
- import { join as join31 } from "path";
51104
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync20 } from "fs";
51105
+ import { join as join30 } from "path";
51168
51106
  var STATE_FILE2 = "quota-watch.json";
51169
51107
  function emptyQuotaWatchState() {
51170
51108
  return {};
@@ -51221,11 +51159,11 @@ function buildThrottlingMessage(agentName3, snap) {
51221
51159
  const resetAt = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
51222
51160
  const resetStr = resetAt ? ` \u00b7 refills in ${formatRelative(resetAt, new Date)}` : "";
51223
51161
  const activeNote = snap.isActive ? "" : `
51224
- This is a non-active account. Consider <code>/auth use ${escapeHtml11(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
51162
+ This is a non-active account. Consider <code>/auth use ${escapeHtml10(snap.label)}</code> to switch, or keep it as a fallback reserve.`;
51225
51163
  const altNote = snap.isActive ? `
51226
51164
  Consider <code>/auth use &lt;other-account&gt;</code> if you have a healthier account, or wait for the ${winLabel} window to refill${resetStr}.` : "";
51227
51165
  return [
51228
- `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51166
+ `\uD83D\uDFE1 <b>Quota approaching limit</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51229
51167
  ``,
51230
51168
  `${fiveStr} of 5h \u00b7 ${sevenStr} of 7d`,
51231
51169
  `Binding window: ${winLabel}${resetStr}`,
@@ -51242,7 +51180,7 @@ function buildRecoveryMessage(agentName3, snap) {
51242
51180
  const q = snap.quota;
51243
51181
  const utilLine = q ? `Current: ${fmtPct(q.fiveHourUtilizationPct)} of 5h \u00b7 ${fmtPct(q.sevenDayUtilizationPct)} of 7d` : "Current quota data unavailable.";
51244
51182
  return [
51245
- `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml11(snap.label)}</code>`,
51183
+ `\uD83D\uDFE2 <b>Quota back in healthy range</b> \u2014 <code>${escapeHtml10(snap.label)}</code>`,
51246
51184
  ``,
51247
51185
  utilLine,
51248
51186
  ``,
@@ -51250,15 +51188,15 @@ function buildRecoveryMessage(agentName3, snap) {
51250
51188
  ].join(`
51251
51189
  `);
51252
51190
  }
51253
- function escapeHtml11(s) {
51191
+ function escapeHtml10(s) {
51254
51192
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
51255
51193
  }
51256
51194
  function loadQuotaWatchState(stateDir) {
51257
- const path = join31(stateDir, STATE_FILE2);
51258
- if (!existsSync34(path))
51195
+ const path = join30(stateDir, STATE_FILE2);
51196
+ if (!existsSync33(path))
51259
51197
  return emptyQuotaWatchState();
51260
51198
  try {
51261
- const raw = readFileSync32(path, "utf-8");
51199
+ const raw = readFileSync31(path, "utf-8");
51262
51200
  const parsed = JSON.parse(raw);
51263
51201
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
51264
51202
  return emptyQuotaWatchState();
@@ -51276,8 +51214,8 @@ function loadQuotaWatchState(stateDir) {
51276
51214
  }
51277
51215
  function saveQuotaWatchState(stateDir, state4) {
51278
51216
  mkdirSync20(stateDir, { recursive: true });
51279
- const path = join31(stateDir, STATE_FILE2);
51280
- writeFileSync21(path, JSON.stringify(state4, null, 2) + `
51217
+ const path = join30(stateDir, STATE_FILE2);
51218
+ writeFileSync20(path, JSON.stringify(state4, null, 2) + `
51281
51219
  `, { mode: 384 });
51282
51220
  }
51283
51221
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -51290,27 +51228,27 @@ init_auth_snapshot_format();
51290
51228
  // gateway/turn-active-marker.ts
51291
51229
  import {
51292
51230
  closeSync as closeSync5,
51293
- existsSync as existsSync35,
51231
+ existsSync as existsSync34,
51294
51232
  mkdirSync as mkdirSync21,
51295
51233
  openSync as openSync5,
51296
- readFileSync as readFileSync33,
51234
+ readFileSync as readFileSync32,
51297
51235
  statSync as statSync9,
51298
- unlinkSync as unlinkSync12,
51236
+ unlinkSync as unlinkSync11,
51299
51237
  utimesSync as utimesSync2,
51300
- writeFileSync as writeFileSync22
51238
+ writeFileSync as writeFileSync21
51301
51239
  } from "node:fs";
51302
- import { join as join32 } from "node:path";
51240
+ import { join as join31 } from "node:path";
51303
51241
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
51304
51242
  function writeTurnActiveMarker(stateDir, marker) {
51305
51243
  try {
51306
51244
  mkdirSync21(stateDir, { recursive: true });
51307
- writeFileSync22(join32(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51245
+ writeFileSync21(join31(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
51308
51246
  `, { mode: 384 });
51309
51247
  } catch {}
51310
51248
  }
51311
51249
  function touchTurnActiveMarker2(stateDir) {
51312
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51313
- if (!existsSync35(path))
51250
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51251
+ if (!existsSync34(path))
51314
51252
  return;
51315
51253
  const now = new Date;
51316
51254
  try {
@@ -51324,12 +51262,12 @@ function touchTurnActiveMarker2(stateDir) {
51324
51262
  }
51325
51263
  function removeTurnActiveMarker(stateDir) {
51326
51264
  try {
51327
- unlinkSync12(join32(stateDir, TURN_ACTIVE_MARKER_FILE2));
51265
+ unlinkSync11(join31(stateDir, TURN_ACTIVE_MARKER_FILE2));
51328
51266
  } catch {}
51329
51267
  }
51330
51268
  function sweepStaleTurnActiveMarker(stateDir, opts) {
51331
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
51332
- if (!existsSync35(path))
51269
+ const path = join31(stateDir, TURN_ACTIVE_MARKER_FILE2);
51270
+ if (!existsSync34(path))
51333
51271
  return false;
51334
51272
  const now = opts.now ?? Date.now();
51335
51273
  try {
@@ -51341,9 +51279,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51341
51279
  return false;
51342
51280
  let payload = null;
51343
51281
  try {
51344
- payload = readFileSync33(path, "utf8");
51282
+ payload = readFileSync32(path, "utf8");
51345
51283
  } catch {}
51346
- unlinkSync12(path);
51284
+ unlinkSync11(path);
51347
51285
  if (opts.onRemove) {
51348
51286
  try {
51349
51287
  opts.onRemove({
@@ -51360,10 +51298,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51360
51298
  }
51361
51299
 
51362
51300
  // ../src/build-info.ts
51363
- var VERSION = "0.14.20";
51364
- var COMMIT_SHA = "c8b965b2";
51365
- var COMMIT_DATE = "2026-05-31T01:51:10Z";
51366
- var LATEST_PR = 2018;
51301
+ var VERSION = "0.14.22";
51302
+ var COMMIT_SHA = "ab2692b9";
51303
+ var COMMIT_DATE = "2026-05-31T06:26:06Z";
51304
+ var LATEST_PR = 2028;
51367
51305
  var COMMITS_AHEAD_OF_TAG = 0;
51368
51306
 
51369
51307
  // gateway/boot-version.ts
@@ -51437,11 +51375,11 @@ init_peercred();
51437
51375
  import * as net5 from "node:net";
51438
51376
  import * as fs2 from "node:fs";
51439
51377
  import { homedir as homedir13 } from "node:os";
51440
- import { join as join33 } from "node:path";
51378
+ import { join as join32 } from "node:path";
51441
51379
  var DEFAULT_TIMEOUT_MS4 = 2000;
51442
51380
  var UNLOCK_TIMEOUT_MS = 30000;
51443
- var LEGACY_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "vault-broker.sock");
51444
- var OPERATOR_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "broker-operator", "sock");
51381
+ var LEGACY_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "vault-broker.sock");
51382
+ var OPERATOR_SOCKET_PATH2 = join32(homedir13(), ".switchroom", "broker-operator", "sock");
51445
51383
  function defaultBrokerSocketPath2() {
51446
51384
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
51447
51385
  return OPERATOR_SOCKET_PATH2;
@@ -51664,7 +51602,7 @@ function resolveVaultApprovalPosture(broker) {
51664
51602
 
51665
51603
  // registry/turns-schema.ts
51666
51604
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync22 } from "fs";
51667
- import { join as join34 } from "path";
51605
+ import { join as join33 } from "path";
51668
51606
  var DatabaseClass2 = null;
51669
51607
  function loadDatabaseClass2() {
51670
51608
  if (DatabaseClass2 != null)
@@ -51697,6 +51635,7 @@ var SCHEMA_SQL = `
51697
51635
  user_prompt_preview TEXT,
51698
51636
  assistant_reply_preview TEXT,
51699
51637
  tool_call_count INTEGER,
51638
+ interrupt_reason TEXT,
51700
51639
  created_at INTEGER NOT NULL,
51701
51640
  updated_at INTEGER NOT NULL
51702
51641
  );
@@ -51707,11 +51646,14 @@ var PHASE1_MIGRATIONS = [
51707
51646
  `ALTER TABLE turns ADD COLUMN assistant_reply_preview TEXT`,
51708
51647
  `ALTER TABLE turns ADD COLUMN tool_call_count INTEGER`
51709
51648
  ];
51649
+ var PHASE2_MIGRATIONS = [
51650
+ `ALTER TABLE turns ADD COLUMN interrupt_reason TEXT`
51651
+ ];
51710
51652
  function applySchema(db2) {
51711
51653
  db2.exec("PRAGMA journal_mode = WAL");
51712
51654
  db2.exec("PRAGMA synchronous = NORMAL");
51713
51655
  db2.exec(SCHEMA_SQL);
51714
- for (const sql of PHASE1_MIGRATIONS) {
51656
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS]) {
51715
51657
  try {
51716
51658
  db2.exec(sql);
51717
51659
  } catch (err) {
@@ -51723,9 +51665,9 @@ function applySchema(db2) {
51723
51665
  }
51724
51666
  function openTurnsDb(agentDir) {
51725
51667
  const Database = loadDatabaseClass2();
51726
- const dir = join34(agentDir, "telegram");
51668
+ const dir = join33(agentDir, "telegram");
51727
51669
  mkdirSync22(dir, { recursive: true, mode: 448 });
51728
- const path = join34(dir, "registry.db");
51670
+ const path = join33(dir, "registry.db");
51729
51671
  const db2 = new Database(path, { create: true });
51730
51672
  applySchema(db2);
51731
51673
  try {
@@ -51747,6 +51689,7 @@ function mapRow(row) {
51747
51689
  user_prompt_preview: row.user_prompt_preview,
51748
51690
  assistant_reply_preview: row.assistant_reply_preview,
51749
51691
  tool_call_count: row.tool_call_count,
51692
+ interrupt_reason: row.interrupt_reason,
51750
51693
  created_at: row.created_at,
51751
51694
  updated_at: row.updated_at
51752
51695
  };
@@ -51775,26 +51718,143 @@ function recordTurnEnd(db2, args) {
51775
51718
  WHERE turn_key = ?
51776
51719
  `).run(now, args.endedVia, args.lastAssistantMsgId ?? null, args.lastAssistantDone !== undefined ? args.lastAssistantDone ? 1 : 0 : null, args.assistantReplyPreview ?? null, args.toolCallCount !== undefined ? args.toolCallCount : null, now, args.turnKey);
51777
51720
  }
51778
- function markOrphanedAsRestarted(db2) {
51779
- const now = Date.now();
51780
- const result = db2.prepare(`
51721
+ function markOrphanedWithTimeoutClassification(db2, opts) {
51722
+ const now = opts.now ?? Date.now();
51723
+ const isHang = opts.markerAgeMs != null && opts.markerAgeMs >= opts.hangThresholdMs && opts.markerTurnKey != null && opts.markerTurnKey.length > 0;
51724
+ let timeoutTurnKey = null;
51725
+ if (isHang) {
51726
+ const r = db2.prepare(`
51727
+ UPDATE turns
51728
+ SET ended_at = ?,
51729
+ ended_via = 'timeout',
51730
+ interrupt_reason = ?,
51731
+ updated_at = ?
51732
+ WHERE turn_key = ? AND ended_at IS NULL
51733
+ `).run(now, opts.reasonSnapshot ?? null, now, opts.markerTurnKey);
51734
+ if (r.changes > 0)
51735
+ timeoutTurnKey = opts.markerTurnKey ?? null;
51736
+ }
51737
+ const rest = db2.prepare(`
51781
51738
  UPDATE turns
51782
51739
  SET ended_at = ?,
51783
51740
  ended_via = 'restart',
51784
51741
  updated_at = ?
51785
51742
  WHERE ended_at IS NULL
51786
51743
  `).run(now, now);
51787
- return result.changes;
51744
+ return { reaped: (timeoutTurnKey ? 1 : 0) + rest.changes, timeoutTurnKey };
51788
51745
  }
51789
- function findMostRecentInterruptedTurn(db2) {
51746
+ var INTERRUPTED_VIA = new Set([
51747
+ "restart",
51748
+ "sigterm",
51749
+ "timeout",
51750
+ "unknown"
51751
+ ]);
51752
+ function findLatestTurnIfInterrupted(db2) {
51790
51753
  const row = db2.prepare(`
51791
51754
  SELECT * FROM turns
51792
- WHERE ended_at IS NULL
51793
- OR ended_via IN ('restart', 'sigterm', 'timeout')
51794
51755
  ORDER BY started_at DESC
51795
51756
  LIMIT 1
51796
51757
  `).get();
51797
- return row ? mapRow(row) : null;
51758
+ if (!row)
51759
+ return null;
51760
+ const turn = mapRow(row);
51761
+ if (turn.ended_at == null)
51762
+ return turn;
51763
+ if (turn.ended_via != null && INTERRUPTED_VIA.has(turn.ended_via))
51764
+ return turn;
51765
+ return null;
51766
+ }
51767
+
51768
+ // gateway/resume-inbound-builder.ts
51769
+ function humanizeElapsed(ms) {
51770
+ if (!Number.isFinite(ms) || ms < 0)
51771
+ return "an unknown amount of time";
51772
+ const sec = Math.round(ms / 1000);
51773
+ if (sec < 45)
51774
+ return "moments";
51775
+ const min = Math.round(sec / 60);
51776
+ if (min < 60)
51777
+ return `~${min} min`;
51778
+ const hr = Math.round(min / 60);
51779
+ if (hr < 24)
51780
+ return `~${hr}h`;
51781
+ const days = Math.round(hr / 24);
51782
+ return `~${days} day${days === 1 ? "" : "s"}`;
51783
+ }
51784
+ function threadIdNum(turn) {
51785
+ if (turn.thread_id == null)
51786
+ return;
51787
+ const n = Number(turn.thread_id);
51788
+ return Number.isFinite(n) ? n : undefined;
51789
+ }
51790
+ function promptClause(turn) {
51791
+ const p = turn.user_prompt_preview?.trim();
51792
+ if (!p)
51793
+ return "";
51794
+ const snippet = p.length > 160 ? p.slice(0, 160) + "\u2026" : p;
51795
+ return ` The request was: "${snippet}".`;
51796
+ }
51797
+ function buildResumeInterruptedInbound(ctx) {
51798
+ const ts = ctx.nowMs ?? Date.now();
51799
+ const elapsed = humanizeElapsed(ts - ctx.turn.started_at);
51800
+ const meta = {
51801
+ source: "resume_interrupted",
51802
+ resume_turn_key: ctx.turn.turn_key,
51803
+ interrupted_via: ctx.turn.ended_via ?? "restart",
51804
+ started_at: String(ctx.turn.started_at)
51805
+ };
51806
+ if (ctx.turn.user_prompt_preview)
51807
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51808
+ const threadId = threadIdNum(ctx.turn);
51809
+ return {
51810
+ type: "inbound",
51811
+ chatId: ctx.turn.chat_id,
51812
+ ...threadId != null ? { threadId } : {},
51813
+ messageId: ts,
51814
+ user: "switchroom",
51815
+ userId: 0,
51816
+ ts,
51817
+ text: `You just restarted. Your previous turn was interrupted ${elapsed} ago, ` + `before it finished \u2014 it was cut off by a restart, not completed.` + promptClause(ctx.turn) + ` Pick that work back up now and continue it through to completion. ` + `In your first message, briefly let the user know you're resuming what ` + `was interrupted (mention roughly how long ago in plain language) so ` + `they're not left wondering \u2014 then carry on with the actual task. Do ` + `not ask whether to resume; just resume. If you genuinely can't tell ` + `what the work was, say so and ask.`,
51818
+ meta
51819
+ };
51820
+ }
51821
+ function buildResumeWatchdogReportInbound(ctx) {
51822
+ const ts = ctx.nowMs ?? Date.now();
51823
+ const idle = humanizeElapsed(ctx.idleMs);
51824
+ const since = humanizeElapsed(ts - ctx.turn.started_at);
51825
+ const toolClause = ctx.turn.tool_call_count != null && ctx.turn.tool_call_count > 0 ? ` You'd run ${ctx.turn.tool_call_count} tool call${ctx.turn.tool_call_count === 1 ? "" : "s"} before it stalled.` : "";
51826
+ const meta = {
51827
+ source: "resume_watchdog_timeout",
51828
+ resume_turn_key: ctx.turn.turn_key,
51829
+ interrupted_via: "timeout",
51830
+ idle_ms: String(ctx.idleMs),
51831
+ started_at: String(ctx.turn.started_at)
51832
+ };
51833
+ if (ctx.turn.tool_call_count != null)
51834
+ meta.tool_call_count = String(ctx.turn.tool_call_count);
51835
+ if (ctx.turn.user_prompt_preview)
51836
+ meta.original_prompt = ctx.turn.user_prompt_preview;
51837
+ const threadId = threadIdNum(ctx.turn);
51838
+ return {
51839
+ type: "inbound",
51840
+ chatId: ctx.turn.chat_id,
51841
+ ...threadId != null ? { threadId } : {},
51842
+ messageId: ts,
51843
+ user: "switchroom",
51844
+ userId: 0,
51845
+ ts,
51846
+ text: `You just restarted. Your previous turn (started ${since} ago) was ` + `killed by the hang-watchdog: it made no observable progress for ${idle} ` + `and the watchdog restarts a turn that goes that long without activity.` + toolClause + promptClause(ctx.turn) + ` Do NOT silently resume it \u2014 it may hang again the same way. Instead, ` + `tell the user plainly what happened: that your last turn was killed ` + `after ${idle} of no progress, and roughly what it was doing. Then ask ` + `whether they want you to retry it or take a different angle. Report ` + `only the honest cause \u2014 no observable progress for that long \u2014 don't ` + `speculate about a deeper root cause you can't see.`,
51847
+ meta
51848
+ };
51849
+ }
51850
+ function selectResumeBuilder(endedVia) {
51851
+ if (endedVia === "timeout")
51852
+ return "report";
51853
+ if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
51854
+ return "resume";
51855
+ if (endedVia == null)
51856
+ return "resume";
51857
+ return null;
51798
51858
  }
51799
51859
 
51800
51860
  // registry/subagents-schema.ts
@@ -51894,11 +51954,11 @@ installGlobalErrorHandlers();
51894
51954
  process.on("beforeExit", () => {
51895
51955
  shutdownAnalytics();
51896
51956
  });
51897
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join36(homedir14(), ".claude", "channels", "telegram");
51898
- var ACCESS_FILE = join36(STATE_DIR, "access.json");
51899
- var APPROVED_DIR = join36(STATE_DIR, "approved");
51900
- var ENV_FILE = join36(STATE_DIR, ".env");
51901
- var INBOX_DIR = join36(STATE_DIR, "inbox");
51957
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join35(homedir14(), ".claude", "channels", "telegram");
51958
+ var ACCESS_FILE = join35(STATE_DIR, "access.json");
51959
+ var APPROVED_DIR = join35(STATE_DIR, "approved");
51960
+ var ENV_FILE = join35(STATE_DIR, ".env");
51961
+ var INBOX_DIR = join35(STATE_DIR, "inbox");
51902
51962
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
51903
51963
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
51904
51964
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -51963,7 +52023,7 @@ function formatBootVersion() {
51963
52023
  }
51964
52024
  try {
51965
52025
  chmodSync6(ENV_FILE, 384);
51966
- for (const line of readFileSync36(ENV_FILE, "utf8").split(`
52026
+ for (const line of readFileSync35(ENV_FILE, "utf8").split(`
51967
52027
  `)) {
51968
52028
  const m = line.match(/^(\w+)=(.*)$/);
51969
52029
  if (m && process.env[m[1]] === undefined)
@@ -52016,7 +52076,7 @@ installTgPostLogger(bot);
52016
52076
  var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
52017
52077
  var GRAMMY_VERSION = (() => {
52018
52078
  try {
52019
- const raw = readFileSync36(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52079
+ const raw = readFileSync35(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
52020
52080
  return JSON.parse(raw).version ?? "unknown";
52021
52081
  } catch {
52022
52082
  return "unknown";
@@ -52089,7 +52149,7 @@ function assertSendable(f) {
52089
52149
  } catch {
52090
52150
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
52091
52151
  }
52092
- const inbox = join36(stateReal, "inbox");
52152
+ const inbox = join35(stateReal, "inbox");
52093
52153
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
52094
52154
  throw new Error(`refusing to send channel state: ${f}`);
52095
52155
  }
@@ -52108,7 +52168,7 @@ function assertSendable(f) {
52108
52168
  }
52109
52169
  function readAccessFile() {
52110
52170
  try {
52111
- const raw = readFileSync36(ACCESS_FILE, "utf8");
52171
+ const raw = readFileSync35(ACCESS_FILE, "utf8");
52112
52172
  const parsed = JSON.parse(raw);
52113
52173
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
52114
52174
  const groups = {};
@@ -52145,7 +52205,7 @@ function readAccessFile() {
52145
52205
  if (err.code === "ENOENT")
52146
52206
  return defaultAccess();
52147
52207
  try {
52148
- renameSync13(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52208
+ renameSync12(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
52149
52209
  } catch {}
52150
52210
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
52151
52211
  `);
@@ -52178,9 +52238,9 @@ function saveAccess(a) {
52178
52238
  return;
52179
52239
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
52180
52240
  const tmp = ACCESS_FILE + ".tmp";
52181
- writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
52241
+ writeFileSync24(tmp, JSON.stringify(a, null, 2) + `
52182
52242
  `, { mode: 384 });
52183
- renameSync13(tmp, ACCESS_FILE);
52243
+ renameSync12(tmp, ACCESS_FILE);
52184
52244
  }
52185
52245
  function pruneExpired(a) {
52186
52246
  const now = Date.now();
@@ -52198,7 +52258,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
52198
52258
  if (HISTORY_ENABLED) {
52199
52259
  try {
52200
52260
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
52201
- process.stderr.write(`telegram gateway: history capture enabled at ${join36(STATE_DIR, "history.db")}
52261
+ process.stderr.write(`telegram gateway: history capture enabled at ${join35(STATE_DIR, "history.db")}
52202
52262
  `);
52203
52263
  } catch (err) {
52204
52264
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -52206,21 +52266,71 @@ if (HISTORY_ENABLED) {
52206
52266
  }
52207
52267
  }
52208
52268
  var turnsDb = null;
52269
+ var bootResumeInbound = null;
52209
52270
  try {
52210
52271
  const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
52211
52272
  turnsDb = openTurnsDb(agentDir);
52212
52273
  applySubagentsSchema(turnsDb);
52213
- const reaped = markOrphanedAsRestarted(turnsDb);
52274
+ let markerTurnKey = null;
52275
+ let markerAgeMs = null;
52276
+ try {
52277
+ const markerPath = join35(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
52278
+ if (existsSync38(markerPath)) {
52279
+ const st = statSync13(markerPath);
52280
+ markerAgeMs = Date.now() - st.mtimeMs;
52281
+ try {
52282
+ const payload = JSON.parse(readFileSync35(markerPath, "utf8"));
52283
+ if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
52284
+ markerTurnKey = payload.turnKey;
52285
+ }
52286
+ } catch {}
52287
+ }
52288
+ } catch {}
52289
+ const hangSecs = Number(process.env.TURN_HANG_SECS);
52290
+ const hangThresholdMs = (Number.isFinite(hangSecs) && hangSecs > 0 ? hangSecs : 300) * 1000;
52291
+ const reasonSnapshot = markerAgeMs != null ? JSON.stringify({ idleMs: Math.round(markerAgeMs) }) : null;
52292
+ const { reaped, timeoutTurnKey } = markOrphanedWithTimeoutClassification(turnsDb, {
52293
+ markerTurnKey,
52294
+ markerAgeMs,
52295
+ hangThresholdMs,
52296
+ reasonSnapshot
52297
+ });
52214
52298
  if (reaped > 0) {
52215
- process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
52299
+ process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
52216
52300
  `);
52217
52301
  } else {
52218
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join36(agentDir, "telegram", "registry.db")}
52302
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join35(agentDir, "telegram", "registry.db")}
52219
52303
  `);
52220
52304
  }
52221
- const pendingEnvPath = join36(agentDir, ".pending-turn.env");
52305
+ const pending2 = findLatestTurnIfInterrupted(turnsDb);
52306
+ const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
52307
+ if (pending2 != null && selfAgent) {
52308
+ const kind = selectResumeBuilder(pending2.ended_via);
52309
+ if (kind === "resume") {
52310
+ bootResumeInbound = { agent: selfAgent, msg: buildResumeInterruptedInbound({ turn: pending2 }) };
52311
+ } else if (kind === "report") {
52312
+ let idleMs = pending2.turn_key === timeoutTurnKey && markerAgeMs != null ? markerAgeMs : null;
52313
+ if (idleMs == null && pending2.interrupt_reason) {
52314
+ try {
52315
+ const parsed = JSON.parse(pending2.interrupt_reason);
52316
+ if (typeof parsed.idleMs === "number" && Number.isFinite(parsed.idleMs))
52317
+ idleMs = parsed.idleMs;
52318
+ } catch {}
52319
+ }
52320
+ if (idleMs == null)
52321
+ idleMs = Math.max(0, Date.now() - pending2.started_at);
52322
+ bootResumeInbound = {
52323
+ agent: selfAgent,
52324
+ msg: buildResumeWatchdogReportInbound({ turn: pending2, idleMs })
52325
+ };
52326
+ }
52327
+ if (bootResumeInbound != null) {
52328
+ process.stderr.write(`telegram gateway: boot-resume queued kind=${kind} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"} chat=${pending2.chat_id}
52329
+ `);
52330
+ }
52331
+ }
52332
+ const pendingEnvPath = join35(agentDir, ".pending-turn.env");
52222
52333
  try {
52223
- const pending2 = findMostRecentInterruptedTurn(turnsDb);
52224
52334
  if (pending2 != null) {
52225
52335
  const lines = [
52226
52336
  `SWITCHROOM_PENDING_TURN=true`,
@@ -52229,22 +52339,23 @@ try {
52229
52339
  pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
52230
52340
  pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
52231
52341
  `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
52232
- `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
52342
+ `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`,
52343
+ pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
52233
52344
  ];
52234
52345
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
52235
- writeFileSync25(pendingEnvTmp, lines.join(`
52346
+ writeFileSync24(pendingEnvTmp, lines.join(`
52236
52347
  `) + `
52237
52348
  `, { mode: 384 });
52238
- renameSync13(pendingEnvTmp, pendingEnvPath);
52349
+ renameSync12(pendingEnvTmp, pendingEnvPath);
52239
52350
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
52240
52351
  `);
52241
- } else if (existsSync39(pendingEnvPath)) {
52352
+ } else if (existsSync38(pendingEnvPath)) {
52242
52353
  rmSync4(pendingEnvPath, { force: true });
52243
52354
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
52244
52355
  `);
52245
52356
  }
52246
52357
  } catch (err) {
52247
- process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message}) \u2014 resume protocol may not fire
52358
+ process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message})
52248
52359
  `);
52249
52360
  }
52250
52361
  } catch (err) {
@@ -52292,7 +52403,7 @@ function checkApprovals() {
52292
52403
  return;
52293
52404
  }
52294
52405
  for (const senderId of files) {
52295
- const file = join36(APPROVED_DIR, senderId);
52406
+ const file = join35(APPROVED_DIR, senderId);
52296
52407
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
52297
52408
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
52298
52409
  `);
@@ -52659,26 +52770,6 @@ function probeAvailableReactions(chatId) {
52659
52770
  }
52660
52771
  })();
52661
52772
  }
52662
- var pendingHandoffTopic = null;
52663
- function initHandoffContinuity() {
52664
- if (!shouldShowHandoffLine()) {
52665
- pendingHandoffTopic = null;
52666
- return;
52667
- }
52668
- const agentDir = resolveAgentDirFromEnv();
52669
- if (agentDir == null) {
52670
- pendingHandoffTopic = null;
52671
- return;
52672
- }
52673
- pendingHandoffTopic = consumeHandoffTopic(agentDir);
52674
- }
52675
- function takeHandoffPrefix(format) {
52676
- if (pendingHandoffTopic == null)
52677
- return "";
52678
- const line = formatHandoffLine(pendingHandoffTopic, format);
52679
- pendingHandoffTopic = null;
52680
- return line;
52681
- }
52682
52773
  var PHOTO_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"]);
52683
52774
  function chunk2(text, limit, mode) {
52684
52775
  if (text.length <= limit)
@@ -52703,7 +52794,7 @@ function chunk2(text, limit, mode) {
52703
52794
  out.push(rest);
52704
52795
  return out;
52705
52796
  }
52706
- function escapeMarkdownV22(text) {
52797
+ function escapeMarkdownV2(text) {
52707
52798
  const specialChars = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
52708
52799
  const parts = [];
52709
52800
  let last = 0;
@@ -53118,7 +53209,7 @@ function looksLikeAuthCode(text) {
53118
53209
  }
53119
53210
  var bufferedAttachmentKeys = new Map;
53120
53211
  function coalesceMaxAttachments() {
53121
- return Math.max(1, loadAccess().coalesceMaxAttachments ?? 1);
53212
+ return resolveCoalesceMaxAttachments(loadAccess().coalesceMaxAttachments);
53122
53213
  }
53123
53214
  var inboundCoalescer = createInboundCoalescer({
53124
53215
  gapMs: () => loadAccess().coalescingGapMs ?? 500,
@@ -53248,11 +53339,11 @@ var unpinProgressCardForChat = null;
53248
53339
  var getPinnedProgressCardMessageId = null;
53249
53340
  var completeProgressCardTurn = null;
53250
53341
  var subagentWatcher = null;
53251
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join36(STATE_DIR, "gateway.sock");
53342
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join35(STATE_DIR, "gateway.sock");
53252
53343
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
53253
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join36(STATE_DIR, "gateway.pid.json");
53254
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join36(STATE_DIR, "gateway-session.json");
53255
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join36(STATE_DIR, "clean-shutdown.json");
53344
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join35(STATE_DIR, "gateway.pid.json");
53345
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join35(STATE_DIR, "gateway-session.json");
53346
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join35(STATE_DIR, "clean-shutdown.json");
53256
53347
  var GATEWAY_STARTED_AT_MS = Date.now();
53257
53348
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
53258
53349
  var activeBootCard = null;
@@ -53281,7 +53372,7 @@ function ensureIssuesCard(chatId, threadId) {
53281
53372
  bot: botApi,
53282
53373
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
53283
53374
  `),
53284
- persistPath: join36(stateDir, "issues-card.json")
53375
+ persistPath: join35(stateDir, "issues-card.json")
53285
53376
  });
53286
53377
  activeIssuesWatcher = startIssuesWatcher({
53287
53378
  stateDir,
@@ -53440,17 +53531,24 @@ startTimer2({
53440
53531
  }
53441
53532
  });
53442
53533
  var inboundSpool = STATIC ? undefined : createInboundSpool({
53443
- path: join36(STATE_DIR, "inbound-spool.jsonl"),
53534
+ path: join35(STATE_DIR, "inbound-spool.jsonl"),
53444
53535
  fs: {
53445
53536
  appendFileSync: (p, d) => appendFileSync5(p, d),
53446
- readFileSync: (p) => readFileSync36(p, "utf8"),
53447
- writeFileSync: (p, d) => writeFileSync25(p, d),
53448
- renameSync: (a, b) => renameSync13(a, b),
53449
- existsSync: (p) => existsSync39(p),
53537
+ readFileSync: (p) => readFileSync35(p, "utf8"),
53538
+ writeFileSync: (p, d) => writeFileSync24(p, d),
53539
+ renameSync: (a, b) => renameSync12(a, b),
53540
+ existsSync: (p) => existsSync38(p),
53450
53541
  statSizeSync: (p) => statSync13(p).size
53451
53542
  }
53452
53543
  });
53453
53544
  var pendingInboundBuffer = createPendingInboundBuffer({ spool: inboundSpool });
53545
+ if (bootResumeInbound != null) {
53546
+ if (inboundSpool != null) {
53547
+ inboundSpool.put(bootResumeInbound.agent, bootResumeInbound.msg);
53548
+ } else {
53549
+ pendingInboundBuffer.push(bootResumeInbound.agent, bootResumeInbound.msg);
53550
+ }
53551
+ }
53454
53552
  if (inboundSpool != null) {
53455
53553
  const replay = inboundSpool.liveEntries();
53456
53554
  for (const e of replay)
@@ -53579,7 +53677,7 @@ var ipcServer = createIpcServer({
53579
53677
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
53580
53678
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
53581
53679
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
53582
- configSnapshotPath: join36(resolvedAgentDirForCard, ".config-snapshot.json"),
53680
+ configSnapshotPath: join35(resolvedAgentDirForCard, ".config-snapshot.json"),
53583
53681
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
53584
53682
  }, ackMsgId).then((handle) => {
53585
53683
  activeBootCard = handle;
@@ -54001,7 +54099,7 @@ var ipcServer = createIpcServer({
54001
54099
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
54002
54100
  if (Number.isInteger(receiverUid))
54003
54101
  allowedUids.push(receiverUid);
54004
- const socketPath = join36(STATE_DIR, "webhook.sock");
54102
+ const socketPath = join35(STATE_DIR, "webhook.sock");
54005
54103
  const webhookInject = (agentName3, inbound) => {
54006
54104
  const msg = inbound;
54007
54105
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -54251,16 +54349,11 @@ ${url}`;
54251
54349
  effectiveText = markdownToHtml(text);
54252
54350
  } else if (format === "markdownv2") {
54253
54351
  parseMode = "MarkdownV2";
54254
- effectiveText = escapeMarkdownV22(text);
54352
+ effectiveText = escapeMarkdownV2(text);
54255
54353
  } else {
54256
54354
  parseMode = undefined;
54257
54355
  effectiveText = text;
54258
54356
  }
54259
- {
54260
- const prefix = takeHandoffPrefix(format === "html" ? "html" : format === "markdownv2" ? "markdownv2" : "text");
54261
- if (prefix.length > 0)
54262
- effectiveText = prefix + effectiveText;
54263
- }
54264
54357
  assertAllowedChat(chat_id);
54265
54358
  let threadId = resolveThreadId(chat_id, args.message_thread_id);
54266
54359
  if (reply_to == null && quoteOptIn && HISTORY_ENABLED) {
@@ -54673,9 +54766,8 @@ async function executeStreamReply(args) {
54673
54766
  bot: lockedBot,
54674
54767
  retry: robustApiCall,
54675
54768
  markdownToHtml,
54676
- escapeMarkdownV2: escapeMarkdownV22,
54769
+ escapeMarkdownV2,
54677
54770
  repairEscapedWhitespace,
54678
- takeHandoffPrefix,
54679
54771
  assertAllowedChat,
54680
54772
  resolveThreadId,
54681
54773
  disableLinkPreview: access.disableLinkPreview !== false,
@@ -54705,8 +54797,8 @@ async function executeStreamReply(args) {
54705
54797
  progressDriver?.recordOutboundDelivered(args.chat_id, args.message_thread_id);
54706
54798
  } catch {}
54707
54799
  try {
54708
- const threadIdNum = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54709
- noteSignal(statusKey(args.chat_id, threadIdNum), Date.now());
54800
+ const threadIdNum2 = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
54801
+ noteSignal(statusKey(args.chat_id, threadIdNum2), Date.now());
54710
54802
  } catch {}
54711
54803
  }
54712
54804
  if (args.done === true && result.messageId != null && streamButtonMeta != null && streamButtonMeta.size > 0) {
@@ -54998,11 +55090,11 @@ async function executeSendGif(rawArgs) {
54998
55090
  };
54999
55091
  }
55000
55092
  async function publishToTelegraph(text, shortName, authorName) {
55001
- const accountPath = join36(STATE_DIR, "telegraph-account.json");
55093
+ const accountPath = join35(STATE_DIR, "telegraph-account.json");
55002
55094
  let account = null;
55003
55095
  try {
55004
- if (existsSync39(accountPath)) {
55005
- const raw = readFileSync36(accountPath, "utf-8");
55096
+ if (existsSync38(accountPath)) {
55097
+ const raw = readFileSync35(accountPath, "utf-8");
55006
55098
  const parsed = JSON.parse(raw);
55007
55099
  if (parsed.shortName && parsed.accessToken) {
55008
55100
  account = parsed;
@@ -55022,7 +55114,7 @@ async function publishToTelegraph(text, shortName, authorName) {
55022
55114
  account = created.value;
55023
55115
  try {
55024
55116
  mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
55025
- writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55117
+ writeFileSync24(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
55026
55118
  } catch (err) {
55027
55119
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
55028
55120
  `);
@@ -55266,7 +55358,7 @@ async function executeDownloadAttachment(args) {
55266
55358
  });
55267
55359
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
55268
55360
  assertInsideInbox(INBOX_DIR, dlPath);
55269
- writeFileSync25(dlPath, buf, { mode: 384 });
55361
+ writeFileSync24(dlPath, buf, { mode: 384 });
55270
55362
  return { content: [{ type: "text", text: dlPath }] };
55271
55363
  }
55272
55364
  async function executeEditMessage(args) {
@@ -55300,7 +55392,7 @@ async function executeEditMessage(args) {
55300
55392
  editText = markdownToHtml(editRawText);
55301
55393
  } else if (editFormat === "markdownv2") {
55302
55394
  editParseMode = "MarkdownV2";
55303
- editText = escapeMarkdownV22(editRawText);
55395
+ editText = escapeMarkdownV2(editRawText);
55304
55396
  } else {
55305
55397
  editParseMode = undefined;
55306
55398
  editText = editRawText;
@@ -55487,6 +55579,14 @@ function closeProgressLane(chatId, threadId) {
55487
55579
  }
55488
55580
  }
55489
55581
  }
55582
+ var FOREGROUND_SUBAGENT_ACCUM_MAX = 12;
55583
+ function composeTurnActivity(turn) {
55584
+ const childLines = [];
55585
+ for (const narrative of turn.foregroundSubAgents.values()) {
55586
+ childLines.push(...narrative);
55587
+ }
55588
+ return renderActivityFeedWithNested(turn.mirrorLines, childLines);
55589
+ }
55490
55590
  async function drainActivitySummary(turn) {
55491
55591
  try {
55492
55592
  while (turn.activityPendingRender !== turn.activityLastSentRender) {
@@ -55579,6 +55679,7 @@ function handleSessionEvent(ev) {
55579
55679
  activityPendingRender: null,
55580
55680
  activityLastSentRender: null,
55581
55681
  mirrorLines: [],
55682
+ foregroundSubAgents: new Map,
55582
55683
  answerStream: null,
55583
55684
  isDm: isDmChatId(ev.chatId)
55584
55685
  };
@@ -55673,7 +55774,7 @@ function handleSessionEvent(ev) {
55673
55774
  return;
55674
55775
  const rendered = appendActivityLabel(turn.mirrorLines, ev.label);
55675
55776
  if (rendered != null) {
55676
- turn.activityPendingRender = rendered;
55777
+ turn.activityPendingRender = composeTurnActivity(turn) ?? rendered;
55677
55778
  if (turn.activityInFlight == null) {
55678
55779
  turn.activityInFlight = drainActivitySummary(turn);
55679
55780
  }
@@ -56407,7 +56508,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
56407
56508
  const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
56408
56509
  const access2 = loadAccess();
56409
56510
  deferInterrupt = !interrupt.emptyBody && decideInterruptTiming({
56410
- safeBoundaryEnabled: access2.interruptSafeBoundary === true,
56511
+ safeBoundaryEnabled: resolveSafeBoundaryEnabled(access2.interruptSafeBoundary),
56411
56512
  midToolCall: toolFlightTracker.isMidToolCall()
56412
56513
  }) === "defer";
56413
56514
  process.stderr.write(`telegram gateway: interrupt-marker received chat_id=${chat_id} agent=${agentName3 ?? "-"} body_len=${interrupt.body.length} empty=${interrupt.emptyBody} defer=${deferInterrupt} in_flight=${toolFlightTracker.inFlightCount()}
@@ -57096,14 +57197,14 @@ function restartMarkerPath() {
57096
57197
  const agentDir = resolveAgentDirFromEnv();
57097
57198
  if (!agentDir)
57098
57199
  return null;
57099
- return join36(agentDir, "restart-pending.json");
57200
+ return join35(agentDir, "restart-pending.json");
57100
57201
  }
57101
57202
  function writeRestartMarker(marker) {
57102
57203
  const p = restartMarkerPath();
57103
57204
  if (!p)
57104
57205
  return;
57105
57206
  try {
57106
- writeFileSync25(p, JSON.stringify(marker));
57207
+ writeFileSync24(p, JSON.stringify(marker));
57107
57208
  lastPlannedRestartAt = Date.now();
57108
57209
  process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
57109
57210
  `);
@@ -57122,7 +57223,7 @@ function readRestartMarker() {
57122
57223
  if (!p)
57123
57224
  return null;
57124
57225
  try {
57125
- return JSON.parse(readFileSync36(p, "utf8"));
57226
+ return JSON.parse(readFileSync35(p, "utf8"));
57126
57227
  } catch {
57127
57228
  return null;
57128
57229
  }
@@ -57220,7 +57321,7 @@ var _dockerReachable;
57220
57321
  function isDockerReachable() {
57221
57322
  if (_dockerReachable !== undefined)
57222
57323
  return _dockerReachable;
57223
- if (!existsSync39("/var/run/docker.sock")) {
57324
+ if (!existsSync38("/var/run/docker.sock")) {
57224
57325
  _dockerReachable = false;
57225
57326
  return _dockerReachable;
57226
57327
  }
@@ -57237,12 +57338,12 @@ function _resetDockerReachableCache() {
57237
57338
  }
57238
57339
  function spawnSwitchroomDetached(args, onFailure) {
57239
57340
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
57240
- const logPath = join36(STATE_DIR, "detached-spawn.log");
57341
+ const logPath = join35(STATE_DIR, "detached-spawn.log");
57241
57342
  let outFd = null;
57242
57343
  try {
57243
57344
  mkdirSync26(STATE_DIR, { recursive: true });
57244
57345
  outFd = openSync8(logPath, "a");
57245
- writeFileSync25(logPath, `
57346
+ writeFileSync24(logPath, `
57246
57347
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
57247
57348
  `, { flag: "a" });
57248
57349
  } catch {}
@@ -57268,7 +57369,7 @@ function spawnSwitchroomDetached(args, onFailure) {
57268
57369
  return;
57269
57370
  let tail = "";
57270
57371
  try {
57271
- const full = readFileSync36(logPath, "utf8");
57372
+ const full = readFileSync35(logPath, "utf8");
57272
57373
  tail = full.split(`
57273
57374
  `).slice(-30).join(`
57274
57375
  `).trim();
@@ -57610,10 +57711,10 @@ bot.use(async (ctx, next) => {
57610
57711
  });
57611
57712
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
57612
57713
  try {
57613
- const auditPath = join36(homedir14(), ".switchroom", "vault-audit.log");
57614
- if (!existsSync39(auditPath))
57714
+ const auditPath = join35(homedir14(), ".switchroom", "vault-audit.log");
57715
+ if (!existsSync38(auditPath))
57615
57716
  return [];
57616
- const raw = readFileSync36(auditPath, "utf8");
57717
+ const raw = readFileSync35(auditPath, "utf8");
57617
57718
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
57618
57719
  } catch {
57619
57720
  return [];
@@ -57664,7 +57765,7 @@ async function buildAgentMetadata(agentName3) {
57664
57765
  try {
57665
57766
  const agentDir = resolveAgentDirFromEnv();
57666
57767
  if (agentDir) {
57667
- const raw = readFileSync36(join36(agentDir, ".claude", ".claude.json"), "utf8");
57768
+ const raw = readFileSync35(join35(agentDir, ".claude", ".claude.json"), "utf8");
57668
57769
  claudeJson = JSON.parse(raw);
57669
57770
  }
57670
57771
  } catch {}
@@ -57878,10 +57979,10 @@ bot.command("restart", async (ctx) => {
57878
57979
  function flushAgentHandoff(agentDir) {
57879
57980
  let removed = 0;
57880
57981
  for (const fname of [".handoff.md", ".handoff-topic"]) {
57881
- const p = join36(agentDir, fname);
57982
+ const p = join35(agentDir, fname);
57882
57983
  try {
57883
- if (existsSync39(p)) {
57884
- unlinkSync15(p);
57984
+ if (existsSync38(p)) {
57985
+ unlinkSync14(p);
57885
57986
  removed++;
57886
57987
  }
57887
57988
  } catch (err) {
@@ -57936,7 +58037,7 @@ async function handleNewOrResetCommand(ctx, kind) {
57936
58037
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
57937
58038
  if (agentDir != null) {
57938
58039
  try {
57939
- writeFileSync25(join36(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
58040
+ writeFileSync24(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
57940
58041
  `, "utf8");
57941
58042
  } catch (err) {
57942
58043
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -58296,16 +58397,16 @@ bot.command("interrupt", async (ctx) => {
58296
58397
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
58297
58398
  });
58298
58399
  var lockoutOps = {
58299
- readFileSync: (p, enc) => readFileSync36(p, enc),
58300
- writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
58301
- existsSync: (p) => existsSync39(p),
58400
+ readFileSync: (p, enc) => readFileSync35(p, enc),
58401
+ writeFileSync: (p, data, opts) => writeFileSync24(p, data, opts),
58402
+ existsSync: (p) => existsSync38(p),
58302
58403
  mkdirSync: (p, opts) => mkdirSync26(p, opts),
58303
- joinPath: (...parts) => join36(...parts)
58404
+ joinPath: (...parts) => join35(...parts)
58304
58405
  };
58305
58406
  var FLEET_FALLBACK_DEDUP_MS = 30000;
58306
58407
  function isAuthBrokerSocketReachable() {
58307
58408
  try {
58308
- return existsSync39(resolveAuthBrokerSocketPath2());
58409
+ return existsSync38(resolveAuthBrokerSocketPath2());
58309
58410
  } catch {
58310
58411
  return false;
58311
58412
  }
@@ -58366,7 +58467,7 @@ async function runCreditWatch() {
58366
58467
  if (!agentDir)
58367
58468
  return;
58368
58469
  const agentName3 = getMyAgentName();
58369
- const claudeConfigDir = join36(agentDir, ".claude");
58470
+ const claudeConfigDir = join35(agentDir, ".claude");
58370
58471
  const stateDir = STATE_DIR;
58371
58472
  const reason = readClaudeJsonOverage(claudeConfigDir);
58372
58473
  const prev = loadCreditState(stateDir);
@@ -58669,10 +58770,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
58669
58770
  return;
58670
58771
  }
58671
58772
  const { token, id } = result;
58672
- const tokenPath = join36(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58773
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
58673
58774
  try {
58674
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58675
- writeFileSync25(tokenPath, token, { mode: 384 });
58775
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58776
+ writeFileSync24(tokenPath, token, { mode: 384 });
58676
58777
  } catch (err) {
58677
58778
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58678
58779
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -58748,10 +58849,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
58748
58849
  return;
58749
58850
  }
58750
58851
  const { token, id } = result;
58751
- const tokenPath = join36(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58852
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
58752
58853
  try {
58753
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58754
- writeFileSync25(tokenPath, token, { mode: 384 });
58854
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58855
+ writeFileSync24(tokenPath, token, { mode: 384 });
58755
58856
  } catch (err) {
58756
58857
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
58757
58858
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
@@ -59226,10 +59327,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
59226
59327
  return;
59227
59328
  }
59228
59329
  const { token, id } = result;
59229
- const tokenPath = join36(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59330
+ const tokenPath = join35(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
59230
59331
  try {
59231
- mkdirSync26(join36(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59232
- writeFileSync25(tokenPath, token, { mode: 384 });
59332
+ mkdirSync26(join35(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
59333
+ writeFileSync24(tokenPath, token, { mode: 384 });
59233
59334
  } catch (err) {
59234
59335
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
59235
59336
  return;
@@ -60077,7 +60178,7 @@ bot.command("usage", async (ctx) => {
60077
60178
  await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
60078
60179
  return;
60079
60180
  }
60080
- const result = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
60181
+ const result = await fetchQuota2({ claudeConfigDir: join35(agentDir, ".claude") });
60081
60182
  if (!result.ok) {
60082
60183
  await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
60083
60184
  return;
@@ -60517,7 +60618,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
60517
60618
  const unifiedDiff = (() => {
60518
60619
  try {
60519
60620
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
60520
- const raw = readFileSync36(cfgPath, "utf8");
60621
+ const raw = readFileSync35(cfgPath, "utf8");
60521
60622
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
60522
60623
  } catch (err) {
60523
60624
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -60659,7 +60760,7 @@ bot.on("message:photo", async (ctx) => {
60659
60760
  });
60660
60761
  mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
60661
60762
  assertInsideInbox(INBOX_DIR, dlPath);
60662
- writeFileSync25(dlPath, buf, { mode: 384 });
60763
+ writeFileSync24(dlPath, buf, { mode: 384 });
60663
60764
  return dlPath;
60664
60765
  } catch (err) {
60665
60766
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -60699,8 +60800,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
60699
60800
  let apiKey = null;
60700
60801
  try {
60701
60802
  const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
60702
- if (existsSync39(path)) {
60703
- apiKey = readFileSync36(path, "utf-8").trim();
60803
+ if (existsSync38(path)) {
60804
+ apiKey = readFileSync35(path, "utf-8").trim();
60704
60805
  }
60705
60806
  } catch (err) {
60706
60807
  process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
@@ -61335,7 +61436,6 @@ process.on("SIGINT", () => void shutdown("SIGINT"));
61335
61436
  `) });
61336
61437
  }
61337
61438
  }
61338
- initHandoffContinuity();
61339
61439
  process.on("unhandledRejection", (err) => {
61340
61440
  const action = classifyRejection(err);
61341
61441
  process.stderr.write(`telegram gateway: unhandled rejection (${action}): ${err}
@@ -61556,7 +61656,7 @@ var didOneTimeSetup = false;
61556
61656
  return;
61557
61657
  }
61558
61658
  })();
61559
- const resolvedAgentDirForBootCard = agentDir ?? join36(homedir14(), ".switchroom", "agents", agentSlug);
61659
+ const resolvedAgentDirForBootCard = agentDir ?? join35(homedir14(), ".switchroom", "agents", agentSlug);
61560
61660
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
61561
61661
  agentName: agentDisplayName,
61562
61662
  agentSlug,
@@ -61570,7 +61670,7 @@ var didOneTimeSetup = false;
61570
61670
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
61571
61671
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
61572
61672
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
61573
- configSnapshotPath: join36(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61673
+ configSnapshotPath: join35(resolvedAgentDirForBootCard, ".config-snapshot.json"),
61574
61674
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
61575
61675
  }, ackMsgId);
61576
61676
  activeBootCard = handle;
@@ -61654,7 +61754,8 @@ var didOneTimeSetup = false;
61654
61754
  if (streamMode === "checklist") {
61655
61755
  const watcherAgentDir = resolveAgentDirFromEnv();
61656
61756
  if (watcherAgentDir != null) {
61657
- const workerFeedEnabled = process.env.SWITCHROOM_WORKER_ACTIVITY_FEED === "1";
61757
+ const workerFeedEnabled = isWorkerActivityFeedEnabled(process.env.SWITCHROOM_WORKER_ACTIVITY_FEED);
61758
+ const foregroundNestingEnabled = process.env.SWITCHROOM_FOREGROUND_SUBAGENT_NESTING !== "0";
61658
61759
  const workerActivityFeed = createWorkerActivityFeed({
61659
61760
  bot: {
61660
61761
  sendMessage: async (cid, text, sendOpts) => {
@@ -61714,6 +61815,19 @@ var didOneTimeSetup = false;
61714
61815
  } catch {}
61715
61816
  }
61716
61817
  const isBackground = dispatch.isBackground;
61818
+ if (!isBackground) {
61819
+ const turn = currentTurn;
61820
+ if (turn != null && turn.foregroundSubAgents.delete(agentId) && !turn.replyCalled) {
61821
+ const rendered = composeTurnActivity(turn);
61822
+ if (rendered != null) {
61823
+ turn.activityPendingRender = rendered;
61824
+ if (turn.activityInFlight == null) {
61825
+ turn.activityInFlight = drainActivitySummary(turn);
61826
+ }
61827
+ }
61828
+ }
61829
+ return;
61830
+ }
61717
61831
  if (workerFeedEnabled) {
61718
61832
  workerActivityFeed.finish(agentId, {
61719
61833
  description: dispatch.feedDescription,
@@ -61774,8 +61888,35 @@ var didOneTimeSetup = false;
61774
61888
  } catch {}
61775
61889
  }
61776
61890
  const isBackground = dispatch.isBackground;
61777
- if (!isBackground)
61891
+ if (!isBackground) {
61892
+ if (!foregroundNestingEnabled)
61893
+ return;
61894
+ const turn = currentTurn;
61895
+ if (turn == null || turn.replyCalled)
61896
+ return;
61897
+ const child = latestSummary.trim().slice(0, 120);
61898
+ if (child.length === 0)
61899
+ return;
61900
+ let narrative = turn.foregroundSubAgents.get(agentId);
61901
+ if (narrative == null) {
61902
+ narrative = [];
61903
+ turn.foregroundSubAgents.set(agentId, narrative);
61904
+ }
61905
+ if (narrative[narrative.length - 1] !== child) {
61906
+ narrative.push(child);
61907
+ if (narrative.length > FOREGROUND_SUBAGENT_ACCUM_MAX) {
61908
+ narrative.splice(0, narrative.length - FOREGROUND_SUBAGENT_ACCUM_MAX);
61909
+ }
61910
+ }
61911
+ const rendered = composeTurnActivity(turn);
61912
+ if (rendered != null) {
61913
+ turn.activityPendingRender = rendered;
61914
+ if (turn.activityInFlight == null) {
61915
+ turn.activityInFlight = drainActivitySummary(turn);
61916
+ }
61917
+ }
61778
61918
  return;
61919
+ }
61779
61920
  if (workerFeedEnabled) {
61780
61921
  workerActivityFeed.update(agentId, fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
61781
61922
  description: dispatch.feedDescription,