switchroom 0.14.9 → 0.14.10

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.
@@ -23742,6 +23742,7 @@ var init_schema = __esm(() => {
23742
23742
  webhook_rate_limit: exports_external.object({
23743
23743
  rpm: exports_external.number().int().positive()
23744
23744
  }).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
23745
+ webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
23745
23746
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
23746
23747
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
23747
23748
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
@@ -25161,6 +25162,59 @@ function formatResetRelative(target, now = new Date) {
25161
25162
  }
25162
25163
  var OAUTH_BETA = "oauth-2025-04-20", DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)", DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
25163
25164
  var init_quota_check = () => {};
25165
+
25166
+ // ../src/vault/broker/peercred-ffi.ts
25167
+ function getPeerCred(fd) {
25168
+ if (process.platform !== "linux")
25169
+ return null;
25170
+ try {
25171
+ const ffi = __require("bun:ffi");
25172
+ const { dlopen, FFIType, ptr } = ffi;
25173
+ const SOL_SOCKET = 1;
25174
+ const SO_PEERCRED = 17;
25175
+ const UCRED_SIZE = 12;
25176
+ const cache = getPeerCred;
25177
+ const lib = cache._lib ?? (() => {
25178
+ const candidates = ["libc.so.6", "libc.so"];
25179
+ const symbolSpec = {
25180
+ getsockopt: {
25181
+ args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
25182
+ returns: FFIType.i32
25183
+ }
25184
+ };
25185
+ const errors3 = [];
25186
+ for (const name of candidates) {
25187
+ try {
25188
+ const opened = dlopen(name, symbolSpec);
25189
+ cache._lib = opened;
25190
+ return opened;
25191
+ } catch (e) {
25192
+ errors3.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
25193
+ }
25194
+ }
25195
+ process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors3.join("; ")}); falling back to ss-parsing.
25196
+ `);
25197
+ throw new Error("no libc candidate could be opened");
25198
+ })();
25199
+ const credBuf = new ArrayBuffer(UCRED_SIZE);
25200
+ const lenBuf = new Uint32Array(1);
25201
+ lenBuf[0] = UCRED_SIZE;
25202
+ const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
25203
+ if (rc !== 0)
25204
+ return null;
25205
+ if (lenBuf[0] !== UCRED_SIZE)
25206
+ return null;
25207
+ const view = new DataView(credBuf);
25208
+ return {
25209
+ pid: view.getInt32(0, true),
25210
+ uid: view.getInt32(4, true),
25211
+ gid: view.getInt32(8, true)
25212
+ };
25213
+ } catch {
25214
+ return null;
25215
+ }
25216
+ }
25217
+
25164
25218
  // ../src/vault/broker/peercred.ts
25165
25219
  function isReservedAgentName(name) {
25166
25220
  return RESERVED_AGENT_NAMES.has(name);
@@ -27869,19 +27923,19 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
27869
27923
  }
27870
27924
 
27871
27925
  // gateway/quota-cache.ts
27872
- import { existsSync as existsSync23, readFileSync as readFileSync22, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
27873
- import { join as join20, dirname as dirname8 } from "path";
27926
+ import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14 } from "fs";
27927
+ import { join as join23, dirname as dirname8 } from "path";
27874
27928
  function defaultCachePath() {
27875
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join20(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
27929
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join23(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
27876
27930
  }
27877
27931
  function readQuotaCache(opts = {}) {
27878
27932
  const path = opts.path ?? defaultCachePath();
27879
27933
  const now = opts.now ?? Date.now();
27880
- if (!existsSync23(path))
27934
+ if (!existsSync26(path))
27881
27935
  return null;
27882
27936
  let entry;
27883
27937
  try {
27884
- entry = JSON.parse(readFileSync22(path, "utf8"));
27938
+ entry = JSON.parse(readFileSync24(path, "utf8"));
27885
27939
  } catch {
27886
27940
  return null;
27887
27941
  }
@@ -27907,8 +27961,8 @@ function writeQuotaCache(result, opts = {}) {
27907
27961
  result
27908
27962
  };
27909
27963
  try {
27910
- mkdirSync11(dirname8(path), { recursive: true });
27911
- writeFileSync13(path, JSON.stringify(entry, null, 2), { mode: 384 });
27964
+ mkdirSync14(dirname8(path), { recursive: true });
27965
+ writeFileSync15(path, JSON.stringify(entry, null, 2), { mode: 384 });
27912
27966
  } catch {}
27913
27967
  }
27914
27968
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -27918,8 +27972,8 @@ var init_quota_cache = __esm(() => {
27918
27972
  });
27919
27973
 
27920
27974
  // gateway/boot-probes.ts
27921
- import { readFileSync as readFileSync23, readdirSync as readdirSync4, existsSync as existsSync24 } from "fs";
27922
- import { join as join21 } from "path";
27975
+ import { readFileSync as readFileSync25, readdirSync as readdirSync4, existsSync as existsSync27 } from "fs";
27976
+ import { join as join24 } from "path";
27923
27977
  import { execFile as execFileCb } from "child_process";
27924
27978
  import { promisify as promisify3 } from "util";
27925
27979
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -27961,11 +28015,11 @@ function mapPlan(billingType, hasExtra) {
27961
28015
  }
27962
28016
  async function probeAccount(agentDir) {
27963
28017
  return withTimeout("Account", (async () => {
27964
- const claudeDir = join21(agentDir, ".claude");
27965
- const claudeJsonPath = join21(claudeDir, ".claude.json");
28018
+ const claudeDir = join24(agentDir, ".claude");
28019
+ const claudeJsonPath = join24(claudeDir, ".claude.json");
27966
28020
  let cfg = {};
27967
28021
  try {
27968
- const raw = readFileSync23(claudeJsonPath, "utf8");
28022
+ const raw = readFileSync25(claudeJsonPath, "utf8");
27969
28023
  cfg = JSON.parse(raw);
27970
28024
  } catch {
27971
28025
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -27983,12 +28037,12 @@ async function probeAccount(agentDir) {
27983
28037
  let tokenStr = "";
27984
28038
  let status = "ok";
27985
28039
  for (const candidate of [
27986
- join21(claudeDir, ".oauth-token.meta.json"),
27987
- join21(claudeDir, "accounts", "default", ".oauth-token.meta.json")
28040
+ join24(claudeDir, ".oauth-token.meta.json"),
28041
+ join24(claudeDir, "accounts", "default", ".oauth-token.meta.json")
27988
28042
  ]) {
27989
- if (existsSync24(candidate)) {
28043
+ if (existsSync27(candidate)) {
27990
28044
  try {
27991
- const meta = JSON.parse(readFileSync23(candidate, "utf8"));
28045
+ const meta = JSON.parse(readFileSync25(candidate, "utf8"));
27992
28046
  if (meta.expiresAt) {
27993
28047
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
27994
28048
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -28163,9 +28217,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28163
28217
  if (!cgroup)
28164
28218
  return null;
28165
28219
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
28166
- if (!existsSync24(procsPath))
28220
+ if (!existsSync27(procsPath))
28167
28221
  return null;
28168
- const pidsRaw = readFileSync23(procsPath, "utf-8");
28222
+ const pidsRaw = readFileSync25(procsPath, "utf-8");
28169
28223
  const pids = pidsRaw.split(`
28170
28224
  `).map((s) => s.trim()).filter(Boolean);
28171
28225
  if (pids.length === 0)
@@ -28178,7 +28232,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28178
28232
  let rss = 0;
28179
28233
  let comm = "";
28180
28234
  try {
28181
- const status = readFileSync23(`/proc/${pid}/status`, "utf-8");
28235
+ const status = readFileSync25(`/proc/${pid}/status`, "utf-8");
28182
28236
  const rssLine = status.split(`
28183
28237
  `).find((l) => l.startsWith("VmRSS:"));
28184
28238
  if (rssLine) {
@@ -28190,7 +28244,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
28190
28244
  continue;
28191
28245
  }
28192
28246
  try {
28193
- comm = readFileSync23(`/proc/${pid}/comm`, "utf-8").trim();
28247
+ comm = readFileSync25(`/proc/${pid}/comm`, "utf-8").trim();
28194
28248
  } catch {}
28195
28249
  candidates.push({ pid, rss, comm });
28196
28250
  }
@@ -28370,9 +28424,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
28370
28424
  let claudeDirForProbe = null;
28371
28425
  for (const candidate of [
28372
28426
  claudeConfigDir,
28373
- join21(claudeConfigDir, "accounts", "default")
28427
+ join24(claudeConfigDir, "accounts", "default")
28374
28428
  ]) {
28375
- if (existsSync24(join21(candidate, ".oauth-token"))) {
28429
+ if (existsSync27(join24(candidate, ".oauth-token"))) {
28376
28430
  claudeDirForProbe = candidate;
28377
28431
  break;
28378
28432
  }
@@ -28437,7 +28491,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
28437
28491
  }
28438
28492
  function readContainerBootTimeMsForProbe() {
28439
28493
  try {
28440
- const stat1 = readFileSync23("/proc/1/stat", "utf8");
28494
+ const stat1 = readFileSync25("/proc/1/stat", "utf8");
28441
28495
  const lastParen = stat1.lastIndexOf(")");
28442
28496
  if (lastParen < 0)
28443
28497
  return null;
@@ -28445,7 +28499,7 @@ function readContainerBootTimeMsForProbe() {
28445
28499
  const starttimeTicks = Number(after[19]);
28446
28500
  if (!Number.isFinite(starttimeTicks))
28447
28501
  return null;
28448
- const procStat = readFileSync23("/proc/stat", "utf8");
28502
+ const procStat = readFileSync25("/proc/stat", "utf8");
28449
28503
  const btimeLine = procStat.split(`
28450
28504
  `).find((l) => l.startsWith("btime "));
28451
28505
  if (!btimeLine)
@@ -28543,7 +28597,7 @@ async function probeUds(label, socketPath, opts = {}) {
28543
28597
  }
28544
28598
  return withTimeout(label, (async () => {
28545
28599
  if (!opts.connectImpl) {
28546
- if (!existsSync24(socketPath)) {
28600
+ if (!existsSync27(socketPath)) {
28547
28601
  return {
28548
28602
  status: "fail",
28549
28603
  label,
@@ -28578,9 +28632,9 @@ function udsNextStep(label, kind) {
28578
28632
  return `${label} socket not reachable \u2014 bring up the daemon with \`docker compose up -d ${svc}\` (or check \`docker compose ps\`)`;
28579
28633
  }
28580
28634
  function defaultUdsConnect(socketPath) {
28581
- const net4 = __require("net");
28635
+ const net5 = __require("net");
28582
28636
  return new Promise((resolve6, reject) => {
28583
- const sock = net4.createConnection({ path: socketPath });
28637
+ const sock = net5.createConnection({ path: socketPath });
28584
28638
  const t = setTimeout(() => {
28585
28639
  sock.destroy();
28586
28640
  reject(new Error("connect timeout"));
@@ -28607,7 +28661,7 @@ async function probeSkills(agentDir, opts = {}) {
28607
28661
  return withTimeout("Skills", (async () => {
28608
28662
  const fs2 = opts.fs ?? realSkillsFs;
28609
28663
  const max = opts.maxNamesShown ?? 3;
28610
- const skillsDir = join21(agentDir, ".claude", "skills");
28664
+ const skillsDir = join24(agentDir, ".claude", "skills");
28611
28665
  if (!fs2.exists(skillsDir)) {
28612
28666
  return { status: "ok", label: "Skills", detail: "no skills dir" };
28613
28667
  }
@@ -28622,17 +28676,17 @@ async function probeSkills(agentDir, opts = {}) {
28622
28676
  }
28623
28677
  const dangling = [];
28624
28678
  for (const name of entries) {
28625
- const skillPath = join21(skillsDir, name);
28679
+ const skillPath = join24(skillsDir, name);
28626
28680
  if (!fs2.exists(skillPath)) {
28627
28681
  dangling.push(name);
28628
28682
  continue;
28629
28683
  }
28630
- const skillMd = join21(skillPath, "SKILL.md");
28684
+ const skillMd = join24(skillPath, "SKILL.md");
28631
28685
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
28632
28686
  continue;
28633
28687
  }
28634
28688
  }
28635
- const overlayDir = opts.overlaySkillsDir ?? join21(agentDir, "skills.d");
28689
+ const overlayDir = opts.overlaySkillsDir ?? join24(agentDir, "skills.d");
28636
28690
  const overlaySlugs = new Set;
28637
28691
  if (fs2.exists(overlayDir)) {
28638
28692
  let overlayEntries = [];
@@ -28679,24 +28733,24 @@ var init_boot_probes = __esm(() => {
28679
28733
  execFile3 = promisify3(execFileCb);
28680
28734
  realProcFs = {
28681
28735
  readdir: (p) => readdirSync4(p),
28682
- readFile: (p) => readFileSync23(p, "utf-8")
28736
+ readFile: (p) => readFileSync25(p, "utf-8")
28683
28737
  };
28684
28738
  realSchedulerFs = {
28685
- readFile: (p) => readFileSync23(p, "utf-8"),
28739
+ readFile: (p) => readFileSync25(p, "utf-8"),
28686
28740
  mtimeMs: (p) => {
28687
28741
  const { statSync: statSync7 } = __require("fs");
28688
28742
  return statSync7(p).mtimeMs;
28689
28743
  },
28690
- exists: (p) => existsSync24(p)
28744
+ exists: (p) => existsSync27(p)
28691
28745
  };
28692
28746
  realSkillsFs = {
28693
28747
  readdir: (p) => readdirSync4(p),
28694
- exists: (p) => existsSync24(p)
28748
+ exists: (p) => existsSync27(p)
28695
28749
  };
28696
28750
  });
28697
28751
 
28698
28752
  // gateway/boot-issue-cache.ts
28699
- import { existsSync as existsSync25, readFileSync as readFileSync24, writeFileSync as writeFileSync14, mkdirSync as mkdirSync12, renameSync as renameSync9 } from "fs";
28753
+ import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, renameSync as renameSync9 } from "fs";
28700
28754
  import { dirname as dirname9 } from "path";
28701
28755
  function fingerprintProbe(key, r) {
28702
28756
  if (r.status === "ok")
@@ -28776,11 +28830,11 @@ function diffProbes(probes, cache, opts = {}) {
28776
28830
  return out;
28777
28831
  }
28778
28832
  function loadCache(path, now = Date.now) {
28779
- if (!existsSync25(path))
28833
+ if (!existsSync28(path))
28780
28834
  return { ...EMPTY_CACHE, probes: {} };
28781
28835
  let raw;
28782
28836
  try {
28783
- raw = readFileSync24(path, "utf-8");
28837
+ raw = readFileSync26(path, "utf-8");
28784
28838
  } catch {
28785
28839
  return { ...EMPTY_CACHE, probes: {} };
28786
28840
  }
@@ -28823,9 +28877,9 @@ function applyAndSave(path, cache, diff) {
28823
28877
  }
28824
28878
  }
28825
28879
  try {
28826
- mkdirSync12(dirname9(path), { recursive: true });
28880
+ mkdirSync15(dirname9(path), { recursive: true });
28827
28881
  const tmp = `${path}.tmp`;
28828
- writeFileSync14(tmp, JSON.stringify(next), { mode: 384 });
28882
+ writeFileSync16(tmp, JSON.stringify(next), { mode: 384 });
28829
28883
  renameSync9(tmp, path);
28830
28884
  } catch {}
28831
28885
  return next;
@@ -28839,7 +28893,7 @@ var init_boot_issue_cache = __esm(() => {
28839
28893
 
28840
28894
  // gateway/config-snapshot.ts
28841
28895
  import { createHash as createHash2 } from "crypto";
28842
- import { existsSync as existsSync26, readFileSync as readFileSync25, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, renameSync as renameSync10 } from "fs";
28896
+ import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16, renameSync as renameSync10 } from "fs";
28843
28897
  import { dirname as dirname10 } from "path";
28844
28898
  function hashStringArray(items) {
28845
28899
  if (!items || items.length === 0)
@@ -28904,11 +28958,11 @@ function renderConfigChangeDim(dim) {
28904
28958
  }
28905
28959
  }
28906
28960
  function loadSnapshot(path, now = Date.now) {
28907
- if (!existsSync26(path))
28961
+ if (!existsSync29(path))
28908
28962
  return null;
28909
28963
  let raw;
28910
28964
  try {
28911
- raw = readFileSync25(path, "utf-8");
28965
+ raw = readFileSync27(path, "utf-8");
28912
28966
  } catch {
28913
28967
  return null;
28914
28968
  }
@@ -28938,9 +28992,9 @@ function loadSnapshot(path, now = Date.now) {
28938
28992
  }
28939
28993
  function persistSnapshot(path, snapshot) {
28940
28994
  try {
28941
- mkdirSync13(dirname10(path), { recursive: true });
28995
+ mkdirSync16(dirname10(path), { recursive: true });
28942
28996
  const tmp = `${path}.tmp`;
28943
- writeFileSync15(tmp, JSON.stringify(snapshot), { mode: 384 });
28997
+ writeFileSync17(tmp, JSON.stringify(snapshot), { mode: 384 });
28944
28998
  renameSync10(tmp, path);
28945
28999
  } catch {}
28946
29000
  }
@@ -28956,7 +29010,7 @@ __export(exports_boot_card, {
28956
29010
  renderBootCard: () => renderBootCard,
28957
29011
  renderAccountRows: () => renderAuthLine
28958
29012
  });
28959
- import { join as join22 } from "path";
29013
+ import { join as join25 } from "path";
28960
29014
  function resolvePersonaName(slug, loadConfig3) {
28961
29015
  try {
28962
29016
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -29046,7 +29100,7 @@ function renderBootCard(opts) {
29046
29100
  `);
29047
29101
  }
29048
29102
  async function runAllProbes(opts) {
29049
- const claudeDir = join22(opts.agentDir, ".claude");
29103
+ const claudeDir = join25(opts.agentDir, ".claude");
29050
29104
  const probes = {};
29051
29105
  const slug = opts.agentSlug ?? opts.agentName;
29052
29106
  await Promise.allSettled([
@@ -29664,12 +29718,12 @@ var init_flock = () => {};
29664
29718
  // ../src/vault/vault.ts
29665
29719
  import { randomBytes as randomBytes5, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
29666
29720
  import {
29667
- readFileSync as readFileSync33,
29668
- writeFileSync as writeFileSync21,
29669
- existsSync as existsSync34,
29721
+ readFileSync as readFileSync35,
29722
+ writeFileSync as writeFileSync23,
29723
+ existsSync as existsSync37,
29670
29724
  renameSync as renameSync12,
29671
- mkdirSync as mkdirSync20,
29672
- unlinkSync as unlinkSync12,
29725
+ mkdirSync as mkdirSync23,
29726
+ unlinkSync as unlinkSync13,
29673
29727
  lstatSync,
29674
29728
  realpathSync
29675
29729
  } from "node:fs";
@@ -29704,12 +29758,12 @@ function normalizeSecrets(raw) {
29704
29758
  return out;
29705
29759
  }
29706
29760
  function openVault(passphrase, vaultPath) {
29707
- if (!existsSync34(vaultPath)) {
29761
+ if (!existsSync37(vaultPath)) {
29708
29762
  throw new VaultError(`Vault file not found: ${vaultPath}`);
29709
29763
  }
29710
29764
  let vaultFile;
29711
29765
  try {
29712
- vaultFile = JSON.parse(readFileSync33(vaultPath, "utf8"));
29766
+ vaultFile = JSON.parse(readFileSync35(vaultPath, "utf8"));
29713
29767
  } catch {
29714
29768
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
29715
29769
  }
@@ -29754,16 +29808,16 @@ var init_vault = __esm(() => {
29754
29808
 
29755
29809
  // ../src/vault/resolver.ts
29756
29810
  import {
29757
- chmodSync as chmodSync4,
29811
+ chmodSync as chmodSync5,
29758
29812
  closeSync as closeSync7,
29759
- mkdirSync as mkdirSync21,
29813
+ mkdirSync as mkdirSync24,
29760
29814
  mkdtempSync as mkdtempSync2,
29761
29815
  openSync as openSync7,
29762
29816
  rmSync as rmSync3,
29763
29817
  statSync as statSync11,
29764
29818
  writeSync as writeSync2
29765
29819
  } from "node:fs";
29766
- import { join as join32 } from "node:path";
29820
+ import { join as join35 } from "node:path";
29767
29821
  import { tmpdir } from "node:os";
29768
29822
  import { constants as fsConstants } from "node:fs";
29769
29823
  function isVaultReference(value) {
@@ -29815,13 +29869,13 @@ function materializationRoot() {
29815
29869
  return cachedRoot;
29816
29870
  const xdg = process.env.XDG_RUNTIME_DIR;
29817
29871
  if (xdg) {
29818
- const base = join32(xdg, "switchroom", "vault");
29819
- mkdirSync21(base, { recursive: true, mode: 448 });
29820
- cachedRoot = mkdtempSync2(join32(base, "run-"));
29872
+ const base = join35(xdg, "switchroom", "vault");
29873
+ mkdirSync24(base, { recursive: true, mode: 448 });
29874
+ cachedRoot = mkdtempSync2(join35(base, "run-"));
29821
29875
  } else {
29822
- cachedRoot = mkdtempSync2(join32(tmpdir(), "switchroom-vault-"));
29876
+ cachedRoot = mkdtempSync2(join35(tmpdir(), "switchroom-vault-"));
29823
29877
  }
29824
- chmodSync4(cachedRoot, 448);
29878
+ chmodSync5(cachedRoot, 448);
29825
29879
  return cachedRoot;
29826
29880
  }
29827
29881
  function writeFileExclusive(filePath, content) {
@@ -29834,14 +29888,14 @@ function writeFileExclusive(filePath, content) {
29834
29888
  }
29835
29889
  }
29836
29890
  function materializeFilesEntry(key, files) {
29837
- const dir = join32(materializationRoot(), key);
29891
+ const dir = join35(materializationRoot(), key);
29838
29892
  if (materializedDirs.has(dir)) {
29839
29893
  try {
29840
29894
  rmSync3(dir, { recursive: true, force: true });
29841
29895
  } catch {}
29842
29896
  }
29843
- mkdirSync21(dir, { recursive: true, mode: 448 });
29844
- chmodSync4(dir, 448);
29897
+ mkdirSync24(dir, { recursive: true, mode: 448 });
29898
+ chmodSync5(dir, 448);
29845
29899
  const st = statSync11(dir);
29846
29900
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
29847
29901
  throw new Error(`Refusing to materialize vault entry: ${dir} not owned by caller`);
@@ -29850,7 +29904,7 @@ function materializeFilesEntry(key, files) {
29850
29904
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
29851
29905
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
29852
29906
  }
29853
- const filePath = join32(dir, filename);
29907
+ const filePath = join35(dir, filename);
29854
29908
  const content = encoding === "base64" ? Buffer.from(value, "base64") : value;
29855
29909
  writeFileExclusive(filePath, content);
29856
29910
  }
@@ -29983,7 +30037,7 @@ __export(exports_materialize_bot_token, {
29983
30037
  materializeBotToken: () => materializeBotToken,
29984
30038
  BotTokenMaterializeError: () => BotTokenMaterializeError
29985
30039
  });
29986
- import { existsSync as existsSync35 } from "node:fs";
30040
+ import { existsSync as existsSync38 } from "node:fs";
29987
30041
  function pickConfiguredToken(config, agentName3) {
29988
30042
  if (agentName3) {
29989
30043
  const agent = config.agents?.[agentName3];
@@ -29997,7 +30051,7 @@ function tryDirectVaultRead(ref, config, passphrase) {
29997
30051
  if (!passphrase)
29998
30052
  return null;
29999
30053
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
30000
- if (!existsSync35(vaultPath))
30054
+ if (!existsSync38(vaultPath))
30001
30055
  return null;
30002
30056
  try {
30003
30057
  const secrets = openVault(passphrase, vaultPath);
@@ -30307,7 +30361,7 @@ __export(exports_tmux, {
30307
30361
  captureAgentPane: () => captureAgentPane
30308
30362
  });
30309
30363
  import { execFileSync as execFileSync4 } from "node:child_process";
30310
- import { mkdirSync as mkdirSync22, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync22 } from "node:fs";
30364
+ import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync14, writeFileSync as writeFileSync24 } from "node:fs";
30311
30365
  import { resolve as resolve7 } from "node:path";
30312
30366
  function captureAgentPane(opts) {
30313
30367
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -30319,7 +30373,7 @@ function captureAgentPane(opts) {
30319
30373
  const reasonSlug = sanitizeReason(reason);
30320
30374
  const outPath = resolve7(outDir, `${ts}-${reasonSlug}.txt`);
30321
30375
  try {
30322
- mkdirSync22(outDir, { recursive: true, mode: 493 });
30376
+ mkdirSync25(outDir, { recursive: true, mode: 493 });
30323
30377
  } catch (err) {
30324
30378
  const msg = `mkdir crash-reports failed: ${err.message}`;
30325
30379
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -30353,7 +30407,7 @@ function captureAgentPane(opts) {
30353
30407
  ` + `
30354
30408
  `;
30355
30409
  try {
30356
- writeFileSync22(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30410
+ writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30357
30411
  mode: 420
30358
30412
  });
30359
30413
  } catch (err) {
@@ -30421,7 +30475,7 @@ function pruneOldReports(dir, retain) {
30421
30475
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
30422
30476
  for (const stale of files.slice(retain)) {
30423
30477
  try {
30424
- unlinkSync13(stale.full);
30478
+ unlinkSync14(stale.full);
30425
30479
  } catch {}
30426
30480
  }
30427
30481
  }
@@ -30695,23 +30749,23 @@ var import_runner2 = __toESM(require_mod3(), 1);
30695
30749
  import { randomBytes as randomBytes6 } from "crypto";
30696
30750
  import { execFileSync as execFileSync5, execSync as execSync2, spawn as spawn2 } from "child_process";
30697
30751
  import {
30698
- readFileSync as readFileSync34,
30699
- writeFileSync as writeFileSync23,
30700
- mkdirSync as mkdirSync23,
30752
+ readFileSync as readFileSync36,
30753
+ writeFileSync as writeFileSync25,
30754
+ mkdirSync as mkdirSync26,
30701
30755
  readdirSync as readdirSync7,
30702
30756
  rmSync as rmSync4,
30703
30757
  statSync as statSync13,
30704
30758
  renameSync as renameSync13,
30705
30759
  realpathSync as realpathSync2,
30706
- chmodSync as chmodSync5,
30760
+ chmodSync as chmodSync6,
30707
30761
  openSync as openSync8,
30708
30762
  closeSync as closeSync8,
30709
- existsSync as existsSync36,
30710
- unlinkSync as unlinkSync14,
30711
- appendFileSync as appendFileSync3
30763
+ existsSync as existsSync39,
30764
+ unlinkSync as unlinkSync15,
30765
+ appendFileSync as appendFileSync5
30712
30766
  } from "fs";
30713
- import { homedir as homedir12 } from "os";
30714
- import { join as join33, extname, sep as sep3, basename as basename7 } from "path";
30767
+ import { homedir as homedir14 } from "os";
30768
+ import { join as join36, extname, sep as sep3, basename as basename7 } from "path";
30715
30769
 
30716
30770
  // plugin-logger.ts
30717
30771
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -44616,8 +44670,643 @@ function shouldSweepChatAtBoot(chatId) {
44616
44670
  return n < 0;
44617
44671
  }
44618
44672
 
44673
+ // gateway/webhook-ingest-server.ts
44674
+ import net4 from "node:net";
44675
+ import { chmodSync as chmodSync3, existsSync as existsSync21, unlinkSync as unlinkSync5 } from "node:fs";
44676
+ var MAX_REQUEST_BYTES = 1024 * 1024;
44677
+ function fdOf(conn) {
44678
+ const handle = conn._handle;
44679
+ if (!handle || typeof handle.fd !== "number" || handle.fd < 0)
44680
+ return null;
44681
+ return handle.fd;
44682
+ }
44683
+ function startWebhookIngestServer(opts) {
44684
+ const log = opts.log ?? ((s) => process.stderr.write(s));
44685
+ const allowed = new Set(opts.allowedUids);
44686
+ try {
44687
+ if (existsSync21(opts.socketPath))
44688
+ unlinkSync5(opts.socketPath);
44689
+ } catch (err) {
44690
+ log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
44691
+ `);
44692
+ }
44693
+ const server = net4.createServer((conn) => {
44694
+ const fd = fdOf(conn);
44695
+ const cred = fd !== null ? getPeerCred(fd) : null;
44696
+ if (cred === null || !allowed.has(cred.uid)) {
44697
+ log(`webhook-ingest-server: DENY connection uid=${cred?.uid ?? "unknown"} ` + `(allowed=${[...allowed].join(",")})
44698
+ `);
44699
+ conn.destroy();
44700
+ return;
44701
+ }
44702
+ let buf = "";
44703
+ let handled = false;
44704
+ conn.setEncoding("utf8");
44705
+ const reply = (resp) => {
44706
+ if (handled)
44707
+ return;
44708
+ handled = true;
44709
+ try {
44710
+ conn.write(JSON.stringify(resp) + `
44711
+ `);
44712
+ } catch {}
44713
+ conn.end();
44714
+ };
44715
+ conn.on("data", (chunk) => {
44716
+ if (handled)
44717
+ return;
44718
+ buf += chunk;
44719
+ if (buf.length > MAX_REQUEST_BYTES) {
44720
+ reply({ status: "error", error: "request too large" });
44721
+ return;
44722
+ }
44723
+ const nl = buf.indexOf(`
44724
+ `);
44725
+ if (nl === -1)
44726
+ return;
44727
+ const line = buf.slice(0, nl);
44728
+ let req;
44729
+ try {
44730
+ req = JSON.parse(line);
44731
+ } catch {
44732
+ reply({ status: "error", error: "malformed request" });
44733
+ return;
44734
+ }
44735
+ if (!req || typeof req.agent !== "string" || typeof req.source !== "string" || typeof req.event_type !== "string" || typeof req.rendered_text !== "string" || typeof req.payload !== "object" || req.payload === null) {
44736
+ reply({ status: "error", error: "invalid request shape" });
44737
+ return;
44738
+ }
44739
+ Promise.resolve().then(() => opts.onRecord(req)).then((resp) => reply(resp)).catch((err) => {
44740
+ log(`webhook-ingest-server: onRecord threw: ${String(err)}
44741
+ `);
44742
+ reply({ status: "error", error: "internal error" });
44743
+ });
44744
+ });
44745
+ conn.on("error", (err) => {
44746
+ log(`webhook-ingest-server: conn error: ${err.message}
44747
+ `);
44748
+ });
44749
+ conn.setTimeout(1e4, () => {
44750
+ if (!handled)
44751
+ reply({ status: "error", error: "timeout" });
44752
+ conn.destroy();
44753
+ });
44754
+ });
44755
+ server.on("error", (err) => {
44756
+ log(`webhook-ingest-server: server error: ${err.message}
44757
+ `);
44758
+ });
44759
+ try {
44760
+ server.listen(opts.socketPath, () => {
44761
+ try {
44762
+ chmodSync3(opts.socketPath, 438);
44763
+ } catch (err) {
44764
+ log(`webhook-ingest-server: chmod failed: ${err.message}
44765
+ `);
44766
+ }
44767
+ log(`webhook-ingest-server: listening at ${opts.socketPath} ` + `(allowed uids: ${[...allowed].join(",")})
44768
+ `);
44769
+ });
44770
+ } catch (err) {
44771
+ log(`webhook-ingest-server: listen failed: ${err.message}
44772
+ `);
44773
+ }
44774
+ return {
44775
+ close: () => {
44776
+ try {
44777
+ server.close();
44778
+ } catch {}
44779
+ try {
44780
+ if (existsSync21(opts.socketPath))
44781
+ unlinkSync5(opts.socketPath);
44782
+ } catch {}
44783
+ }
44784
+ };
44785
+ }
44786
+
44787
+ // ../src/web/webhook-gateway-record.ts
44788
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync12 } from "fs";
44789
+ import { join as join20 } from "path";
44790
+ import { homedir as homedir10 } from "os";
44791
+
44792
+ // ../src/web/webhook-handler.ts
44793
+ import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
44794
+ import { join as join18 } from "path";
44795
+ var DEDUP_MAX = 1000;
44796
+ var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
44797
+ function loadDedupFile(path) {
44798
+ try {
44799
+ if (!existsSync22(path))
44800
+ return {};
44801
+ const raw = JSON.parse(readFileSync16(path, "utf-8"));
44802
+ return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
44803
+ } catch {
44804
+ return {};
44805
+ }
44806
+ }
44807
+ function saveDedupFile(path, deliveries, now) {
44808
+ const pruned = {};
44809
+ for (const [id, ts] of Object.entries(deliveries)) {
44810
+ if (now - ts < DEDUP_TTL_MS)
44811
+ pruned[id] = ts;
44812
+ }
44813
+ const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
44814
+ const final = Object.fromEntries(sorted);
44815
+ writeFileSync9(path, JSON.stringify({ deliveries: final }), {
44816
+ mode: 384
44817
+ });
44818
+ }
44819
+ var agentDedupCache = new Map;
44820
+ function createFileDedupStore(resolveAgentDir) {
44821
+ return {
44822
+ check(agent, deliveryId, now) {
44823
+ const telegramDir = join18(resolveAgentDir(agent), "telegram");
44824
+ const filePath = join18(telegramDir, "webhook-dedup.json");
44825
+ if (!agentDedupCache.has(agent)) {
44826
+ agentDedupCache.set(agent, loadDedupFile(filePath));
44827
+ }
44828
+ const deliveries = agentDedupCache.get(agent);
44829
+ if (deliveries[deliveryId] !== undefined) {
44830
+ return deliveries[deliveryId];
44831
+ }
44832
+ deliveries[deliveryId] = now;
44833
+ try {
44834
+ mkdirSync10(telegramDir, { recursive: true });
44835
+ saveDedupFile(filePath, deliveries, now);
44836
+ } catch {}
44837
+ return;
44838
+ }
44839
+ };
44840
+ }
44841
+ var tokenBuckets = new Map;
44842
+ var throttleIssueWindow = new Map;
44843
+
44844
+ // ../src/web/webhook-dispatch.ts
44845
+ import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
44846
+ import { join as join19 } from "path";
44847
+ import { homedir as homedir9 } from "os";
44848
+
44849
+ // ../src/agent-scheduler/ipc-client.ts
44850
+ import { createConnection as createConnection2 } from "node:net";
44851
+ function createInjectIpcClient(options) {
44852
+ const {
44853
+ socketPath,
44854
+ reconnectDelayMs = 1000,
44855
+ maxReconnectDelayMs = 30000,
44856
+ connectTimeoutMs = 5000,
44857
+ log = () => {},
44858
+ _connect = (path) => createConnection2(path)
44859
+ } = options;
44860
+ let socket = null;
44861
+ let connected = false;
44862
+ let closed = false;
44863
+ let currentDelay = reconnectDelayMs;
44864
+ let reconnectTimer = null;
44865
+ let connectTimeoutTimer = null;
44866
+ function clearConnectTimeout() {
44867
+ if (connectTimeoutTimer !== null) {
44868
+ clearTimeout(connectTimeoutTimer);
44869
+ connectTimeoutTimer = null;
44870
+ }
44871
+ }
44872
+ function clearReconnectTimer() {
44873
+ if (reconnectTimer !== null) {
44874
+ clearTimeout(reconnectTimer);
44875
+ reconnectTimer = null;
44876
+ }
44877
+ }
44878
+ function scheduleReconnect() {
44879
+ if (closed)
44880
+ return;
44881
+ log(`scheduler ipc: reconnecting in ${currentDelay}ms`);
44882
+ reconnectTimer = setTimeout(() => {
44883
+ reconnectTimer = null;
44884
+ if (!closed)
44885
+ connect2();
44886
+ }, currentDelay);
44887
+ currentDelay = Math.min(currentDelay * 2, maxReconnectDelayMs);
44888
+ }
44889
+ function onClose() {
44890
+ clearConnectTimeout();
44891
+ connected = false;
44892
+ socket = null;
44893
+ if (!closed)
44894
+ scheduleReconnect();
44895
+ }
44896
+ function connect2() {
44897
+ if (closed)
44898
+ return;
44899
+ let s;
44900
+ try {
44901
+ s = _connect(socketPath);
44902
+ } catch (err) {
44903
+ log(`scheduler ipc: connect threw: ${err.message}`);
44904
+ scheduleReconnect();
44905
+ return;
44906
+ }
44907
+ socket = s;
44908
+ connectTimeoutTimer = setTimeout(() => {
44909
+ connectTimeoutTimer = null;
44910
+ if (!connected) {
44911
+ log(`scheduler ipc: connect timeout after ${connectTimeoutMs}ms`);
44912
+ try {
44913
+ s.destroy();
44914
+ } catch {}
44915
+ }
44916
+ }, connectTimeoutMs);
44917
+ s.on("connect", () => {
44918
+ clearConnectTimeout();
44919
+ connected = true;
44920
+ currentDelay = reconnectDelayMs;
44921
+ log(`scheduler ipc: connected to ${socketPath}`);
44922
+ });
44923
+ s.on("close", () => onClose());
44924
+ s.on("error", (err) => {
44925
+ log(`scheduler ipc: socket error: ${err.message}`);
44926
+ });
44927
+ s.on("data", () => {});
44928
+ }
44929
+ setImmediate(connect2);
44930
+ return {
44931
+ sendInjectInbound(msg) {
44932
+ if (!socket || !connected)
44933
+ return false;
44934
+ try {
44935
+ return socket.write(JSON.stringify(msg) + `
44936
+ `);
44937
+ } catch (err) {
44938
+ log(`scheduler ipc: write failed: ${err.message}`);
44939
+ return false;
44940
+ }
44941
+ },
44942
+ isConnected() {
44943
+ return connected;
44944
+ },
44945
+ waitForConnect(timeoutMs) {
44946
+ if (connected)
44947
+ return Promise.resolve(true);
44948
+ if (closed)
44949
+ return Promise.resolve(false);
44950
+ return new Promise((resolve6) => {
44951
+ const start = Date.now();
44952
+ const timer3 = setInterval(() => {
44953
+ if (connected) {
44954
+ clearInterval(timer3);
44955
+ resolve6(true);
44956
+ return;
44957
+ }
44958
+ if (closed || Date.now() - start >= timeoutMs) {
44959
+ clearInterval(timer3);
44960
+ resolve6(connected);
44961
+ }
44962
+ }, 50);
44963
+ if (typeof timer3.unref === "function") {
44964
+ timer3.unref();
44965
+ }
44966
+ });
44967
+ },
44968
+ close() {
44969
+ closed = true;
44970
+ clearReconnectTimer();
44971
+ clearConnectTimeout();
44972
+ if (socket) {
44973
+ try {
44974
+ socket.end();
44975
+ } catch {}
44976
+ socket = null;
44977
+ }
44978
+ connected = false;
44979
+ }
44980
+ };
44981
+ }
44982
+
44983
+ // ../src/web/webhook-dispatch.ts
44984
+ function renderTemplate(template, ctx) {
44985
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => ctx[key] ?? "");
44986
+ }
44987
+ function buildGithubContext(eventType, payload) {
44988
+ const repo = payload.repository?.full_name ?? "";
44989
+ const pr = payload.pull_request;
44990
+ const issue = payload.issue;
44991
+ const obj = pr ?? issue;
44992
+ const number = String(payload.number ?? obj?.number ?? "");
44993
+ const firstCommit = payload.commits?.[0];
44994
+ const title = String(obj?.title ?? firstCommit?.message ?? "");
44995
+ const html_url = String(obj?.html_url ?? payload.html_url ?? "");
44996
+ const author = String(obj?.user?.login ?? payload.sender?.login ?? "");
44997
+ const rawLabels = obj?.labels ?? [];
44998
+ const labels = rawLabels.map((l) => String(l.name ?? "")).join(", ");
44999
+ const action = String(payload.action ?? "");
45000
+ return { repo, number, title, html_url, author, labels, action, event: eventType };
45001
+ }
45002
+ function matchesRule(eventType, payload, matcher) {
45003
+ if (matcher.event !== eventType)
45004
+ return false;
45005
+ const ctx = buildGithubContext(eventType, payload);
45006
+ if (matcher.actions && matcher.actions.length > 0) {
45007
+ if (!matcher.actions.includes(ctx.action))
45008
+ return false;
45009
+ }
45010
+ if (matcher.exclude_authors && matcher.exclude_authors.length > 0) {
45011
+ if (matcher.exclude_authors.includes(ctx.author))
45012
+ return false;
45013
+ }
45014
+ if (matcher.labels_any && matcher.labels_any.length > 0) {
45015
+ const pr = payload.pull_request;
45016
+ const issue = payload.issue;
45017
+ const rawLabels = (pr ?? issue)?.labels ?? [];
45018
+ const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
45019
+ const hasAny = matcher.labels_any.some((l) => labelNames.has(l));
45020
+ if (!hasAny)
45021
+ return false;
45022
+ }
45023
+ if (matcher.labels_all && matcher.labels_all.length > 0) {
45024
+ const pr = payload.pull_request;
45025
+ const issue = payload.issue;
45026
+ const rawLabels = (pr ?? issue)?.labels ?? [];
45027
+ const labelNames = new Set(rawLabels.map((l) => String(l.name ?? "")));
45028
+ const hasAll = matcher.labels_all.every((l) => labelNames.has(l));
45029
+ if (!hasAll)
45030
+ return false;
45031
+ }
45032
+ return true;
45033
+ }
45034
+ function parseDurationMs(d) {
45035
+ const m = d.trim().match(/^(\d+)(s|m|h|d)?$/);
45036
+ if (!m)
45037
+ return 0;
45038
+ const n = parseInt(m[1], 10);
45039
+ switch (m[2]) {
45040
+ case "s":
45041
+ return n * 1000;
45042
+ case "m":
45043
+ return n * 60000;
45044
+ case "h":
45045
+ return n * 3600000;
45046
+ case "d":
45047
+ return n * 86400000;
45048
+ default:
45049
+ return n;
45050
+ }
45051
+ }
45052
+ function cooldownKey(eventType, repo, number, ruleIndex) {
45053
+ return `${eventType}:${repo}:${number}:${ruleIndex}`;
45054
+ }
45055
+ function loadCooldownFile(path) {
45056
+ try {
45057
+ if (!existsSync23(path))
45058
+ return {};
45059
+ const raw = JSON.parse(readFileSync17(path, "utf-8"));
45060
+ return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
45061
+ } catch {
45062
+ return {};
45063
+ }
45064
+ }
45065
+ function saveCooldownFile(path, dispatches) {
45066
+ try {
45067
+ writeFileSync10(path, JSON.stringify({ dispatches }), {
45068
+ mode: 384
45069
+ });
45070
+ } catch {}
45071
+ }
45072
+ function createFileCooldownStore(resolveAgentDir) {
45073
+ const cache = new Map;
45074
+ return {
45075
+ isCoolingDown(agent, key, cooldownMs, now) {
45076
+ if (cooldownMs <= 0)
45077
+ return false;
45078
+ const telegramDir = join19(resolveAgentDir(agent), "telegram");
45079
+ const filePath = join19(telegramDir, "webhook-cooldown.json");
45080
+ if (!cache.has(agent)) {
45081
+ cache.set(agent, loadCooldownFile(filePath));
45082
+ }
45083
+ const dispatches = cache.get(agent);
45084
+ const lastDispatch = dispatches[key];
45085
+ if (lastDispatch !== undefined && now - lastDispatch < cooldownMs) {
45086
+ return true;
45087
+ }
45088
+ dispatches[key] = now;
45089
+ try {
45090
+ mkdirSync11(telegramDir, { recursive: true });
45091
+ saveCooldownFile(filePath, dispatches);
45092
+ } catch {}
45093
+ return false;
45094
+ }
45095
+ };
45096
+ }
45097
+ function isQuietHour(qh, now) {
45098
+ const tz = qh.tz ?? "UTC";
45099
+ let hour;
45100
+ try {
45101
+ const formatter = new Intl.DateTimeFormat("en-US", {
45102
+ timeZone: tz,
45103
+ hour: "numeric",
45104
+ hour12: false
45105
+ });
45106
+ const parts = formatter.formatToParts(now);
45107
+ const hourPart = parts.find((p) => p.type === "hour");
45108
+ hour = parseInt(hourPart?.value ?? "0", 10);
45109
+ } catch {
45110
+ hour = now.getUTCHours();
45111
+ }
45112
+ const { start, end } = qh;
45113
+ if (start < end) {
45114
+ return hour >= start && hour < end;
45115
+ } else {
45116
+ return hour >= start || hour < end;
45117
+ }
45118
+ }
45119
+ async function defaultInject(socketPath, agentName3, inbound) {
45120
+ const client3 = createInjectIpcClient({ socketPath });
45121
+ try {
45122
+ const connected = await client3.waitForConnect(5000);
45123
+ if (!connected)
45124
+ return false;
45125
+ return client3.sendInjectInbound({
45126
+ type: "inject_inbound",
45127
+ agentName: agentName3,
45128
+ inbound
45129
+ });
45130
+ } finally {
45131
+ client3.close();
45132
+ }
45133
+ }
45134
+ function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
45135
+ const log = deps.log ?? ((s) => process.stderr.write(s));
45136
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45137
+ const now = (deps.now ?? Date.now)();
45138
+ const socketPath = join19(resolveAgentDir(agent), "telegram", "gateway.sock");
45139
+ const inbound = {
45140
+ type: "inbound",
45141
+ chatId: ctx.chatId,
45142
+ ...ctx.threadId !== undefined ? { threadId: ctx.threadId } : {},
45143
+ messageId: now,
45144
+ user: "webhook",
45145
+ userId: 0,
45146
+ ts: now,
45147
+ text: prompt,
45148
+ meta: {
45149
+ source: "webhook",
45150
+ event: ctx.eventType,
45151
+ rule_index: String(ctx.ruleIndex)
45152
+ }
45153
+ };
45154
+ const injectFn = deps.injectFn ?? defaultInject;
45155
+ injectFn(socketPath, agent, inbound).then((ok) => log(`webhook-dispatch: agent='${agent}' inject_inbound ` + `${ok ? "delivered to gateway" : "not delivered (gateway not connected)"}
45156
+ `)).catch((err) => log(`webhook-dispatch: agent='${agent}' inject failed: ${String(err)}
45157
+ `));
45158
+ }
45159
+ function evaluateDispatch(args, deps = {}) {
45160
+ const log = deps.log ?? ((s) => process.stderr.write(s));
45161
+ const now = (deps.now ?? Date.now)();
45162
+ const nowDate = deps.nowDate ?? (() => new Date(now));
45163
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
45164
+ const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
45165
+ if (args.source !== "github")
45166
+ return 0;
45167
+ const rules = args.dispatchConfig.github;
45168
+ if (!rules || rules.length === 0)
45169
+ return 0;
45170
+ const ctx = buildGithubContext(args.eventType, args.payload);
45171
+ let fired = 0;
45172
+ for (let i = 0;i < rules.length; i++) {
45173
+ const rule = rules[i];
45174
+ if (!matchesRule(args.eventType, args.payload, rule.match))
45175
+ continue;
45176
+ if (rule.quiet_hours && isQuietHour(rule.quiet_hours, nowDate())) {
45177
+ log(`webhook-dispatch: agent='${args.agent}' rule=${i} skipped (quiet hours)
45178
+ `);
45179
+ continue;
45180
+ }
45181
+ const cooldownMs = rule.cooldown ? parseDurationMs(rule.cooldown) : 0;
45182
+ if (cooldownMs > 0) {
45183
+ const ck = cooldownKey(args.eventType, ctx.repo, ctx.number, i);
45184
+ if (cooldownStore.isCoolingDown(args.agent, ck, cooldownMs, now)) {
45185
+ log(`webhook-dispatch: agent='${args.agent}' rule=${i} skipped (cooldown)
45186
+ `);
45187
+ continue;
45188
+ }
45189
+ }
45190
+ const prompt = renderTemplate(rule.prompt, ctx);
45191
+ log(`webhook-dispatch: agent='${args.agent}' rule=${i} matched event='${args.eventType}' action='${ctx.action}' firing
45192
+ `);
45193
+ injectWebhookInbound(args.agent, prompt, {
45194
+ chatId: args.chatId,
45195
+ ...args.threadId !== undefined ? { threadId: args.threadId } : {},
45196
+ eventType: args.eventType,
45197
+ ruleIndex: i
45198
+ }, { ...deps, resolveAgentDir });
45199
+ fired++;
45200
+ }
45201
+ return fired;
45202
+ }
45203
+
45204
+ // ../src/web/webhook-gateway-record.ts
45205
+ init_loader();
45206
+ init_merge();
45207
+
45208
+ // ../src/agent-scheduler/channel-target.ts
45209
+ init_merge();
45210
+ function resolveChannelTarget(config, agentName3) {
45211
+ const agent = config.agents?.[agentName3];
45212
+ const tgChannel = agent ? resolveAgentConfig(config.defaults, config.profiles, agent).channels?.telegram : undefined;
45213
+ const supergroupChatId = tgChannel?.chat_id;
45214
+ const supergroupDefaultTopic = tgChannel?.default_topic_id;
45215
+ if (typeof supergroupChatId === "string" && supergroupChatId.length > 0) {
45216
+ return {
45217
+ chatId: supergroupChatId,
45218
+ ...typeof supergroupDefaultTopic === "number" ? { threadId: supergroupDefaultTopic } : {},
45219
+ routerConfig: {
45220
+ ...typeof supergroupDefaultTopic === "number" ? { default_topic_id: supergroupDefaultTopic } : {},
45221
+ ...tgChannel?.topic_aliases ? { topic_aliases: tgChannel.topic_aliases } : {}
45222
+ }
45223
+ };
45224
+ }
45225
+ const forumChatId = config.telegram?.forum_chat_id;
45226
+ if (typeof forumChatId !== "string" || forumChatId.length === 0)
45227
+ return null;
45228
+ const threadId = agent?.topic_id;
45229
+ return {
45230
+ chatId: forumChatId,
45231
+ ...typeof threadId === "number" ? { threadId } : {}
45232
+ };
45233
+ }
45234
+
45235
+ // ../src/web/webhook-gateway-record.ts
45236
+ function recordWebhookEvent(rec, deps = {}) {
45237
+ const log = deps.log ?? ((s) => process.stderr.write(s));
45238
+ const now = rec.ts || (deps.now ?? Date.now)();
45239
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir10(), ".switchroom", "agents", a));
45240
+ const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
45241
+ const agent = rec.agent;
45242
+ const telegramDir = join20(resolveAgentDir(agent), "telegram");
45243
+ if (rec.source === "github" && rec.delivery_id) {
45244
+ const originalTs = dedupStore.check(agent, rec.delivery_id, now);
45245
+ if (originalTs !== undefined) {
45246
+ log(`webhook-gateway: agent='${agent}' source='${rec.source}' deduped delivery='${rec.delivery_id}'
45247
+ `);
45248
+ return { status: "deduped", ts: originalTs };
45249
+ }
45250
+ }
45251
+ const logPath = join20(telegramDir, "webhook-events.jsonl");
45252
+ try {
45253
+ mkdirSync12(telegramDir, { recursive: true });
45254
+ const record = {
45255
+ ts: now,
45256
+ source: rec.source,
45257
+ event_type: rec.event_type,
45258
+ rendered_text: rec.rendered_text,
45259
+ payload: rec.payload
45260
+ };
45261
+ appendFileSync4(logPath, JSON.stringify(record) + `
45262
+ `, { mode: 384 });
45263
+ } catch (err) {
45264
+ log(`webhook-gateway: agent='${agent}' source='${rec.source}' write failed: ${err.message}
45265
+ `);
45266
+ return { status: "error", error: "write failed" };
45267
+ }
45268
+ log(`webhook-gateway: agent='${agent}' source='${rec.source}' event='${rec.event_type}' recorded ts=${now}
45269
+ `);
45270
+ let dispatched = 0;
45271
+ try {
45272
+ const config = (deps.loadConfig ?? loadConfig)();
45273
+ const rawAgent = config.agents?.[agent];
45274
+ const dispatchConfig = rawAgent ? resolveAgentConfig(config.defaults, config.profiles, rawAgent).channels?.telegram?.webhook_dispatch : undefined;
45275
+ if (dispatchConfig && rec.source === "github") {
45276
+ const target = resolveChannelTarget(config, agent);
45277
+ if (!target) {
45278
+ log(`webhook-gateway: agent='${agent}' dispatch skipped \u2014 no chat target (forum_chat_id / chat_id unset)
45279
+ `);
45280
+ } else {
45281
+ const injectFn = deps.inject;
45282
+ dispatched = evaluateDispatch({
45283
+ agent,
45284
+ source: rec.source,
45285
+ eventType: rec.event_type,
45286
+ payload: rec.payload,
45287
+ dispatchConfig,
45288
+ chatId: target.chatId,
45289
+ ...target.threadId !== undefined ? { threadId: target.threadId } : {}
45290
+ }, {
45291
+ now: () => now,
45292
+ resolveAgentDir,
45293
+ log,
45294
+ ...deps.cooldownStore ? { cooldownStore: deps.cooldownStore } : {},
45295
+ ...injectFn ? {
45296
+ injectFn: async (_socketPath, agentName3, inbound) => injectFn(agentName3, inbound)
45297
+ } : {}
45298
+ });
45299
+ }
45300
+ }
45301
+ } catch (err) {
45302
+ log(`webhook-gateway: agent='${agent}' dispatch error (event recorded): ${err.message}
45303
+ `);
45304
+ }
45305
+ return { status: "ok", ts: now, dispatched };
45306
+ }
45307
+
44619
45308
  // gateway/ipc-server.ts
44620
- import { renameSync as renameSync5, unlinkSync as unlinkSync5 } from "fs";
45309
+ import { renameSync as renameSync5, unlinkSync as unlinkSync6 } from "fs";
44621
45310
  var MAX_BUFFER_SIZE = 1024 * 1024;
44622
45311
  var VALID_OPERATOR_KINDS = new Set([
44623
45312
  "credentials-expired",
@@ -44735,7 +45424,7 @@ function createIpcServer(options) {
44735
45424
  renameSync5(socketPath, socketPath + ".bak");
44736
45425
  } catch {}
44737
45426
  try {
44738
- unlinkSync5(socketPath + ".bak");
45427
+ unlinkSync6(socketPath + ".bak");
44739
45428
  } catch {}
44740
45429
  const clients = new Set;
44741
45430
  const agentIndex = new Map;
@@ -46805,15 +47494,15 @@ function escapeBody(s) {
46805
47494
  }
46806
47495
 
46807
47496
  // gateway/pid-file.ts
46808
- import { writeFileSync as writeFileSync9, readFileSync as readFileSync16, unlinkSync as unlinkSync6, renameSync as renameSync6 } from "node:fs";
47497
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync18, unlinkSync as unlinkSync7, renameSync as renameSync6 } from "node:fs";
46809
47498
  function writePidFile(path, record) {
46810
47499
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
46811
- writeFileSync9(tmp, JSON.stringify(record), "utf-8");
47500
+ writeFileSync11(tmp, JSON.stringify(record), "utf-8");
46812
47501
  renameSync6(tmp, path);
46813
47502
  }
46814
47503
  function clearPidFile(path) {
46815
47504
  try {
46816
- unlinkSync6(path);
47505
+ unlinkSync7(path);
46817
47506
  } catch {}
46818
47507
  }
46819
47508
 
@@ -46824,10 +47513,10 @@ import {
46824
47513
  writeFile as writeFileAsync,
46825
47514
  readFile as readFileAsync
46826
47515
  } from "node:fs/promises";
46827
- import { readFileSync as readFileSync17 } from "node:fs";
47516
+ import { readFileSync as readFileSync19 } from "node:fs";
46828
47517
  function readCurrentBootId() {
46829
47518
  try {
46830
- const stat = readFileSync17("/proc/1/stat", "utf-8");
47519
+ const stat = readFileSync19("/proc/1/stat", "utf-8");
46831
47520
  const lastParen = stat.lastIndexOf(")");
46832
47521
  if (lastParen < 0)
46833
47522
  return null;
@@ -47030,15 +47719,15 @@ function safeCount(fn) {
47030
47719
  }
47031
47720
 
47032
47721
  // gateway/session-marker.ts
47033
- import { writeFileSync as writeFileSync10, readFileSync as readFileSync18, renameSync as renameSync7, unlinkSync as unlinkSync7 } from "node:fs";
47722
+ import { writeFileSync as writeFileSync12, readFileSync as readFileSync20, renameSync as renameSync7, unlinkSync as unlinkSync8 } from "node:fs";
47034
47723
  function writeSessionMarker(path, marker) {
47035
47724
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
47036
- writeFileSync10(tmp, JSON.stringify(marker), "utf-8");
47725
+ writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
47037
47726
  renameSync7(tmp, path);
47038
47727
  }
47039
47728
  function readSessionMarker(path) {
47040
47729
  try {
47041
- const raw = readFileSync18(path, "utf-8");
47730
+ const raw = readFileSync20(path, "utf-8");
47042
47731
  const parsed = JSON.parse(raw);
47043
47732
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
47044
47733
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -47060,16 +47749,16 @@ function shouldFireRestartBanner(input) {
47060
47749
  }
47061
47750
 
47062
47751
  // gateway/clean-shutdown-marker.ts
47063
- import { writeFileSync as writeFileSync11, readFileSync as readFileSync19, renameSync as renameSync8, unlinkSync as unlinkSync8 } from "node:fs";
47752
+ import { writeFileSync as writeFileSync13, readFileSync as readFileSync21, renameSync as renameSync8, unlinkSync as unlinkSync9 } from "node:fs";
47064
47753
  var DEFAULT_MAX_AGE_MS = 60000;
47065
47754
  function writeCleanShutdownMarker(path, marker) {
47066
47755
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
47067
- writeFileSync11(tmp, JSON.stringify(marker), "utf-8");
47756
+ writeFileSync13(tmp, JSON.stringify(marker), "utf-8");
47068
47757
  renameSync8(tmp, path);
47069
47758
  }
47070
47759
  function readCleanShutdownMarker(path) {
47071
47760
  try {
47072
- const raw = readFileSync19(path, "utf-8");
47761
+ const raw = readFileSync21(path, "utf-8");
47073
47762
  const parsed = JSON.parse(raw);
47074
47763
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
47075
47764
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -47084,7 +47773,7 @@ function readCleanShutdownMarker(path) {
47084
47773
  }
47085
47774
  function clearCleanShutdownMarker(path) {
47086
47775
  try {
47087
- unlinkSync8(path);
47776
+ unlinkSync9(path);
47088
47777
  } catch {}
47089
47778
  }
47090
47779
  function shouldSuppressRecoveryBanner(marker, now, maxAgeMs = DEFAULT_MAX_AGE_MS) {
@@ -47864,16 +48553,16 @@ function classifyAdminGate(text, myAgentName) {
47864
48553
 
47865
48554
  // subagent-watcher.ts
47866
48555
  import {
47867
- existsSync as existsSync22,
48556
+ existsSync as existsSync25,
47868
48557
  openSync as openSync2,
47869
48558
  readSync,
47870
48559
  statSync as statSync6,
47871
48560
  closeSync as closeSync2,
47872
48561
  watch,
47873
48562
  readdirSync as readdirSync3,
47874
- readFileSync as readFileSync21
48563
+ readFileSync as readFileSync23
47875
48564
  } from "fs";
47876
- import { join as join19 } from "path";
48565
+ import { join as join22 } from "path";
47877
48566
 
47878
48567
  // operator-events.ts
47879
48568
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
@@ -48116,20 +48805,20 @@ function bumpSubagentActivity(db2, args) {
48116
48805
  // gateway/turn-active-marker.ts
48117
48806
  import {
48118
48807
  closeSync,
48119
- existsSync as existsSync21,
48120
- mkdirSync as mkdirSync10,
48808
+ existsSync as existsSync24,
48809
+ mkdirSync as mkdirSync13,
48121
48810
  openSync,
48122
- readFileSync as readFileSync20,
48811
+ readFileSync as readFileSync22,
48123
48812
  statSync as statSync5,
48124
- unlinkSync as unlinkSync9,
48813
+ unlinkSync as unlinkSync10,
48125
48814
  utimesSync,
48126
- writeFileSync as writeFileSync12
48815
+ writeFileSync as writeFileSync14
48127
48816
  } from "node:fs";
48128
- import { join as join18 } from "node:path";
48817
+ import { join as join21 } from "node:path";
48129
48818
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
48130
48819
  function touchTurnActiveMarker(stateDir) {
48131
- const path = join18(stateDir, TURN_ACTIVE_MARKER_FILE);
48132
- if (!existsSync21(path))
48820
+ const path = join21(stateDir, TURN_ACTIVE_MARKER_FILE);
48821
+ if (!existsSync24(path))
48133
48822
  return;
48134
48823
  const now = new Date;
48135
48824
  try {
@@ -48164,7 +48853,7 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
48164
48853
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
48165
48854
  let meta;
48166
48855
  try {
48167
- const raw = readFileSync21(metaPath, "utf8");
48856
+ const raw = readFileSync23(metaPath, "utf8");
48168
48857
  meta = JSON.parse(raw);
48169
48858
  } catch {
48170
48859
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -48351,7 +49040,7 @@ function startSubagentWatcher(config) {
48351
49040
  clearTimeout(ref.ref);
48352
49041
  });
48353
49042
  const fs2 = config.fs ?? {
48354
- existsSync: existsSync22,
49043
+ existsSync: existsSync25,
48355
49044
  readdirSync: readdirSync3,
48356
49045
  statSync: statSync6,
48357
49046
  openSync: openSync2,
@@ -48585,8 +49274,8 @@ function startSubagentWatcher(config) {
48585
49274
  function rescanSubagentDirs() {
48586
49275
  if (stopped)
48587
49276
  return;
48588
- const claudeHome = join19(agentDir, ".claude");
48589
- const projectsRoot = join19(claudeHome, "projects");
49277
+ const claudeHome = join22(agentDir, ".claude");
49278
+ const projectsRoot = join22(claudeHome, "projects");
48590
49279
  if (!fs2.existsSync(projectsRoot))
48591
49280
  return;
48592
49281
  let projectDirs;
@@ -48603,7 +49292,7 @@ function startSubagentWatcher(config) {
48603
49292
  }
48604
49293
  continue;
48605
49294
  }
48606
- const projectPath = join19(projectsRoot, pDir);
49295
+ const projectPath = join22(projectsRoot, pDir);
48607
49296
  let sessionDirs;
48608
49297
  try {
48609
49298
  sessionDirs = fs2.readdirSync(projectPath);
@@ -48613,7 +49302,7 @@ function startSubagentWatcher(config) {
48613
49302
  for (const sDir of sessionDirs) {
48614
49303
  if (sDir.endsWith(".jsonl"))
48615
49304
  continue;
48616
- const subagentsPath = join19(projectPath, sDir, "subagents");
49305
+ const subagentsPath = join22(projectPath, sDir, "subagents");
48617
49306
  if (!fs2.existsSync(subagentsPath))
48618
49307
  continue;
48619
49308
  if (!dirWatchers.has(subagentsPath)) {
@@ -48621,7 +49310,7 @@ function startSubagentWatcher(config) {
48621
49310
  const w = fs2.watch(subagentsPath, (_event, filename) => {
48622
49311
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
48623
49312
  return;
48624
- const filePath = join19(subagentsPath, filename.toString());
49313
+ const filePath = join22(subagentsPath, filename.toString());
48625
49314
  if (!knownFiles.has(filePath)) {
48626
49315
  scanSubagentsDir(subagentsPath);
48627
49316
  }
@@ -48646,7 +49335,7 @@ function startSubagentWatcher(config) {
48646
49335
  for (const e of entries) {
48647
49336
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
48648
49337
  continue;
48649
- const filePath = join19(subagentsPath, e);
49338
+ const filePath = join22(subagentsPath, e);
48650
49339
  if (knownFiles.has(filePath))
48651
49340
  continue;
48652
49341
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -48763,15 +49452,15 @@ function determineRestartReason(opts) {
48763
49452
  init_boot_card();
48764
49453
 
48765
49454
  // gateway/update-announce.ts
48766
- import { existsSync as existsSync27, mkdirSync as mkdirSync14, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync26 } from "node:fs";
48767
- import { join as join24 } from "node:path";
48768
- import { homedir as homedir10 } from "node:os";
49455
+ import { existsSync as existsSync30, mkdirSync as mkdirSync17, openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync28 } from "node:fs";
49456
+ import { join as join27 } from "node:path";
49457
+ import { homedir as homedir12 } from "node:os";
48769
49458
 
48770
49459
  // ../src/host-control/audit-reader.ts
48771
- import { homedir as homedir9 } from "node:os";
48772
- import { join as join23 } from "node:path";
48773
- function defaultAuditLogPath(home2 = homedir9()) {
48774
- return join23(home2, ".switchroom", "host-control-audit.log");
49460
+ import { homedir as homedir11 } from "node:os";
49461
+ import { join as join26 } from "node:path";
49462
+ function defaultAuditLogPath(home2 = homedir11()) {
49463
+ return join26(home2, ".switchroom", "host-control-audit.log");
48775
49464
  }
48776
49465
  function parseAuditLine(line) {
48777
49466
  const trimmed = line.trim();
@@ -48877,8 +49566,8 @@ function readAndFilter(raw, filters, limit) {
48877
49566
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
48878
49567
  function readLastTerminalUpdateAudit(opts = {}) {
48879
49568
  const path = opts.auditLogPath ?? defaultAuditLogPath();
48880
- const exists = opts.exists ?? existsSync27;
48881
- const readFile = opts.readFile ?? ((p) => readFileSync26(p, "utf-8"));
49569
+ const exists = opts.exists ?? existsSync30;
49570
+ const readFile = opts.readFile ?? ((p) => readFileSync28(p, "utf-8"));
48882
49571
  if (!exists(path))
48883
49572
  return null;
48884
49573
  let raw;
@@ -48939,15 +49628,15 @@ function renderUpdateOutcomeLine(entry) {
48939
49628
  `);
48940
49629
  }
48941
49630
  function claimUpdateAnnouncement(requestId, opts = {}) {
48942
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join24(homedir10(), ".switchroom");
48943
- const dir = join24(stateDir, "update-announced");
49631
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join27(homedir12(), ".switchroom");
49632
+ const dir = join27(stateDir, "update-announced");
48944
49633
  try {
48945
- mkdirSync14(dir, { recursive: true });
49634
+ mkdirSync17(dir, { recursive: true });
48946
49635
  } catch {
48947
49636
  return false;
48948
49637
  }
48949
49638
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
48950
- const path = join24(dir, safeId);
49639
+ const path = join27(dir, safeId);
48951
49640
  try {
48952
49641
  const fd = openSync3(path, "wx");
48953
49642
  closeSync3(fd);
@@ -48966,7 +49655,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
48966
49655
  }
48967
49656
 
48968
49657
  // issues-card.ts
48969
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
49658
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
48970
49659
  var SEVERITY_EMOJI = {
48971
49660
  info: "\u2139\ufe0f",
48972
49661
  warn: "\u26a0\ufe0f",
@@ -49058,7 +49747,7 @@ function extractRetryAfterSecs(err) {
49058
49747
  var COOLDOWN_JITTER_MS = 500;
49059
49748
  function readPersistedMessageId(path, log) {
49060
49749
  try {
49061
- const raw = readFileSync27(path, "utf8");
49750
+ const raw = readFileSync29(path, "utf8");
49062
49751
  const parsed = JSON.parse(raw);
49063
49752
  const v = parsed.messageId;
49064
49753
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -49074,7 +49763,7 @@ function readPersistedMessageId(path, log) {
49074
49763
  }
49075
49764
  function writePersistedMessageId(path, messageId, log) {
49076
49765
  try {
49077
- writeFileSync16(path, JSON.stringify({ messageId }) + `
49766
+ writeFileSync18(path, JSON.stringify({ messageId }) + `
49078
49767
  `, { mode: 384 });
49079
49768
  } catch (err) {
49080
49769
  log(`issues-card: persist write failed (${err.message})`);
@@ -49167,24 +49856,24 @@ function createIssuesCardHandle(opts) {
49167
49856
  }
49168
49857
 
49169
49858
  // issues-watcher.ts
49170
- import { existsSync as existsSync29, statSync as statSync8 } from "node:fs";
49171
- import { join as join26 } from "node:path";
49859
+ import { existsSync as existsSync32, statSync as statSync8 } from "node:fs";
49860
+ import { join as join29 } from "node:path";
49172
49861
 
49173
49862
  // ../src/issues/store.ts
49174
49863
  import {
49175
49864
  closeSync as closeSync4,
49176
- existsSync as existsSync28,
49177
- mkdirSync as mkdirSync15,
49865
+ existsSync as existsSync31,
49866
+ mkdirSync as mkdirSync18,
49178
49867
  openSync as openSync4,
49179
49868
  readdirSync as readdirSync5,
49180
- readFileSync as readFileSync28,
49869
+ readFileSync as readFileSync30,
49181
49870
  renameSync as renameSync11,
49182
49871
  statSync as statSync7,
49183
- unlinkSync as unlinkSync10,
49184
- writeFileSync as writeFileSync17,
49872
+ unlinkSync as unlinkSync11,
49873
+ writeFileSync as writeFileSync19,
49185
49874
  writeSync
49186
49875
  } from "node:fs";
49187
- import { join as join25 } from "node:path";
49876
+ import { join as join28 } from "node:path";
49188
49877
  import { randomBytes as randomBytes4 } from "node:crypto";
49189
49878
  import { execSync } from "node:child_process";
49190
49879
 
@@ -49200,12 +49889,12 @@ var SEVERITY_RANK2 = {
49200
49889
  var ISSUES_FILE = "issues.jsonl";
49201
49890
  var ISSUES_LOCK = "issues.lock";
49202
49891
  function readAll(stateDir) {
49203
- const path = join25(stateDir, ISSUES_FILE);
49204
- if (!existsSync28(path))
49892
+ const path = join28(stateDir, ISSUES_FILE);
49893
+ if (!existsSync31(path))
49205
49894
  return [];
49206
49895
  let raw;
49207
49896
  try {
49208
- raw = readFileSync28(path, "utf-8");
49897
+ raw = readFileSync30(path, "utf-8");
49209
49898
  } catch {
49210
49899
  return [];
49211
49900
  }
@@ -49237,7 +49926,7 @@ function list(stateDir, opts = {}) {
49237
49926
  });
49238
49927
  }
49239
49928
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
49240
- if (!existsSync28(join25(stateDir, ISSUES_FILE)))
49929
+ if (!existsSync31(join28(stateDir, ISSUES_FILE)))
49241
49930
  return 0;
49242
49931
  return withLock(stateDir, () => {
49243
49932
  const all = readAll(stateDir);
@@ -49255,13 +49944,13 @@ function resolve6(stateDir, fingerprint, nowFn = Date.now) {
49255
49944
  });
49256
49945
  }
49257
49946
  function writeAll(stateDir, events) {
49258
- const path = join25(stateDir, ISSUES_FILE);
49947
+ const path = join28(stateDir, ISSUES_FILE);
49259
49948
  sweepOrphanTmpFiles(stateDir);
49260
49949
  const tmp = `${path}.tmp-${process.pid}-${randomBytes4(4).toString("hex")}`;
49261
49950
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
49262
49951
  `) + `
49263
49952
  `;
49264
- writeFileSync17(tmp, body, "utf-8");
49953
+ writeFileSync19(tmp, body, "utf-8");
49265
49954
  renameSync11(tmp, path);
49266
49955
  }
49267
49956
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -49277,11 +49966,11 @@ function sweepOrphanTmpFiles(stateDir) {
49277
49966
  for (const entry of entries) {
49278
49967
  if (!entry.startsWith(TMP_PREFIX))
49279
49968
  continue;
49280
- const tmpPath2 = join25(stateDir, entry);
49969
+ const tmpPath2 = join28(stateDir, entry);
49281
49970
  try {
49282
49971
  const stat = statSync7(tmpPath2);
49283
49972
  if (stat.mtimeMs < cutoff) {
49284
- unlinkSync10(tmpPath2);
49973
+ unlinkSync11(tmpPath2);
49285
49974
  }
49286
49975
  } catch {}
49287
49976
  }
@@ -49289,7 +49978,7 @@ function sweepOrphanTmpFiles(stateDir) {
49289
49978
  var LOCK_RETRY_MS = 25;
49290
49979
  var LOCK_TIMEOUT_MS = 1e4;
49291
49980
  function withLock(stateDir, fn) {
49292
- const lockPath = join25(stateDir, ISSUES_LOCK);
49981
+ const lockPath = join28(stateDir, ISSUES_LOCK);
49293
49982
  const startedAt = Date.now();
49294
49983
  let fd = null;
49295
49984
  while (fd === null) {
@@ -49317,27 +50006,27 @@ function withLock(stateDir, fn) {
49317
50006
  closeSync4(fd);
49318
50007
  } catch {}
49319
50008
  try {
49320
- unlinkSync10(lockPath);
50009
+ unlinkSync11(lockPath);
49321
50010
  } catch {}
49322
50011
  }
49323
50012
  }
49324
50013
  function tryStealStaleLock(lockPath) {
49325
50014
  let pidStr;
49326
50015
  try {
49327
- pidStr = readFileSync28(lockPath, "utf-8").trim();
50016
+ pidStr = readFileSync30(lockPath, "utf-8").trim();
49328
50017
  } catch {
49329
50018
  return true;
49330
50019
  }
49331
50020
  const pid = Number(pidStr);
49332
50021
  if (!Number.isFinite(pid) || pid <= 0) {
49333
50022
  try {
49334
- unlinkSync10(lockPath);
50023
+ unlinkSync11(lockPath);
49335
50024
  } catch {}
49336
50025
  return true;
49337
50026
  }
49338
50027
  if (pid === process.pid) {
49339
50028
  try {
49340
- unlinkSync10(lockPath);
50029
+ unlinkSync11(lockPath);
49341
50030
  } catch {}
49342
50031
  return true;
49343
50032
  }
@@ -49352,7 +50041,7 @@ function tryStealStaleLock(lockPath) {
49352
50041
  return false;
49353
50042
  }
49354
50043
  try {
49355
- unlinkSync10(lockPath);
50044
+ unlinkSync11(lockPath);
49356
50045
  } catch {}
49357
50046
  return true;
49358
50047
  }
@@ -49374,7 +50063,7 @@ function isIssueEvent(v) {
49374
50063
  // issues-watcher.ts
49375
50064
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
49376
50065
  function startIssuesWatcher(opts) {
49377
- const path = join26(opts.stateDir, ISSUES_FILE);
50066
+ const path = join29(opts.stateDir, ISSUES_FILE);
49378
50067
  const log = opts.log ?? (() => {});
49379
50068
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
49380
50069
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -49422,7 +50111,7 @@ function startIssuesWatcher(opts) {
49422
50111
  };
49423
50112
  }
49424
50113
  function defaultSignatureProvider(path) {
49425
- if (!existsSync29(path))
50114
+ if (!existsSync32(path))
49426
50115
  return null;
49427
50116
  try {
49428
50117
  const stat = statSync8(path);
@@ -49906,8 +50595,8 @@ function extractFlowItems(line) {
49906
50595
  }
49907
50596
 
49908
50597
  // credits-watch.ts
49909
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync16 } from "fs";
49910
- import { join as join27 } from "path";
50598
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync20, existsSync as existsSync33, mkdirSync as mkdirSync19 } from "fs";
50599
+ import { join as join30 } from "path";
49911
50600
  var STATE_FILE = "credits-watch.json";
49912
50601
  var FATAL_REASONS = new Set([
49913
50602
  "out_of_credits",
@@ -49919,12 +50608,12 @@ function emptyCreditState() {
49919
50608
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
49920
50609
  }
49921
50610
  function readClaudeJsonOverage(claudeConfigDir) {
49922
- const path = join27(claudeConfigDir, ".claude.json");
49923
- if (!existsSync30(path))
50611
+ const path = join30(claudeConfigDir, ".claude.json");
50612
+ if (!existsSync33(path))
49924
50613
  return null;
49925
50614
  let raw;
49926
50615
  try {
49927
- raw = readFileSync29(path, "utf-8");
50616
+ raw = readFileSync31(path, "utf-8");
49928
50617
  } catch {
49929
50618
  return null;
49930
50619
  }
@@ -50004,11 +50693,11 @@ function escapeHtml10(s) {
50004
50693
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
50005
50694
  }
50006
50695
  function loadCreditState(stateDir) {
50007
- const path = join27(stateDir, STATE_FILE);
50008
- if (!existsSync30(path))
50696
+ const path = join30(stateDir, STATE_FILE);
50697
+ if (!existsSync33(path))
50009
50698
  return emptyCreditState();
50010
50699
  try {
50011
- const raw = readFileSync29(path, "utf-8");
50700
+ const raw = readFileSync31(path, "utf-8");
50012
50701
  const parsed = JSON.parse(raw);
50013
50702
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
50014
50703
  return {
@@ -50020,15 +50709,15 @@ function loadCreditState(stateDir) {
50020
50709
  return emptyCreditState();
50021
50710
  }
50022
50711
  function saveCreditState(stateDir, state4) {
50023
- mkdirSync16(stateDir, { recursive: true });
50024
- const path = join27(stateDir, STATE_FILE);
50025
- writeFileSync18(path, JSON.stringify(state4, null, 2) + `
50712
+ mkdirSync19(stateDir, { recursive: true });
50713
+ const path = join30(stateDir, STATE_FILE);
50714
+ writeFileSync20(path, JSON.stringify(state4, null, 2) + `
50026
50715
  `, { mode: 384 });
50027
50716
  }
50028
50717
 
50029
50718
  // quota-watch.ts
50030
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync19, existsSync as existsSync31, mkdirSync as mkdirSync17 } from "fs";
50031
- import { join as join28 } from "path";
50719
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync21, existsSync as existsSync34, mkdirSync as mkdirSync20 } from "fs";
50720
+ import { join as join31 } from "path";
50032
50721
  var STATE_FILE2 = "quota-watch.json";
50033
50722
  function emptyQuotaWatchState() {
50034
50723
  return {};
@@ -50118,11 +50807,11 @@ function escapeHtml11(s) {
50118
50807
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
50119
50808
  }
50120
50809
  function loadQuotaWatchState(stateDir) {
50121
- const path = join28(stateDir, STATE_FILE2);
50122
- if (!existsSync31(path))
50810
+ const path = join31(stateDir, STATE_FILE2);
50811
+ if (!existsSync34(path))
50123
50812
  return emptyQuotaWatchState();
50124
50813
  try {
50125
- const raw = readFileSync30(path, "utf-8");
50814
+ const raw = readFileSync32(path, "utf-8");
50126
50815
  const parsed = JSON.parse(raw);
50127
50816
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
50128
50817
  return emptyQuotaWatchState();
@@ -50139,9 +50828,9 @@ function loadQuotaWatchState(stateDir) {
50139
50828
  }
50140
50829
  }
50141
50830
  function saveQuotaWatchState(stateDir, state4) {
50142
- mkdirSync17(stateDir, { recursive: true });
50143
- const path = join28(stateDir, STATE_FILE2);
50144
- writeFileSync19(path, JSON.stringify(state4, null, 2) + `
50831
+ mkdirSync20(stateDir, { recursive: true });
50832
+ const path = join31(stateDir, STATE_FILE2);
50833
+ writeFileSync21(path, JSON.stringify(state4, null, 2) + `
50145
50834
  `, { mode: 384 });
50146
50835
  }
50147
50836
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -50154,27 +50843,27 @@ init_auth_snapshot_format();
50154
50843
  // gateway/turn-active-marker.ts
50155
50844
  import {
50156
50845
  closeSync as closeSync5,
50157
- existsSync as existsSync32,
50158
- mkdirSync as mkdirSync18,
50846
+ existsSync as existsSync35,
50847
+ mkdirSync as mkdirSync21,
50159
50848
  openSync as openSync5,
50160
- readFileSync as readFileSync31,
50849
+ readFileSync as readFileSync33,
50161
50850
  statSync as statSync9,
50162
- unlinkSync as unlinkSync11,
50851
+ unlinkSync as unlinkSync12,
50163
50852
  utimesSync as utimesSync2,
50164
- writeFileSync as writeFileSync20
50853
+ writeFileSync as writeFileSync22
50165
50854
  } from "node:fs";
50166
- import { join as join29 } from "node:path";
50855
+ import { join as join32 } from "node:path";
50167
50856
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
50168
50857
  function writeTurnActiveMarker(stateDir, marker) {
50169
50858
  try {
50170
- mkdirSync18(stateDir, { recursive: true });
50171
- writeFileSync20(join29(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
50859
+ mkdirSync21(stateDir, { recursive: true });
50860
+ writeFileSync22(join32(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
50172
50861
  `, { mode: 384 });
50173
50862
  } catch {}
50174
50863
  }
50175
50864
  function touchTurnActiveMarker2(stateDir) {
50176
- const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
50177
- if (!existsSync32(path))
50865
+ const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
50866
+ if (!existsSync35(path))
50178
50867
  return;
50179
50868
  const now = new Date;
50180
50869
  try {
@@ -50188,12 +50877,12 @@ function touchTurnActiveMarker2(stateDir) {
50188
50877
  }
50189
50878
  function removeTurnActiveMarker(stateDir) {
50190
50879
  try {
50191
- unlinkSync11(join29(stateDir, TURN_ACTIVE_MARKER_FILE2));
50880
+ unlinkSync12(join32(stateDir, TURN_ACTIVE_MARKER_FILE2));
50192
50881
  } catch {}
50193
50882
  }
50194
50883
  function sweepStaleTurnActiveMarker(stateDir, opts) {
50195
- const path = join29(stateDir, TURN_ACTIVE_MARKER_FILE2);
50196
- if (!existsSync32(path))
50884
+ const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
50885
+ if (!existsSync35(path))
50197
50886
  return false;
50198
50887
  const now = opts.now ?? Date.now();
50199
50888
  try {
@@ -50205,9 +50894,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
50205
50894
  return false;
50206
50895
  let payload = null;
50207
50896
  try {
50208
- payload = readFileSync31(path, "utf8");
50897
+ payload = readFileSync33(path, "utf8");
50209
50898
  } catch {}
50210
- unlinkSync11(path);
50899
+ unlinkSync12(path);
50211
50900
  if (opts.onRemove) {
50212
50901
  try {
50213
50902
  opts.onRemove({
@@ -50224,10 +50913,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
50224
50913
  }
50225
50914
 
50226
50915
  // ../src/build-info.ts
50227
- var VERSION = "0.14.9";
50228
- var COMMIT_SHA = "fd8ed8ed";
50229
- var COMMIT_DATE = "2026-05-29T03:11:29Z";
50230
- var LATEST_PR = 1985;
50916
+ var VERSION = "0.14.10";
50917
+ var COMMIT_SHA = "0cd391b5";
50918
+ var COMMIT_DATE = "2026-05-29T05:14:25Z";
50919
+ var LATEST_PR = 1987;
50231
50920
  var COMMITS_AHEAD_OF_TAG = 0;
50232
50921
 
50233
50922
  // gateway/boot-version.ts
@@ -50298,14 +50987,14 @@ function classifyRejection(err, opts = {}) {
50298
50987
  // ../src/vault/broker/client.ts
50299
50988
  init_protocol2();
50300
50989
  init_peercred();
50301
- import * as net4 from "node:net";
50990
+ import * as net5 from "node:net";
50302
50991
  import * as fs2 from "node:fs";
50303
- import { homedir as homedir11 } from "node:os";
50304
- import { join as join30 } from "node:path";
50992
+ import { homedir as homedir13 } from "node:os";
50993
+ import { join as join33 } from "node:path";
50305
50994
  var DEFAULT_TIMEOUT_MS4 = 2000;
50306
50995
  var UNLOCK_TIMEOUT_MS = 30000;
50307
- var LEGACY_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "vault-broker.sock");
50308
- var OPERATOR_SOCKET_PATH2 = join30(homedir11(), ".switchroom", "broker-operator", "sock");
50996
+ var LEGACY_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "vault-broker.sock");
50997
+ var OPERATOR_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "broker-operator", "sock");
50309
50998
  function defaultBrokerSocketPath2() {
50310
50999
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
50311
51000
  return OPERATOR_SOCKET_PATH2;
@@ -50334,7 +51023,7 @@ async function rpc2(req, opts) {
50334
51023
  settled = true;
50335
51024
  resolve7(val);
50336
51025
  };
50337
- const client3 = new net4.Socket;
51026
+ const client3 = new net5.Socket;
50338
51027
  const timer3 = setTimeout(() => {
50339
51028
  client3.destroy();
50340
51029
  settle({ kind: "unreachable", msg: `broker did not respond within ${timeoutMs}ms` });
@@ -50428,7 +51117,7 @@ async function unlockViaBroker(passphrase, opts) {
50428
51117
  client3.destroy();
50429
51118
  settle({ ok: false, msg: "Timeout waiting for broker" });
50430
51119
  }, timeoutMs);
50431
- const client3 = net4.createConnection({ path: unlockSocketPath });
51120
+ const client3 = net5.createConnection({ path: unlockSocketPath });
50432
51121
  client3.on("error", (err) => {
50433
51122
  clearTimeout(timer3);
50434
51123
  settle({ ok: false, msg: `Broker unreachable: ${err.message}` });
@@ -50527,8 +51216,8 @@ function resolveVaultApprovalPosture(broker) {
50527
51216
  }
50528
51217
 
50529
51218
  // registry/turns-schema.ts
50530
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync19 } from "fs";
50531
- import { join as join31 } from "path";
51219
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync22 } from "fs";
51220
+ import { join as join34 } from "path";
50532
51221
  var DatabaseClass2 = null;
50533
51222
  function loadDatabaseClass2() {
50534
51223
  if (DatabaseClass2 != null)
@@ -50587,13 +51276,13 @@ function applySchema(db2) {
50587
51276
  }
50588
51277
  function openTurnsDb(agentDir) {
50589
51278
  const Database = loadDatabaseClass2();
50590
- const dir = join31(agentDir, "telegram");
50591
- mkdirSync19(dir, { recursive: true, mode: 448 });
50592
- const path = join31(dir, "registry.db");
51279
+ const dir = join34(agentDir, "telegram");
51280
+ mkdirSync22(dir, { recursive: true, mode: 448 });
51281
+ const path = join34(dir, "registry.db");
50593
51282
  const db2 = new Database(path, { create: true });
50594
51283
  applySchema(db2);
50595
51284
  try {
50596
- chmodSync3(path, 384);
51285
+ chmodSync4(path, 384);
50597
51286
  } catch {}
50598
51287
  return db2;
50599
51288
  }
@@ -50730,11 +51419,11 @@ installGlobalErrorHandlers();
50730
51419
  process.on("beforeExit", () => {
50731
51420
  shutdownAnalytics();
50732
51421
  });
50733
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join33(homedir12(), ".claude", "channels", "telegram");
50734
- var ACCESS_FILE = join33(STATE_DIR, "access.json");
50735
- var APPROVED_DIR = join33(STATE_DIR, "approved");
50736
- var ENV_FILE = join33(STATE_DIR, ".env");
50737
- var INBOX_DIR = join33(STATE_DIR, "inbox");
51422
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join36(homedir14(), ".claude", "channels", "telegram");
51423
+ var ACCESS_FILE = join36(STATE_DIR, "access.json");
51424
+ var APPROVED_DIR = join36(STATE_DIR, "approved");
51425
+ var ENV_FILE = join36(STATE_DIR, ".env");
51426
+ var INBOX_DIR = join36(STATE_DIR, "inbox");
50738
51427
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
50739
51428
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
50740
51429
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -50798,8 +51487,8 @@ function formatBootVersion() {
50798
51487
  });
50799
51488
  }
50800
51489
  try {
50801
- chmodSync5(ENV_FILE, 384);
50802
- for (const line of readFileSync34(ENV_FILE, "utf8").split(`
51490
+ chmodSync6(ENV_FILE, 384);
51491
+ for (const line of readFileSync36(ENV_FILE, "utf8").split(`
50803
51492
  `)) {
50804
51493
  const m = line.match(/^(\w+)=(.*)$/);
50805
51494
  if (m && process.env[m[1]] === undefined)
@@ -50852,7 +51541,7 @@ installTgPostLogger(bot);
50852
51541
  var _rawSendMessageDraft = bot.api.raw.sendMessageDraft;
50853
51542
  var GRAMMY_VERSION = (() => {
50854
51543
  try {
50855
- const raw = readFileSync34(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
51544
+ const raw = readFileSync36(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
50856
51545
  return JSON.parse(raw).version ?? "unknown";
50857
51546
  } catch {
50858
51547
  return "unknown";
@@ -50925,7 +51614,7 @@ function assertSendable(f) {
50925
51614
  } catch {
50926
51615
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
50927
51616
  }
50928
- const inbox = join33(stateReal, "inbox");
51617
+ const inbox = join36(stateReal, "inbox");
50929
51618
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
50930
51619
  throw new Error(`refusing to send channel state: ${f}`);
50931
51620
  }
@@ -50944,7 +51633,7 @@ function assertSendable(f) {
50944
51633
  }
50945
51634
  function readAccessFile() {
50946
51635
  try {
50947
- const raw = readFileSync34(ACCESS_FILE, "utf8");
51636
+ const raw = readFileSync36(ACCESS_FILE, "utf8");
50948
51637
  const parsed = JSON.parse(raw);
50949
51638
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
50950
51639
  const groups = {};
@@ -51009,9 +51698,9 @@ function assertAllowedChat(chat_id) {
51009
51698
  function saveAccess(a) {
51010
51699
  if (STATIC)
51011
51700
  return;
51012
- mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
51701
+ mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
51013
51702
  const tmp = ACCESS_FILE + ".tmp";
51014
- writeFileSync23(tmp, JSON.stringify(a, null, 2) + `
51703
+ writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
51015
51704
  `, { mode: 384 });
51016
51705
  renameSync13(tmp, ACCESS_FILE);
51017
51706
  }
@@ -51031,7 +51720,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
51031
51720
  if (HISTORY_ENABLED) {
51032
51721
  try {
51033
51722
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
51034
- process.stderr.write(`telegram gateway: history capture enabled at ${join33(STATE_DIR, "history.db")}
51723
+ process.stderr.write(`telegram gateway: history capture enabled at ${join36(STATE_DIR, "history.db")}
51035
51724
  `);
51036
51725
  } catch (err) {
51037
51726
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -51048,10 +51737,10 @@ try {
51048
51737
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s) as ended_via='restart'
51049
51738
  `);
51050
51739
  } else {
51051
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join33(agentDir, "telegram", "registry.db")}
51740
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join36(agentDir, "telegram", "registry.db")}
51052
51741
  `);
51053
51742
  }
51054
- const pendingEnvPath = join33(agentDir, ".pending-turn.env");
51743
+ const pendingEnvPath = join36(agentDir, ".pending-turn.env");
51055
51744
  try {
51056
51745
  const pending2 = findMostRecentInterruptedTurn(turnsDb);
51057
51746
  if (pending2 != null) {
@@ -51065,13 +51754,13 @@ try {
51065
51754
  `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
51066
51755
  ];
51067
51756
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
51068
- writeFileSync23(pendingEnvTmp, lines.join(`
51757
+ writeFileSync25(pendingEnvTmp, lines.join(`
51069
51758
  `) + `
51070
51759
  `, { mode: 384 });
51071
51760
  renameSync13(pendingEnvTmp, pendingEnvPath);
51072
51761
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
51073
51762
  `);
51074
- } else if (existsSync36(pendingEnvPath)) {
51763
+ } else if (existsSync39(pendingEnvPath)) {
51075
51764
  rmSync4(pendingEnvPath, { force: true });
51076
51765
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
51077
51766
  `);
@@ -51125,7 +51814,7 @@ function checkApprovals() {
51125
51814
  return;
51126
51815
  }
51127
51816
  for (const senderId of files) {
51128
- const file = join33(APPROVED_DIR, senderId);
51817
+ const file = join36(APPROVED_DIR, senderId);
51129
51818
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
51130
51819
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
51131
51820
  `);
@@ -52014,11 +52703,11 @@ var unpinProgressCardForChat = null;
52014
52703
  var getPinnedProgressCardMessageId = null;
52015
52704
  var completeProgressCardTurn = null;
52016
52705
  var subagentWatcher = null;
52017
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join33(STATE_DIR, "gateway.sock");
52018
- mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
52019
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join33(STATE_DIR, "gateway.pid.json");
52020
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join33(STATE_DIR, "gateway-session.json");
52021
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join33(STATE_DIR, "clean-shutdown.json");
52706
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join36(STATE_DIR, "gateway.sock");
52707
+ mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
52708
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join36(STATE_DIR, "gateway.pid.json");
52709
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join36(STATE_DIR, "gateway-session.json");
52710
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join36(STATE_DIR, "clean-shutdown.json");
52022
52711
  var GATEWAY_STARTED_AT_MS = Date.now();
52023
52712
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
52024
52713
  var activeBootCard = null;
@@ -52047,7 +52736,7 @@ function ensureIssuesCard(chatId, threadId) {
52047
52736
  bot: botApi,
52048
52737
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
52049
52738
  `),
52050
- persistPath: join33(stateDir, "issues-card.json")
52739
+ persistPath: join36(stateDir, "issues-card.json")
52051
52740
  });
52052
52741
  activeIssuesWatcher = startIssuesWatcher({
52053
52742
  stateDir,
@@ -52221,13 +52910,13 @@ startTimer2({
52221
52910
  }
52222
52911
  });
52223
52912
  var inboundSpool = STATIC ? undefined : createInboundSpool({
52224
- path: join33(STATE_DIR, "inbound-spool.jsonl"),
52913
+ path: join36(STATE_DIR, "inbound-spool.jsonl"),
52225
52914
  fs: {
52226
- appendFileSync: (p, d) => appendFileSync3(p, d),
52227
- readFileSync: (p) => readFileSync34(p, "utf8"),
52228
- writeFileSync: (p, d) => writeFileSync23(p, d),
52915
+ appendFileSync: (p, d) => appendFileSync5(p, d),
52916
+ readFileSync: (p) => readFileSync36(p, "utf8"),
52917
+ writeFileSync: (p, d) => writeFileSync25(p, d),
52229
52918
  renameSync: (a, b) => renameSync13(a, b),
52230
- existsSync: (p) => existsSync36(p),
52919
+ existsSync: (p) => existsSync39(p),
52231
52920
  statSizeSync: (p) => statSync13(p).size
52232
52921
  }
52233
52922
  });
@@ -52354,7 +53043,7 @@ var ipcServer = createIpcServer({
52354
53043
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
52355
53044
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
52356
53045
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
52357
- configSnapshotPath: join33(resolvedAgentDirForCard, ".config-snapshot.json"),
53046
+ configSnapshotPath: join36(resolvedAgentDirForCard, ".config-snapshot.json"),
52358
53047
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
52359
53048
  }, ackMsgId).then((handle) => {
52360
53049
  activeBootCard = handle;
@@ -52769,6 +53458,62 @@ ${reminder}
52769
53458
  log: (msg) => process.stderr.write(`telegram gateway: ipc \u2014 ${msg}
52770
53459
  `)
52771
53460
  });
53461
+ (() => {
53462
+ try {
53463
+ const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
53464
+ if (!selfAgent)
53465
+ return;
53466
+ let viaGateway = false;
53467
+ try {
53468
+ const cfg = loadConfig2();
53469
+ const raw = cfg.agents?.[selfAgent];
53470
+ viaGateway = raw ? resolveAgentConfig2(cfg.defaults, cfg.profiles, raw).channels?.telegram?.webhook_via_gateway === true : false;
53471
+ } catch (err) {
53472
+ process.stderr.write(`telegram gateway: webhook-ingest config probe failed: ${err.message}
53473
+ `);
53474
+ }
53475
+ if (!viaGateway)
53476
+ return;
53477
+ const allowedUids = [];
53478
+ const ownUid = typeof process.getuid === "function" ? process.getuid() : null;
53479
+ if (ownUid !== null)
53480
+ allowedUids.push(ownUid);
53481
+ const receiverUidRaw = process.env.SWITCHROOM_WEBHOOK_RECEIVER_UID;
53482
+ const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
53483
+ if (Number.isInteger(receiverUid))
53484
+ allowedUids.push(receiverUid);
53485
+ const socketPath = join36(STATE_DIR, "webhook.sock");
53486
+ const webhookInject = (agentName3, inbound) => {
53487
+ const msg = inbound;
53488
+ const delivered = ipcServer.sendToAgent(agentName3, msg);
53489
+ if (delivered)
53490
+ markClaudeBusyForInbound(msg);
53491
+ else
53492
+ pendingInboundBuffer.push(agentName3, msg);
53493
+ return delivered;
53494
+ };
53495
+ startWebhookIngestServer({
53496
+ socketPath,
53497
+ allowedUids,
53498
+ log: (s) => process.stderr.write(`telegram gateway: ${s}`),
53499
+ onRecord: (req) => recordWebhookEvent({
53500
+ agent: selfAgent,
53501
+ source: req.source,
53502
+ event_type: req.event_type,
53503
+ ts: req.ts,
53504
+ rendered_text: req.rendered_text,
53505
+ payload: req.payload,
53506
+ ...req.delivery_id ? { delivery_id: req.delivery_id } : {}
53507
+ }, {
53508
+ inject: webhookInject,
53509
+ log: (s) => process.stderr.write(`telegram gateway: ${s}`)
53510
+ })
53511
+ });
53512
+ } catch (err) {
53513
+ process.stderr.write(`telegram gateway: webhook-ingest server start failed (non-fatal): ${err.message}
53514
+ `);
53515
+ }
53516
+ })();
52772
53517
  var IDLE_DRAIN_INTERVAL_MS = 5000;
52773
53518
  if (!STATIC) {
52774
53519
  setInterval(() => {
@@ -53734,11 +54479,11 @@ async function executeSendGif(rawArgs) {
53734
54479
  };
53735
54480
  }
53736
54481
  async function publishToTelegraph(text, shortName, authorName) {
53737
- const accountPath = join33(STATE_DIR, "telegraph-account.json");
54482
+ const accountPath = join36(STATE_DIR, "telegraph-account.json");
53738
54483
  let account = null;
53739
54484
  try {
53740
- if (existsSync36(accountPath)) {
53741
- const raw = readFileSync34(accountPath, "utf-8");
54485
+ if (existsSync39(accountPath)) {
54486
+ const raw = readFileSync36(accountPath, "utf-8");
53742
54487
  const parsed = JSON.parse(raw);
53743
54488
  if (parsed.shortName && parsed.accessToken) {
53744
54489
  account = parsed;
@@ -53757,8 +54502,8 @@ async function publishToTelegraph(text, shortName, authorName) {
53757
54502
  }
53758
54503
  account = created.value;
53759
54504
  try {
53760
- mkdirSync23(STATE_DIR, { recursive: true, mode: 448 });
53761
- writeFileSync23(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
54505
+ mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
54506
+ writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
53762
54507
  } catch (err) {
53763
54508
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
53764
54509
  `);
@@ -54000,9 +54745,9 @@ async function executeDownloadAttachment(args) {
54000
54745
  fileUniqueId: file.file_unique_id,
54001
54746
  now: Date.now()
54002
54747
  });
54003
- mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
54748
+ mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
54004
54749
  assertInsideInbox(INBOX_DIR, dlPath);
54005
- writeFileSync23(dlPath, buf, { mode: 384 });
54750
+ writeFileSync25(dlPath, buf, { mode: 384 });
54006
54751
  return { content: [{ type: "text", text: dlPath }] };
54007
54752
  }
54008
54753
  async function executeEditMessage(args) {
@@ -55777,14 +56522,14 @@ function restartMarkerPath() {
55777
56522
  const agentDir = resolveAgentDirFromEnv();
55778
56523
  if (!agentDir)
55779
56524
  return null;
55780
- return join33(agentDir, "restart-pending.json");
56525
+ return join36(agentDir, "restart-pending.json");
55781
56526
  }
55782
56527
  function writeRestartMarker(marker) {
55783
56528
  const p = restartMarkerPath();
55784
56529
  if (!p)
55785
56530
  return;
55786
56531
  try {
55787
- writeFileSync23(p, JSON.stringify(marker));
56532
+ writeFileSync25(p, JSON.stringify(marker));
55788
56533
  lastPlannedRestartAt = Date.now();
55789
56534
  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}
55790
56535
  `);
@@ -55803,7 +56548,7 @@ function readRestartMarker() {
55803
56548
  if (!p)
55804
56549
  return null;
55805
56550
  try {
55806
- return JSON.parse(readFileSync34(p, "utf8"));
56551
+ return JSON.parse(readFileSync36(p, "utf8"));
55807
56552
  } catch {
55808
56553
  return null;
55809
56554
  }
@@ -55901,7 +56646,7 @@ var _dockerReachable;
55901
56646
  function isDockerReachable() {
55902
56647
  if (_dockerReachable !== undefined)
55903
56648
  return _dockerReachable;
55904
- if (!existsSync36("/var/run/docker.sock")) {
56649
+ if (!existsSync39("/var/run/docker.sock")) {
55905
56650
  _dockerReachable = false;
55906
56651
  return _dockerReachable;
55907
56652
  }
@@ -55918,12 +56663,12 @@ function _resetDockerReachableCache() {
55918
56663
  }
55919
56664
  function spawnSwitchroomDetached(args, onFailure) {
55920
56665
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
55921
- const logPath = join33(STATE_DIR, "detached-spawn.log");
56666
+ const logPath = join36(STATE_DIR, "detached-spawn.log");
55922
56667
  let outFd = null;
55923
56668
  try {
55924
- mkdirSync23(STATE_DIR, { recursive: true });
56669
+ mkdirSync26(STATE_DIR, { recursive: true });
55925
56670
  outFd = openSync8(logPath, "a");
55926
- writeFileSync23(logPath, `
56671
+ writeFileSync25(logPath, `
55927
56672
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
55928
56673
  `, { flag: "a" });
55929
56674
  } catch {}
@@ -55949,7 +56694,7 @@ function spawnSwitchroomDetached(args, onFailure) {
55949
56694
  return;
55950
56695
  let tail = "";
55951
56696
  try {
55952
- const full = readFileSync34(logPath, "utf8");
56697
+ const full = readFileSync36(logPath, "utf8");
55953
56698
  tail = full.split(`
55954
56699
  `).slice(-30).join(`
55955
56700
  `).trim();
@@ -56291,10 +57036,10 @@ bot.use(async (ctx, next) => {
56291
57036
  });
56292
57037
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
56293
57038
  try {
56294
- const auditPath = join33(homedir12(), ".switchroom", "vault-audit.log");
56295
- if (!existsSync36(auditPath))
57039
+ const auditPath = join36(homedir14(), ".switchroom", "vault-audit.log");
57040
+ if (!existsSync39(auditPath))
56296
57041
  return [];
56297
- const raw = readFileSync34(auditPath, "utf8");
57042
+ const raw = readFileSync36(auditPath, "utf8");
56298
57043
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
56299
57044
  } catch {
56300
57045
  return [];
@@ -56345,7 +57090,7 @@ async function buildAgentMetadata(agentName3) {
56345
57090
  try {
56346
57091
  const agentDir = resolveAgentDirFromEnv();
56347
57092
  if (agentDir) {
56348
- const raw = readFileSync34(join33(agentDir, ".claude", ".claude.json"), "utf8");
57093
+ const raw = readFileSync36(join36(agentDir, ".claude", ".claude.json"), "utf8");
56349
57094
  claudeJson = JSON.parse(raw);
56350
57095
  }
56351
57096
  } catch {}
@@ -56559,10 +57304,10 @@ bot.command("restart", async (ctx) => {
56559
57304
  function flushAgentHandoff(agentDir) {
56560
57305
  let removed = 0;
56561
57306
  for (const fname of [".handoff.md", ".handoff-topic"]) {
56562
- const p = join33(agentDir, fname);
57307
+ const p = join36(agentDir, fname);
56563
57308
  try {
56564
- if (existsSync36(p)) {
56565
- unlinkSync14(p);
57309
+ if (existsSync39(p)) {
57310
+ unlinkSync15(p);
56566
57311
  removed++;
56567
57312
  }
56568
57313
  } catch (err) {
@@ -56617,7 +57362,7 @@ async function handleNewOrResetCommand(ctx, kind) {
56617
57362
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
56618
57363
  if (agentDir != null) {
56619
57364
  try {
56620
- writeFileSync23(join33(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
57365
+ writeFileSync25(join36(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
56621
57366
  `, "utf8");
56622
57367
  } catch (err) {
56623
57368
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -56976,16 +57721,16 @@ bot.command("interrupt", async (ctx) => {
56976
57721
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
56977
57722
  });
56978
57723
  var lockoutOps = {
56979
- readFileSync: (p, enc) => readFileSync34(p, enc),
56980
- writeFileSync: (p, data, opts) => writeFileSync23(p, data, opts),
56981
- existsSync: (p) => existsSync36(p),
56982
- mkdirSync: (p, opts) => mkdirSync23(p, opts),
56983
- joinPath: (...parts) => join33(...parts)
57724
+ readFileSync: (p, enc) => readFileSync36(p, enc),
57725
+ writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
57726
+ existsSync: (p) => existsSync39(p),
57727
+ mkdirSync: (p, opts) => mkdirSync26(p, opts),
57728
+ joinPath: (...parts) => join36(...parts)
56984
57729
  };
56985
57730
  var FLEET_FALLBACK_DEDUP_MS = 30000;
56986
57731
  function isAuthBrokerSocketReachable() {
56987
57732
  try {
56988
- return existsSync36(resolveAuthBrokerSocketPath2());
57733
+ return existsSync39(resolveAuthBrokerSocketPath2());
56989
57734
  } catch {
56990
57735
  return false;
56991
57736
  }
@@ -57046,7 +57791,7 @@ async function runCreditWatch() {
57046
57791
  if (!agentDir)
57047
57792
  return;
57048
57793
  const agentName3 = getMyAgentName();
57049
- const claudeConfigDir = join33(agentDir, ".claude");
57794
+ const claudeConfigDir = join36(agentDir, ".claude");
57050
57795
  const stateDir = STATE_DIR;
57051
57796
  const reason = readClaudeJsonOverage(claudeConfigDir);
57052
57797
  const prev = loadCreditState(stateDir);
@@ -57349,10 +58094,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
57349
58094
  return;
57350
58095
  }
57351
58096
  const { token, id } = result;
57352
- const tokenPath = join33(homedir12(), ".switchroom", "agents", agentName3, ".vault-token");
58097
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
57353
58098
  try {
57354
- mkdirSync23(join33(homedir12(), ".switchroom", "agents", agentName3), { recursive: true });
57355
- writeFileSync23(tokenPath, token, { mode: 384 });
58099
+ mkdirSync26(join36(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
58100
+ writeFileSync25(tokenPath, token, { mode: 384 });
57356
58101
  } catch (err) {
57357
58102
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
57358
58103
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -57428,10 +58173,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
57428
58173
  return;
57429
58174
  }
57430
58175
  const { token, id } = result;
57431
- const tokenPath = join33(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
58176
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
57432
58177
  try {
57433
- mkdirSync23(join33(homedir12(), ".switchroom", "agents", pending2.agent), { recursive: true });
57434
- writeFileSync23(tokenPath, token, { mode: 384 });
58178
+ mkdirSync26(join36(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
58179
+ writeFileSync25(tokenPath, token, { mode: 384 });
57435
58180
  } catch (err) {
57436
58181
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
57437
58182
  <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 });
@@ -57906,10 +58651,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
57906
58651
  return;
57907
58652
  }
57908
58653
  const { token, id } = result;
57909
- const tokenPath = join33(homedir12(), ".switchroom", "agents", state4.agent, ".vault-token");
58654
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
57910
58655
  try {
57911
- mkdirSync23(join33(homedir12(), ".switchroom", "agents", state4.agent), { recursive: true });
57912
- writeFileSync23(tokenPath, token, { mode: 384 });
58656
+ mkdirSync26(join36(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
58657
+ writeFileSync25(tokenPath, token, { mode: 384 });
57913
58658
  } catch (err) {
57914
58659
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
57915
58660
  return;
@@ -58757,7 +59502,7 @@ bot.command("usage", async (ctx) => {
58757
59502
  await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
58758
59503
  return;
58759
59504
  }
58760
- const result = await fetchQuota2({ claudeConfigDir: join33(agentDir, ".claude") });
59505
+ const result = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
58761
59506
  if (!result.ok) {
58762
59507
  await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
58763
59508
  return;
@@ -59198,7 +59943,7 @@ ${prettyInput}`;
59198
59943
  const unifiedDiff = (() => {
59199
59944
  try {
59200
59945
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
59201
- const raw = readFileSync34(cfgPath, "utf8");
59946
+ const raw = readFileSync36(cfgPath, "utf8");
59202
59947
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: rule.rule, configText: raw });
59203
59948
  } catch (err) {
59204
59949
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -59335,9 +60080,9 @@ bot.on("message:photo", async (ctx) => {
59335
60080
  fileUniqueId: best.file_unique_id,
59336
60081
  now: Date.now()
59337
60082
  });
59338
- mkdirSync23(INBOX_DIR, { recursive: true, mode: 448 });
60083
+ mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
59339
60084
  assertInsideInbox(INBOX_DIR, dlPath);
59340
- writeFileSync23(dlPath, buf, { mode: 384 });
60085
+ writeFileSync25(dlPath, buf, { mode: 384 });
59341
60086
  return dlPath;
59342
60087
  } catch (err) {
59343
60088
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -59377,8 +60122,8 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
59377
60122
  let apiKey = null;
59378
60123
  try {
59379
60124
  const path = __require("path").join(__require("os").homedir(), ".switchroom", "openai-api-key");
59380
- if (existsSync36(path)) {
59381
- apiKey = readFileSync34(path, "utf-8").trim();
60125
+ if (existsSync39(path)) {
60126
+ apiKey = readFileSync36(path, "utf-8").trim();
59382
60127
  }
59383
60128
  } catch (err) {
59384
60129
  process.stderr.write(`telegram gateway: voice-in: failed to read api key: ${err.message}
@@ -60234,7 +60979,7 @@ var didOneTimeSetup = false;
60234
60979
  return;
60235
60980
  }
60236
60981
  })();
60237
- const resolvedAgentDirForBootCard = agentDir ?? join33(homedir12(), ".switchroom", "agents", agentSlug);
60982
+ const resolvedAgentDirForBootCard = agentDir ?? join36(homedir14(), ".switchroom", "agents", agentSlug);
60238
60983
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
60239
60984
  agentName: agentDisplayName,
60240
60985
  agentSlug,
@@ -60248,7 +60993,7 @@ var didOneTimeSetup = false;
60248
60993
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
60249
60994
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
60250
60995
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
60251
- configSnapshotPath: join33(resolvedAgentDirForBootCard, ".config-snapshot.json"),
60996
+ configSnapshotPath: join36(resolvedAgentDirForBootCard, ".config-snapshot.json"),
60252
60997
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
60253
60998
  }, ackMsgId);
60254
60999
  activeBootCard = handle;