switchroom 0.14.86 → 0.14.87

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.
@@ -29710,7 +29710,7 @@ import {
29710
29710
  constants as fsConstants4,
29711
29711
  existsSync as existsSync52,
29712
29712
  realpathSync as realpathSync4,
29713
- statSync as statSync21
29713
+ statSync as statSync22
29714
29714
  } from "node:fs";
29715
29715
  import { userInfo, homedir as homedir25 } from "node:os";
29716
29716
  import { join as join46 } from "node:path";
@@ -29728,7 +29728,7 @@ function defaultStatVault(path4) {
29728
29728
  let uid = -1;
29729
29729
  let mode = 0;
29730
29730
  try {
29731
- const s = statSync21(real);
29731
+ const s = statSync22(real);
29732
29732
  uid = s.uid;
29733
29733
  mode = s.mode & 511;
29734
29734
  } catch {
@@ -31226,7 +31226,7 @@ var init_doctor_agent_smoke = __esm(() => {
31226
31226
 
31227
31227
  // src/cli/doctor-vault-broker-durability.ts
31228
31228
  import { execFileSync as execFileSync18 } from "node:child_process";
31229
- import { existsSync as existsSync54, statSync as statSync22 } from "node:fs";
31229
+ import { existsSync as existsSync54, statSync as statSync23 } from "node:fs";
31230
31230
  import { homedir as homedir33 } from "node:os";
31231
31231
  import { join as join55 } from "node:path";
31232
31232
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
@@ -31253,7 +31253,7 @@ function defaultStatHost(p) {
31253
31253
  if (!existsSync54(p))
31254
31254
  return null;
31255
31255
  try {
31256
- const s = statSync22(p, { bigint: true });
31256
+ const s = statSync23(p, { bigint: true });
31257
31257
  return { ino: s.ino, size: Number(s.size) };
31258
31258
  } catch {
31259
31259
  return null;
@@ -31463,7 +31463,7 @@ function probeAutoUnlockBlob(home2) {
31463
31463
  fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
31464
31464
  };
31465
31465
  }
31466
- const sz = statSync22(blobPath).size;
31466
+ const sz = statSync23(blobPath).size;
31467
31467
  if (sz === 0) {
31468
31468
  return {
31469
31469
  name: "vault-broker: auto-unlock blob",
@@ -31563,7 +31563,7 @@ import {
31563
31563
  mkdirSync as mkdirSync30,
31564
31564
  readFileSync as readFileSync49,
31565
31565
  readdirSync as readdirSync20,
31566
- statSync as statSync23
31566
+ statSync as statSync24
31567
31567
  } from "node:fs";
31568
31568
  import { dirname as dirname12, join as join56, resolve as resolve32 } from "node:path";
31569
31569
  import { createPublicKey, createPrivateKey } from "node:crypto";
@@ -31576,7 +31576,7 @@ function findInNvm(bin) {
31576
31576
  for (const v of versions) {
31577
31577
  const candidate = join56(nvmRoot, v, "bin", bin);
31578
31578
  try {
31579
- const s = statSync23(candidate);
31579
+ const s = statSync24(candidate);
31580
31580
  if (s.isFile() || s.isSymbolicLink()) {
31581
31581
  return candidate;
31582
31582
  }
@@ -49404,7 +49404,7 @@ __export(exports_server2, {
49404
49404
  TOOLS: () => TOOLS2
49405
49405
  });
49406
49406
  import { randomBytes as randomBytes15 } from "node:crypto";
49407
- import { existsSync as existsSync83, readFileSync as readFileSync68 } from "node:fs";
49407
+ import { existsSync as existsSync83, readFileSync as readFileSync69 } from "node:fs";
49408
49408
  function selfSocketPath() {
49409
49409
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
49410
49410
  }
@@ -49582,7 +49582,7 @@ function getLastUpdateApplyStatus() {
49582
49582
  }
49583
49583
  let raw;
49584
49584
  try {
49585
- raw = readFileSync68(path8, "utf-8");
49585
+ raw = readFileSync69(path8, "utf-8");
49586
49586
  } catch (err2) {
49587
49587
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
49588
49588
  }
@@ -49815,8 +49815,8 @@ var {
49815
49815
  } = import__.default;
49816
49816
 
49817
49817
  // src/build-info.ts
49818
- var VERSION = "0.14.86";
49819
- var COMMIT_SHA = "8c518120";
49818
+ var VERSION = "0.14.87";
49819
+ var COMMIT_SHA = "5ad3e254";
49820
49820
 
49821
49821
  // src/cli/agent.ts
49822
49822
  init_source();
@@ -50583,6 +50583,36 @@ Example response shapes:
50583
50583
  \`docker/Dockerfile.agent\` and rebuild the agent image."
50584
50584
  - "I tried to clone into \`/workspace\` \u2014 that path doesn't exist in my
50585
50585
  sandbox. Cloning into \`$HOME/workspace\` instead."`;
50586
+ var DELIVERY_GUIDANCE = `## Delivering a file to the user
50587
+
50588
+ You run in a container. A local path like \`/state/agent/...\` or
50589
+ \`/tmp/...\` means **nothing** to the user \u2014 they are not on your box and
50590
+ cannot open it. When you produce a file the user should have, **deliver
50591
+ it**, don't print its path.
50592
+
50593
+ ### Send it in Telegram (default for most files)
50594
+ The \`reply\` tool attaches files: pass \`files: ["/abs/path"]\` (images
50595
+ send as photos, everything else as documents; 50 MB max each). This is
50596
+ the right channel for almost anything \u2014 a report, a CSV, a chart, a
50597
+ generated doc \u2014 the user gets it straight in the chat. Prefer this.
50598
+
50599
+ ### Put it in their Drive (for files they'll keep or edit, or > 50 MB)
50600
+ Run \`switchroom deliver-file <path>\`. It uploads the file into the user's
50601
+ \`Switchroom/<your-agent-name>\` folder on their connected drive (creating
50602
+ the folder if needed) and prints a share link. Reply with that link \u2014 not
50603
+ a path. This is the reliable way: switchroom owns the destination and the
50604
+ upload, so you don't guess folder names or hit a per-file approval. (If you
50605
+ prefer the raw \`gdrive\` / \`ms-365\` MCP tools, still target that same
50606
+ \`Switchroom/<your-agent-name>\` folder \u2014 never scatter files loose in the
50607
+ user's drive.)
50608
+
50609
+ ### If you can't deliver
50610
+ No Drive connected and the file is too big for Telegram? Say so plainly
50611
+ and offer the fix ("connect a Drive from the dashboard and I'll put it
50612
+ there"). Don't fall back to handing over a local path that leads nowhere.
50613
+
50614
+ The rule: the user must be able to **reach** what you made. A path on your
50615
+ container is not delivery.`;
50586
50616
  var TELEGRAM_GUIDANCE = `## Talking to a human on Telegram
50587
50617
 
50588
50618
  There is a real person on the other end. Every turn should feel like
@@ -50765,6 +50795,8 @@ function renderFleetInvariants() {
50765
50795
  "",
50766
50796
  SANDBOX_GUIDANCE,
50767
50797
  "",
50798
+ DELIVERY_GUIDANCE,
50799
+ "",
50768
50800
  TELEGRAM_GUIDANCE,
50769
50801
  "",
50770
50802
  MEMORY_GUIDANCE,
@@ -58626,6 +58658,231 @@ async function readHiddenLine2(prompt) {
58626
58658
  });
58627
58659
  }
58628
58660
 
58661
+ // src/cli/auth-schedule.ts
58662
+ init_source();
58663
+ init_broker_call();
58664
+ init_helpers();
58665
+ var WEEKLY_WALL_PCT = 99.5;
58666
+ function resolveWindow(account, probe) {
58667
+ const entry = probe?.results.find((r) => r.label === account.label);
58668
+ if (entry && entry.result.ok) {
58669
+ const d = entry.result.data;
58670
+ return {
58671
+ fiveHourPct: d.fiveHourUtilizationPct,
58672
+ fiveHourResetAt: d.fiveHourResetAt,
58673
+ weeklyPct: d.sevenDayUtilizationPct,
58674
+ weeklyResetAt: d.sevenDayResetAt,
58675
+ source: "live"
58676
+ };
58677
+ }
58678
+ const lq = account.last_quota;
58679
+ if (lq) {
58680
+ return {
58681
+ fiveHourPct: lq.fiveHourUtilizationPct,
58682
+ fiveHourResetAt: lq.fiveHourResetAt ? new Date(lq.fiveHourResetAt) : null,
58683
+ weeklyPct: lq.sevenDayUtilizationPct,
58684
+ weeklyResetAt: lq.sevenDayResetAt ? new Date(lq.sevenDayResetAt) : null,
58685
+ source: "cached"
58686
+ };
58687
+ }
58688
+ return {
58689
+ fiveHourPct: null,
58690
+ fiveHourResetAt: null,
58691
+ weeklyPct: null,
58692
+ weeklyResetAt: null,
58693
+ source: "none"
58694
+ };
58695
+ }
58696
+ function classifyState(args) {
58697
+ const { exhausted, exhaustedUntil, window: window2, now } = args;
58698
+ const hasWindowData = window2.fiveHourPct !== null || window2.weeklyPct !== null || window2.fiveHourResetAt !== null || window2.weeklyResetAt !== null;
58699
+ if (window2.weeklyPct !== null && window2.weeklyPct >= WEEKLY_WALL_PCT && window2.weeklyResetAt !== null && window2.weeklyResetAt.getTime() > now.getTime()) {
58700
+ return "weekly-walled";
58701
+ }
58702
+ if (!exhausted) {
58703
+ return hasWindowData || exhaustedUntil !== null ? "healthy" : "unprobed";
58704
+ }
58705
+ const until = exhaustedUntil?.getTime();
58706
+ if (until === undefined)
58707
+ return "walled";
58708
+ const remaining = until - now.getTime();
58709
+ if (remaining <= 0)
58710
+ return "healthy";
58711
+ const TOL_MS = 15 * 60 * 1000;
58712
+ const near = (d) => d !== null && Math.abs(d.getTime() - until) <= TOL_MS;
58713
+ if (near(window2.weeklyResetAt))
58714
+ return "weekly-walled";
58715
+ if (near(window2.fiveHourResetAt))
58716
+ return "5h-walled";
58717
+ if (remaining < 6 * 3600000)
58718
+ return "5h-walled";
58719
+ if (remaining >= 20 * 3600000)
58720
+ return "weekly-walled";
58721
+ return "walled";
58722
+ }
58723
+ function buildScheduleRows(state, probe, now) {
58724
+ const rows = state.accounts.map((a) => {
58725
+ const window2 = resolveWindow(a, probe);
58726
+ const exhaustedUntil = typeof a.exhausted_until === "number" ? new Date(a.exhausted_until) : null;
58727
+ const exhausted = a.exhausted && (exhaustedUntil?.getTime() ?? 0) > now.getTime();
58728
+ const rank = state.fallback_order.indexOf(a.label);
58729
+ return {
58730
+ label: a.label,
58731
+ isActive: a.label === state.active,
58732
+ fallbackRank: rank === -1 ? null : rank + 1,
58733
+ window: window2,
58734
+ exhausted,
58735
+ exhaustedUntil,
58736
+ state: classifyState({ exhausted, exhaustedUntil, window: window2, now })
58737
+ };
58738
+ });
58739
+ const key = (r) => r.isActive ? -1 : r.fallbackRank ?? 9999;
58740
+ return rows.sort((a, b) => key(a) - key(b) || a.label.localeCompare(b.label));
58741
+ }
58742
+ var EM_DASH = "\u2014";
58743
+ function formatDuration(ms) {
58744
+ if (ms <= 0)
58745
+ return "now";
58746
+ const d = Math.floor(ms / 86400000);
58747
+ const h = Math.floor(ms % 86400000 / 3600000);
58748
+ const m = Math.floor(ms % 3600000 / 60000);
58749
+ if (d > 0)
58750
+ return `${d}d ${h}h`;
58751
+ if (h > 0)
58752
+ return `${h}h ${m}m`;
58753
+ return `${m}m`;
58754
+ }
58755
+ function tzOpts(tz) {
58756
+ return tz ? { timeZone: tz } : {};
58757
+ }
58758
+ function formatResetDay(d, tz) {
58759
+ const opts = tzOpts(tz);
58760
+ const dow = d.toLocaleDateString("en-US", { weekday: "short", ...opts });
58761
+ const hm = d.toLocaleTimeString("en-GB", {
58762
+ hour: "2-digit",
58763
+ minute: "2-digit",
58764
+ hour12: false,
58765
+ ...opts
58766
+ });
58767
+ return `${dow} ${hm}`;
58768
+ }
58769
+ function formatFiveHourCell(w, now) {
58770
+ if (w.fiveHourPct === null)
58771
+ return EM_DASH;
58772
+ const reset = w.fiveHourResetAt ? ` \u00b7 ${formatDuration(w.fiveHourResetAt.getTime() - now.getTime())}` : "";
58773
+ return `${Math.round(w.fiveHourPct)}%${reset}`;
58774
+ }
58775
+ function formatWeeklyCell(w, now, tz) {
58776
+ if (w.weeklyPct === null && w.weeklyResetAt === null)
58777
+ return EM_DASH;
58778
+ const pct = w.weeklyPct === null ? EM_DASH : `${Math.round(w.weeklyPct)}%`;
58779
+ if (!w.weeklyResetAt)
58780
+ return pct;
58781
+ const rel = formatDuration(w.weeklyResetAt.getTime() - now.getTime());
58782
+ return `${pct} \u00b7 ${formatResetDay(w.weeklyResetAt, tz)} (${rel})`;
58783
+ }
58784
+ function poolLabel(row) {
58785
+ if (row.isActive)
58786
+ return row.fallbackRank ? `active #${row.fallbackRank}` : "active";
58787
+ if (row.fallbackRank)
58788
+ return `#${row.fallbackRank}`;
58789
+ return "excluded";
58790
+ }
58791
+ function formatStateCell(row, now) {
58792
+ const horizon = row.state === "weekly-walled" ? row.window.weeklyResetAt ?? row.exhaustedUntil : row.state === "5h-walled" ? row.window.fiveHourResetAt ?? row.exhaustedUntil : row.exhaustedUntil;
58793
+ const rel = horizon ? ` \u00b7 ${formatDuration(horizon.getTime() - now.getTime())}` : "";
58794
+ switch (row.state) {
58795
+ case "healthy":
58796
+ return "healthy";
58797
+ case "unprobed":
58798
+ return "no recent probe";
58799
+ case "5h-walled":
58800
+ return `5h-walled${rel}`;
58801
+ case "weekly-walled":
58802
+ return `weekly-walled${rel}`;
58803
+ case "walled":
58804
+ return `walled${rel}`;
58805
+ }
58806
+ }
58807
+ var W = { label: 30, pool: 11, five: 15, weekly: 26 };
58808
+ function pad3(s, n) {
58809
+ if (s.length > n)
58810
+ return s.length > 1 ? s.slice(0, n - 1) + "\u2026" : s;
58811
+ return s.padEnd(n);
58812
+ }
58813
+ function glyphFor(row, color) {
58814
+ const g = row.isActive ? "\u25cf" : row.exhausted ? "!" : row.state === "unprobed" ? "\u00b7" : "\u2713";
58815
+ if (!color)
58816
+ return g;
58817
+ return row.isActive ? source_default.green(g) : row.exhausted ? source_default.red(g) : source_default.gray(g);
58818
+ }
58819
+ function colorState(text, state, color) {
58820
+ if (!color)
58821
+ return text;
58822
+ switch (state) {
58823
+ case "healthy":
58824
+ return source_default.green(text);
58825
+ case "weekly-walled":
58826
+ return source_default.red(text);
58827
+ case "5h-walled":
58828
+ return source_default.yellow(text);
58829
+ default:
58830
+ return source_default.gray(text);
58831
+ }
58832
+ }
58833
+ function formatScheduleRows(rows, now, opts = {}) {
58834
+ const color = opts.color ?? true;
58835
+ const line = (glyph, label, p, five, weekly, state) => ` ${glyph} ${pad3(label, W.label)} ${pad3(p, W.pool)} ${pad3(five, W.five)} ${pad3(weekly, W.weekly)} ${state}`;
58836
+ const header = line(" ", "ACCOUNT", "POOL", "5H WINDOW", "WEEKLY WINDOW", "STATE");
58837
+ const out = [color ? source_default.bold(header) : header];
58838
+ for (const row of rows) {
58839
+ out.push(line(glyphFor(row, color), row.label, poolLabel(row), formatFiveHourCell(row.window, now), formatWeeklyCell(row.window, now, opts.tz), colorState(formatStateCell(row, now), row.state, color)));
58840
+ }
58841
+ return out;
58842
+ }
58843
+ function formatFooter(rows, opts, now, tz) {
58844
+ const zone = tz ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
58845
+ const at = formatResetDay(now, tz);
58846
+ const src = opts.probedLive ? `probed live \u00b7 ${at} ${zone}` : opts.liveRequested ? `live probe unavailable \u2014 showing cached snapshot \u00b7 times in ${zone}` : `cached snapshot (run without --cached for a live probe) \u00b7 times in ${zone}`;
58847
+ const anyUnprobed = rows.some((r) => r.window.source === "none");
58848
+ const lines = [src];
58849
+ if (!opts.probedLive && anyUnprobed) {
58850
+ lines.push(opts.liveRequested ? "some accounts have no quota data \u2014 the broker may not have served a probe" : "some accounts have no cached probe yet \u2014 run without --cached to populate");
58851
+ }
58852
+ return lines;
58853
+ }
58854
+ function registerAuthScheduleSubcommands(_program, authParent) {
58855
+ authParent.command("schedule").description("Per-account quota schedule \u2014 5h vs WEEKLY window, % used, and reset day (live probe by default)").option("--cached", "Use the broker's cached snapshot; skip the live probe").option("--json", "Output JSON").action(withConfigError(async (opts) => {
58856
+ const live = !opts.cached;
58857
+ const { state, probe } = await brokerCall(async (client) => {
58858
+ const s = await client.listState();
58859
+ let p;
58860
+ if (live) {
58861
+ const labels = s.accounts.map((a) => a.label);
58862
+ p = labels.length > 0 ? await client.probeQuota(labels).catch(() => {
58863
+ return;
58864
+ }) : undefined;
58865
+ }
58866
+ return { state: s, probe: p };
58867
+ });
58868
+ if (opts.json) {
58869
+ console.log(JSON.stringify({ ...state, probe: probe ?? null }, null, 2));
58870
+ return;
58871
+ }
58872
+ const now = new Date;
58873
+ const rows = buildScheduleRows(state, probe, now);
58874
+ const probedLive = live && probe !== undefined;
58875
+ console.log();
58876
+ for (const l of formatScheduleRows(rows, now))
58877
+ console.log(l);
58878
+ console.log();
58879
+ for (const l of formatFooter(rows, { liveRequested: live, probedLive }, now)) {
58880
+ console.log(source_default.gray(" " + l));
58881
+ }
58882
+ console.log();
58883
+ }));
58884
+ }
58885
+
58629
58886
  // src/cli/auth.ts
58630
58887
  init_auth_active_yaml();
58631
58888
  init_atomic();
@@ -58910,6 +59167,7 @@ function registerAuthCommand(program3) {
58910
59167
  const auth = program3.command("auth").description("Manage OAuth authentication via switchroom-auth-broker (RFC H)");
58911
59168
  registerAuthGoogleSubcommands(program3, auth);
58912
59169
  registerAuthMicrosoftSubcommands(program3, auth);
59170
+ registerAuthScheduleSubcommands(program3, auth);
58913
59171
  auth.command("heal <agent>").description("[boot self-test] emit structural auth-state diagnosis as JSON").option("--json", "Emit JSON (the only supported output)").option("--config-dir <dir>", "Override the .claude config dir to inspect (default: <agentsDir>/<agent>/.claude)").action(async (agent, opts) => {
58914
59172
  let configDir = opts.configDir;
58915
59173
  if (configDir === undefined) {
@@ -66888,7 +67146,7 @@ init_lifecycle();
66888
67146
  init_manager();
66889
67147
  init_hindsight();
66890
67148
  import { spawnSync as spawnSync5 } from "node:child_process";
66891
- import { existsSync as existsSync45, readFileSync as readFileSync41 } from "node:fs";
67149
+ import { existsSync as existsSync45, readFileSync as readFileSync41, statSync as statSync21 } from "node:fs";
66892
67150
  import { resolve as resolve27 } from "node:path";
66893
67151
  init_audit_reader();
66894
67152
 
@@ -71913,9 +72171,18 @@ function listSubagents(db, opts = {}) {
71913
72171
  }
71914
72172
 
71915
72173
  // src/web/api.ts
72174
+ function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
72175
+ try {
72176
+ const f = resolve27(agentsDir, name, "telegram", ".bridge-alive");
72177
+ return now - statSync21(f).mtimeMs <= maxAgeMs;
72178
+ } catch {
72179
+ return false;
72180
+ }
72181
+ }
71916
72182
  function handleGetAgents(config) {
71917
72183
  const statuses = getAllAgentStatuses(config);
71918
72184
  const authStatuses = getAllAuthStatuses(config);
72185
+ const agentsDir = resolveAgentsDir(config);
71919
72186
  const agents = [];
71920
72187
  for (const [name, agentConfig] of Object.entries(config.agents)) {
71921
72188
  const status = statuses[name];
@@ -71923,9 +72190,10 @@ function handleGetAgents(config) {
71923
72190
  const collection = getCollectionForAgent(name, config);
71924
72191
  const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
71925
72192
  const primaryAccount = resolved.auth?.override ?? config.auth?.active;
72193
+ const active = status?.active === "active" ? "active" : agentBridgeAlive(agentsDir, name) ? "active" : status?.active ?? "unknown";
71926
72194
  agents.push({
71927
72195
  name,
71928
- active: status?.active ?? "unknown",
72196
+ active,
71929
72197
  uptime: status?.uptime ?? null,
71930
72198
  memory: status?.memory ?? null,
71931
72199
  extends: agentConfig.extends ?? "default",
@@ -72183,7 +72451,7 @@ function inspectEnv(container, keys) {
72183
72451
  } catch {}
72184
72452
  return out;
72185
72453
  }
72186
- async function handleGetSystemHealth(home2) {
72454
+ async function handleGetSystemHealth(config, home2) {
72187
72455
  const broker = { reachable: false };
72188
72456
  try {
72189
72457
  await withAuthBrokerClient(async (client2) => {
@@ -72204,23 +72472,25 @@ async function handleGetSystemHealth(home2) {
72204
72472
  broker.error = err instanceof Error ? err.message : String(err);
72205
72473
  }
72206
72474
  }
72475
+ const memoryUrl = config?.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
72476
+ let running = false;
72477
+ try {
72478
+ running = (await probeHindsight(memoryUrl)).ok;
72479
+ } catch {
72480
+ running = false;
72481
+ }
72207
72482
  const containerStatus = getHindsightStatus();
72208
- const running = isHindsightRunning();
72209
- const env2 = running ? inspectEnv("switchroom-hindsight", [
72483
+ const env2 = inspectEnv("switchroom-hindsight", [
72210
72484
  "HINDSIGHT_API_LLM_MODEL",
72211
72485
  "HINDSIGHT_API_LLM_PROVIDER",
72212
72486
  "HINDSIGHT_API_MCP_STATELESS"
72213
- ]) : {
72214
- HINDSIGHT_API_LLM_MODEL: null,
72215
- HINDSIGHT_API_LLM_PROVIDER: null,
72216
- HINDSIGHT_API_MCP_STATELESS: null
72217
- };
72487
+ ]);
72218
72488
  const statelessRaw = env2.HINDSIGHT_API_MCP_STATELESS;
72219
72489
  const hindsight = {
72220
72490
  containerStatus,
72221
72491
  running,
72222
72492
  model: env2.HINDSIGHT_API_LLM_MODEL,
72223
- provider: env2.HINDSIGHT_API_LLM_PROVIDER,
72493
+ provider: env2.HINDSIGHT_API_LLM_PROVIDER ?? (config?.memory?.config?.provider ?? null),
72224
72494
  mcpStateless: statelessRaw == null ? null : statelessRaw.toLowerCase() === "true"
72225
72495
  };
72226
72496
  const hostd = {
@@ -73331,7 +73601,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
73331
73601
  return jsonResponse(result.subagents);
73332
73602
  }
73333
73603
  case "getSystemHealth":
73334
- return (async () => jsonResponse(await handleGetSystemHealth()))();
73604
+ return (async () => jsonResponse(await handleGetSystemHealth(config)))();
73335
73605
  case "getGoogleAccounts":
73336
73606
  return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
73337
73607
  case "getMicrosoftAccounts":
@@ -74467,7 +74737,7 @@ init_doctor();
74467
74737
  init_source();
74468
74738
  init_loader();
74469
74739
  init_lifecycle();
74470
- import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync31, readFileSync as readFileSync50, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
74740
+ import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync31, readFileSync as readFileSync50, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync25 } from "node:fs";
74471
74741
  import { spawnSync as spawnSync9 } from "node:child_process";
74472
74742
  import { join as join57, dirname as dirname13, resolve as resolve33 } from "node:path";
74473
74743
  import { homedir as homedir34 } from "node:os";
@@ -74738,7 +75008,7 @@ function defaultStatusProbe(composePath) {
74738
75008
  } catch {}
74739
75009
  if (scriptPath) {
74740
75010
  try {
74741
- cliBuiltAt = new Date(statSync24(scriptPath).mtimeMs).toISOString();
75011
+ cliBuiltAt = new Date(statSync25(scriptPath).mtimeMs).toISOString();
74742
75012
  } catch {}
74743
75013
  let dir = dirname13(scriptPath);
74744
75014
  for (let i = 0;i < 8; i++) {
@@ -75248,7 +75518,7 @@ import {
75248
75518
  readdirSync as readdirSync21,
75249
75519
  readFileSync as readFileSync52,
75250
75520
  renameSync as renameSync12,
75251
- statSync as statSync25,
75521
+ statSync as statSync26,
75252
75522
  unlinkSync as unlinkSync11,
75253
75523
  writeFileSync as writeFileSync27,
75254
75524
  writeSync as writeSync7
@@ -75822,7 +76092,7 @@ function sweepOrphanTmpFiles(stateDir) {
75822
76092
  continue;
75823
76093
  const tmpPath = join59(stateDir, entry);
75824
76094
  try {
75825
- const stat = statSync25(tmpPath);
76095
+ const stat = statSync26(tmpPath);
75826
76096
  if (stat.mtimeMs < cutoff) {
75827
76097
  unlinkSync11(tmpPath);
75828
76098
  }
@@ -77387,7 +77657,7 @@ function registerSoulCommand(program3) {
77387
77657
  // src/cli/debug.ts
77388
77658
  init_helpers();
77389
77659
  init_loader();
77390
- import { existsSync as existsSync64, readFileSync as readFileSync56, readdirSync as readdirSync22, statSync as statSync26 } from "node:fs";
77660
+ import { existsSync as existsSync64, readFileSync as readFileSync56, readdirSync as readdirSync22, statSync as statSync27 } from "node:fs";
77391
77661
  import { resolve as resolve40, join as join64 } from "node:path";
77392
77662
  import { createHash as createHash13 } from "node:crypto";
77393
77663
  init_merge();
@@ -77426,7 +77696,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
77426
77696
  const transcriptPath = join64(projectPath, "transcript.jsonl");
77427
77697
  if (!existsSync64(transcriptPath))
77428
77698
  continue;
77429
- const stat3 = statSync26(transcriptPath);
77699
+ const stat3 = statSync27(transcriptPath);
77430
77700
  if (!latest || stat3.mtimeMs > latest.mtime) {
77431
77701
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
77432
77702
  }
@@ -78764,6 +79034,211 @@ function registerNotionMcpLauncherCommand(program3) {
78764
79034
  });
78765
79035
  }
78766
79036
 
79037
+ // src/cli/deliver-file.ts
79038
+ init_client2();
79039
+ import { readFileSync as readFileSync58, statSync as statSync28 } from "node:fs";
79040
+ import { basename as basename6 } from "node:path";
79041
+
79042
+ // src/delivery/onedrive.ts
79043
+ import { basename as basename5 } from "node:path";
79044
+ var GRAPH = "https://graph.microsoft.com/v1.0";
79045
+ var ONEDRIVE_INLINE_MAX_BYTES = 4 * 1024 * 1024;
79046
+ function authHeaders(token) {
79047
+ return { Authorization: `Bearer ${token}`, Accept: "application/json" };
79048
+ }
79049
+ async function readBody(resp) {
79050
+ try {
79051
+ const t = await resp.text();
79052
+ return t.length > 300 ? `${t.slice(0, 300)}\u2026` : t;
79053
+ } catch {
79054
+ return "";
79055
+ }
79056
+ }
79057
+ async function ensureFolder(deps, parentPath, name) {
79058
+ const f = deps.fetchImpl ?? fetch;
79059
+ const childPath = `${parentPath}/${name}`;
79060
+ const getUrl = `${GRAPH}/me/drive/root:${encodeURI(childPath)}`;
79061
+ const got = await f(getUrl, { headers: authHeaders(deps.accessToken) });
79062
+ if (got.ok) {
79063
+ return await got.json();
79064
+ }
79065
+ if (got.status !== 404) {
79066
+ throw new Error(`OneDrive folder lookup failed: HTTP ${got.status} \u2014 ${await readBody(got)}`);
79067
+ }
79068
+ const createUrl = parentPath === "" ? `${GRAPH}/me/drive/root/children` : `${GRAPH}/me/drive/root:${encodeURI(parentPath)}:/children`;
79069
+ const created = await f(createUrl, {
79070
+ method: "POST",
79071
+ headers: { ...authHeaders(deps.accessToken), "Content-Type": "application/json" },
79072
+ body: JSON.stringify({
79073
+ name,
79074
+ folder: {},
79075
+ "@microsoft.graph.conflictBehavior": "fail"
79076
+ })
79077
+ });
79078
+ if (created.ok) {
79079
+ return await created.json();
79080
+ }
79081
+ if (created.status === 409) {
79082
+ const reget = await f(getUrl, { headers: authHeaders(deps.accessToken) });
79083
+ if (reget.ok)
79084
+ return await reget.json();
79085
+ }
79086
+ throw new Error(`OneDrive folder create failed: HTTP ${created.status} \u2014 ${await readBody(created)}`);
79087
+ }
79088
+ async function ensureSwitchroomFolder(deps, agentName) {
79089
+ await ensureFolder(deps, "", "Switchroom");
79090
+ return ensureFolder(deps, "/Switchroom", agentName);
79091
+ }
79092
+ async function uploadFile(deps, folderId, filename, bytes) {
79093
+ const f = deps.fetchImpl ?? fetch;
79094
+ if (bytes.byteLength <= ONEDRIVE_INLINE_MAX_BYTES) {
79095
+ const url = `${GRAPH}/me/drive/items/${folderId}:/${encodeURIComponent(filename)}:/content`;
79096
+ const resp = await f(url, {
79097
+ method: "PUT",
79098
+ headers: { ...authHeaders(deps.accessToken), "Content-Type": "application/octet-stream" },
79099
+ body: bytes
79100
+ });
79101
+ if (!resp.ok) {
79102
+ throw new Error(`OneDrive upload failed: HTTP ${resp.status} \u2014 ${await readBody(resp)}`);
79103
+ }
79104
+ return await resp.json();
79105
+ }
79106
+ return uploadLargeFile(deps, folderId, filename, bytes);
79107
+ }
79108
+ async function uploadLargeFile(deps, folderId, filename, bytes) {
79109
+ const f = deps.fetchImpl ?? fetch;
79110
+ const sessUrl = `${GRAPH}/me/drive/items/${folderId}:/${encodeURIComponent(filename)}:/createUploadSession`;
79111
+ const sess = await f(sessUrl, {
79112
+ method: "POST",
79113
+ headers: { ...authHeaders(deps.accessToken), "Content-Type": "application/json" },
79114
+ body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
79115
+ });
79116
+ if (!sess.ok) {
79117
+ throw new Error(`OneDrive upload-session failed: HTTP ${sess.status} \u2014 ${await readBody(sess)}`);
79118
+ }
79119
+ const { uploadUrl } = await sess.json();
79120
+ const CHUNK = 5 * 1024 * 1024;
79121
+ const total = bytes.byteLength;
79122
+ let lastItem = null;
79123
+ for (let start = 0;start < total; start += CHUNK) {
79124
+ const end = Math.min(start + CHUNK, total);
79125
+ const chunk2 = bytes.subarray(start, end);
79126
+ const put = await f(uploadUrl, {
79127
+ method: "PUT",
79128
+ headers: {
79129
+ "Content-Length": String(chunk2.byteLength),
79130
+ "Content-Range": `bytes ${start}-${end - 1}/${total}`
79131
+ },
79132
+ body: chunk2
79133
+ });
79134
+ if (put.status === 200 || put.status === 201) {
79135
+ lastItem = await put.json();
79136
+ } else if (put.status !== 202) {
79137
+ throw new Error(`OneDrive chunk upload failed: HTTP ${put.status} \u2014 ${await readBody(put)}`);
79138
+ }
79139
+ }
79140
+ if (!lastItem)
79141
+ throw new Error("OneDrive upload session completed without a final item");
79142
+ return lastItem;
79143
+ }
79144
+ async function createShareLink(deps, item, scopes = ["anonymous", "organization"]) {
79145
+ const f = deps.fetchImpl ?? fetch;
79146
+ const url = `${GRAPH}/me/drive/items/${item.id}/createLink`;
79147
+ for (const scope of scopes) {
79148
+ const resp = await f(url, {
79149
+ method: "POST",
79150
+ headers: { ...authHeaders(deps.accessToken), "Content-Type": "application/json" },
79151
+ body: JSON.stringify({ type: "view", scope })
79152
+ });
79153
+ if (resp.ok) {
79154
+ const body = await resp.json();
79155
+ if (body.link?.webUrl)
79156
+ return body.link.webUrl;
79157
+ }
79158
+ }
79159
+ if (item.webUrl)
79160
+ return item.webUrl;
79161
+ throw new Error("OneDrive: could not create a share link and the item has no webUrl");
79162
+ }
79163
+ async function deliverToOneDrive(args) {
79164
+ const deps = { accessToken: args.accessToken, fetchImpl: args.fetchImpl };
79165
+ const folder = await ensureSwitchroomFolder(deps, args.agentName);
79166
+ const filename = basename5(args.localPath);
79167
+ const item = await uploadFile(deps, folder.id, filename, args.bytes);
79168
+ const link = await createShareLink(deps, item, args.linkScopes);
79169
+ return { itemId: item.id, link, folderPath: `Switchroom/${args.agentName}` };
79170
+ }
79171
+
79172
+ // src/cli/deliver-file.ts
79173
+ function resolveLinkScopes(env2 = process.env) {
79174
+ const raw = (env2.SWITCHROOM_DELIVER_LINK_SCOPE ?? "").trim().toLowerCase();
79175
+ if (raw === "organization")
79176
+ return ["organization"];
79177
+ if (raw === "anonymous")
79178
+ return ["anonymous"];
79179
+ return ["anonymous", "organization"];
79180
+ }
79181
+ function safeAgentName(name) {
79182
+ const n = (name ?? "").trim();
79183
+ return /^[a-z0-9][a-z0-9_-]{0,50}$/.test(n) ? n : "agent";
79184
+ }
79185
+ async function brokerAccessToken(provider) {
79186
+ const client2 = new AuthBrokerClient;
79187
+ const data = await client2.getCredentials(provider);
79188
+ const creds = data.credentials;
79189
+ const token = creds?.microsoftOauth?.accessToken;
79190
+ if (!token) {
79191
+ throw new Error("broker returned no Microsoft access token (is an account connected + enabled for this agent?)");
79192
+ }
79193
+ return token;
79194
+ }
79195
+ async function runDeliverFile(localPath, deps = {}) {
79196
+ const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
79197
+ const sizeOf = deps.fileSize ?? ((p) => statSync28(p).size);
79198
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync58(p)));
79199
+ const getToken = deps.getAccessToken ?? brokerAccessToken;
79200
+ const deliver = deps.deliver ?? ((a) => deliverToOneDrive({ ...a, linkScopes: resolveLinkScopes() }));
79201
+ let size;
79202
+ try {
79203
+ size = sizeOf(localPath);
79204
+ } catch {
79205
+ return { ok: false, error: `file not found: ${localPath}` };
79206
+ }
79207
+ if (size === 0) {
79208
+ return { ok: false, error: `file is empty: ${localPath}` };
79209
+ }
79210
+ let accessToken;
79211
+ try {
79212
+ accessToken = await getToken("microsoft");
79213
+ } catch (err) {
79214
+ return {
79215
+ ok: false,
79216
+ error: `no connected drive for delivery \u2014 ${err.message}. ` + `Connect a Microsoft account from the dashboard, or send the file ` + `directly with the reply tool (files: ["${localPath}"]) for files under 50MB.`
79217
+ };
79218
+ }
79219
+ try {
79220
+ const bytes = read(localPath);
79221
+ const out = await deliver({ accessToken, agentName, localPath, bytes });
79222
+ return { ok: true, link: out.link, folderPath: out.folderPath, filename: basename6(localPath) };
79223
+ } catch (err) {
79224
+ return { ok: false, error: `upload failed: ${err.message}` };
79225
+ }
79226
+ }
79227
+ function registerDeliverFileCommand(program3) {
79228
+ program3.command("deliver-file").description("Deliver a file you produced to the user: upload it to their Switchroom/<agent>/ folder on the connected drive and print a shareable link. Reply with that link \u2014 never a local container path.").argument("<path>", "absolute local path of the file to deliver").action(async (path7) => {
79229
+ const res = await runDeliverFile(path7);
79230
+ if (res.ok) {
79231
+ process.stdout.write(`Delivered ${res.filename} to the user's drive \u2192 ${res.folderPath}/
79232
+ ` + `Share link (reply with this): ${res.link}
79233
+ `);
79234
+ return;
79235
+ }
79236
+ process.stderr.write(`deliver-file: ${res.error}
79237
+ `);
79238
+ process.exitCode = 1;
79239
+ });
79240
+ }
79241
+
78767
79242
  // src/cli/notion.ts
78768
79243
  init_loader();
78769
79244
 
@@ -79013,7 +79488,7 @@ async function fetchToken(vaultKey) {
79013
79488
 
79014
79489
  // src/cli/apply.ts
79015
79490
  init_source();
79016
- import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync73, mkdirSync as mkdirSync41, readFileSync as readFileSync59, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync36 } from "node:fs";
79491
+ import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync73, mkdirSync as mkdirSync41, readFileSync as readFileSync60, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync36 } from "node:fs";
79017
79492
  import { mkdir, writeFile } from "node:fs/promises";
79018
79493
  import { spawnSync as childSpawnSync } from "node:child_process";
79019
79494
  import readline from "node:readline";
@@ -79410,7 +79885,7 @@ init_loader();
79410
79885
  init_loader();
79411
79886
 
79412
79887
  // src/cli/update-prompt-hook.ts
79413
- import { existsSync as existsSync70, readFileSync as readFileSync58, writeFileSync as writeFileSync35, chmodSync as chmodSync10, mkdirSync as mkdirSync40 } from "node:fs";
79888
+ import { existsSync as existsSync70, readFileSync as readFileSync59, writeFileSync as writeFileSync35, chmodSync as chmodSync10, mkdirSync as mkdirSync40 } from "node:fs";
79414
79889
  import { join as join69 } from "node:path";
79415
79890
  var HOOK_FILENAME = "update-card-on-prompt.sh";
79416
79891
  function updatePromptHookScript() {
@@ -79482,7 +79957,7 @@ function installUpdatePromptHook(agentDir) {
79482
79957
  const scriptPath = join69(hooksDir, HOOK_FILENAME);
79483
79958
  const desired = updatePromptHookScript();
79484
79959
  let installed = false;
79485
- const existing = existsSync70(scriptPath) ? readFileSync58(scriptPath, "utf-8") : "";
79960
+ const existing = existsSync70(scriptPath) ? readFileSync59(scriptPath, "utf-8") : "";
79486
79961
  if (existing !== desired) {
79487
79962
  writeFileSync35(scriptPath, desired, { mode: 493 });
79488
79963
  chmodSync10(scriptPath, 493);
@@ -79496,7 +79971,7 @@ function installUpdatePromptHook(agentDir) {
79496
79971
  if (!existsSync70(settingsPath)) {
79497
79972
  return { scriptPath, settingsPath, installed };
79498
79973
  }
79499
- const raw = readFileSync58(settingsPath, "utf-8");
79974
+ const raw = readFileSync59(settingsPath, "utf-8");
79500
79975
  let parsed;
79501
79976
  try {
79502
79977
  parsed = JSON.parse(raw);
@@ -79616,7 +80091,7 @@ import {
79616
80091
  lstatSync as lstatSync7,
79617
80092
  readdirSync as readdirSync25,
79618
80093
  realpathSync as realpathSync6,
79619
- statSync as statSync27
80094
+ statSync as statSync29
79620
80095
  } from "node:fs";
79621
80096
  import { join as join71 } from "node:path";
79622
80097
  function resolveOperatorUid() {
@@ -79656,7 +80131,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
79656
80131
  });
79657
80132
  const isDir = deps.isDir ?? ((p) => {
79658
80133
  try {
79659
- return statSync27(p).isDirectory();
80134
+ return statSync29(p).isDirectory();
79660
80135
  } catch {
79661
80136
  return false;
79662
80137
  }
@@ -79790,7 +80265,7 @@ async function ensureHostMountSources(config) {
79790
80265
  await mkdir(fleetDir, { recursive: true });
79791
80266
  const invariantsPath = join72(fleetDir, "switchroom-invariants.md");
79792
80267
  const invariantsCanonical = renderFleetInvariants();
79793
- const invariantsCurrent = existsSync73(invariantsPath) ? readFileSync59(invariantsPath, "utf-8") : null;
80268
+ const invariantsCurrent = existsSync73(invariantsPath) ? readFileSync60(invariantsPath, "utf-8") : null;
79794
80269
  if (invariantsCurrent !== invariantsCanonical) {
79795
80270
  writeFileSync36(invariantsPath, invariantsCanonical, { mode: 420 });
79796
80271
  }
@@ -80327,7 +80802,7 @@ function runRedactStdin() {
80327
80802
  }
80328
80803
 
80329
80804
  // src/cli/status-ask.ts
80330
- import { readFileSync as readFileSync60, existsSync as existsSync74, readdirSync as readdirSync27 } from "node:fs";
80805
+ import { readFileSync as readFileSync61, existsSync as existsSync74, readdirSync as readdirSync27 } from "node:fs";
80331
80806
  import { join as join73 } from "node:path";
80332
80807
  import { homedir as homedir42 } from "node:os";
80333
80808
 
@@ -80603,7 +81078,7 @@ function runReport(opts) {
80603
81078
  for (const src of sources) {
80604
81079
  let content;
80605
81080
  try {
80606
- content = readFileSync60(src.path, "utf-8");
81081
+ content = readFileSync61(src.path, "utf-8");
80607
81082
  } catch (err) {
80608
81083
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
80609
81084
  `);
@@ -80704,7 +81179,7 @@ import {
80704
81179
  existsSync as existsSync75,
80705
81180
  mkdirSync as mkdirSync42,
80706
81181
  appendFileSync as appendFileSync4,
80707
- readFileSync as readFileSync61
81182
+ readFileSync as readFileSync62
80708
81183
  } from "node:fs";
80709
81184
  var AUDIT_ROOT = join74(homedir43(), ".switchroom", "audit");
80710
81185
  function auditPathFor(agent) {
@@ -80799,7 +81274,7 @@ function readAuditTail(agent, limit, opts = {}) {
80799
81274
  return [];
80800
81275
  let raw;
80801
81276
  try {
80802
- raw = readFileSync61(path8, "utf-8");
81277
+ raw = readFileSync62(path8, "utf-8");
80803
81278
  } catch {
80804
81279
  return [];
80805
81280
  }
@@ -80960,9 +81435,9 @@ import {
80960
81435
  mkdirSync as mkdirSync43,
80961
81436
  openSync as openSync13,
80962
81437
  readdirSync as readdirSync28,
80963
- readFileSync as readFileSync62,
81438
+ readFileSync as readFileSync63,
80964
81439
  renameSync as renameSync15,
80965
- statSync as statSync28,
81440
+ statSync as statSync30,
80966
81441
  unlinkSync as unlinkSync14,
80967
81442
  writeSync as writeSync8
80968
81443
  } from "node:fs";
@@ -81006,7 +81481,7 @@ function withAgentLock(paths, fn) {
81006
81481
  if (e.code !== "EEXIST")
81007
81482
  throw err;
81008
81483
  try {
81009
- const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
81484
+ const age = Date.now() - statSync30(paths.lockPath).mtimeMs;
81010
81485
  if (age > 30000) {
81011
81486
  unlinkSync14(paths.lockPath);
81012
81487
  continue;
@@ -81084,7 +81559,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
81084
81559
  continue;
81085
81560
  const full = join75(paths.skillsDir, name);
81086
81561
  try {
81087
- const raw = readFileSync62(full, "utf-8");
81562
+ const raw = readFileSync63(full, "utf-8");
81088
81563
  const slug = name.replace(/\.ya?ml$/i, "");
81089
81564
  out.push({ slug, path: full, raw });
81090
81565
  } catch {}
@@ -81111,7 +81586,7 @@ function listOverlayEntries(agent, opts = {}) {
81111
81586
  continue;
81112
81587
  const full = join75(paths.scheduleDir, name);
81113
81588
  try {
81114
- const raw = readFileSync62(full, "utf-8");
81589
+ const raw = readFileSync63(full, "utf-8");
81115
81590
  const slug = name.replace(/\.ya?ml$/i, "");
81116
81591
  out.push({ slug, path: full, raw });
81117
81592
  } catch {}
@@ -81259,7 +81734,7 @@ import {
81259
81734
  mkdirSync as mkdirSync44,
81260
81735
  openSync as openSync14,
81261
81736
  readdirSync as readdirSync29,
81262
- readFileSync as readFileSync63,
81737
+ readFileSync as readFileSync64,
81263
81738
  renameSync as renameSync16,
81264
81739
  unlinkSync as unlinkSync15,
81265
81740
  writeFileSync as writeFileSync37,
@@ -81323,7 +81798,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
81323
81798
  if (!existsSync77(yamlPath))
81324
81799
  continue;
81325
81800
  try {
81326
- const meta = JSON.parse(readFileSync63(metaPath, "utf-8"));
81801
+ const meta = JSON.parse(readFileSync64(metaPath, "utf-8"));
81327
81802
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
81328
81803
  continue;
81329
81804
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -81362,7 +81837,7 @@ function denyPendingScheduleEntry(opts) {
81362
81837
 
81363
81838
  // src/cli/agent-config-write.ts
81364
81839
  init_protocol3();
81365
- import { existsSync as existsSync78, readFileSync as readFileSync64 } from "node:fs";
81840
+ import { existsSync as existsSync78, readFileSync as readFileSync65 } from "node:fs";
81366
81841
  var MAX_ENTRIES_PER_AGENT = 20;
81367
81842
  var MIN_CRON_INTERVAL_MIN = 5;
81368
81843
  function extractCronSmallestGapMin(expr) {
@@ -81649,7 +82124,7 @@ function scheduleRemove(opts) {
81649
82124
  let priorContent = null;
81650
82125
  try {
81651
82126
  if (existsSync78(match.path))
81652
- priorContent = readFileSync64(match.path, "utf-8");
82127
+ priorContent = readFileSync65(match.path, "utf-8");
81653
82128
  } catch {}
81654
82129
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
81655
82130
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -82089,12 +82564,12 @@ import {
82089
82564
  mkdirSync as mkdirSync45,
82090
82565
  mkdtempSync as mkdtempSync5,
82091
82566
  openSync as openSync15,
82092
- readFileSync as readFileSync65,
82567
+ readFileSync as readFileSync66,
82093
82568
  readdirSync as readdirSync30,
82094
82569
  realpathSync as realpathSync7,
82095
82570
  renameSync as renameSync17,
82096
82571
  rmSync as rmSync16,
82097
- statSync as statSync29,
82572
+ statSync as statSync31,
82098
82573
  writeFileSync as writeFileSync38
82099
82574
  } from "node:fs";
82100
82575
  import { tmpdir as tmpdir5, homedir as homedir44 } from "node:os";
@@ -82324,7 +82799,7 @@ function isTarballPath(p) {
82324
82799
  }
82325
82800
  function loadFromDir(dir) {
82326
82801
  const abs = realpathSync7(dir);
82327
- if (!statSync29(abs).isDirectory()) {
82802
+ if (!statSync31(abs).isDirectory()) {
82328
82803
  fail2(`--from path is not a directory: ${dir}`);
82329
82804
  }
82330
82805
  const files = {};
@@ -82341,7 +82816,7 @@ function loadFromDir(dir) {
82341
82816
  continue;
82342
82817
  }
82343
82818
  if (ent.isFile()) {
82344
- const buf = readFileSync65(full);
82819
+ const buf = readFileSync66(full);
82345
82820
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
82346
82821
  }
82347
82822
  }
@@ -82390,7 +82865,7 @@ function loadFromTarball(tarPath) {
82390
82865
  }
82391
82866
  }
82392
82867
  function loadSingleFile(filePath) {
82393
- const content = readFileSync65(filePath, "utf-8");
82868
+ const content = readFileSync66(filePath, "utf-8");
82394
82869
  return { "SKILL.md": content };
82395
82870
  }
82396
82871
  function loadFromStdin() {
@@ -82481,7 +82956,7 @@ function diffSummary(currentDir, files) {
82481
82956
  if (ent.isDirectory()) {
82482
82957
  walk2(full);
82483
82958
  } else if (ent.isFile()) {
82484
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync65(full, "utf-8");
82959
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync66(full, "utf-8");
82485
82960
  }
82486
82961
  }
82487
82962
  };
@@ -82584,7 +83059,7 @@ function registerSkillCommand(program3) {
82584
83059
  if (!existsSync80(fromPath)) {
82585
83060
  fail2(`--from path does not exist: ${opts.from}`);
82586
83061
  }
82587
- const st = statSync29(fromPath);
83062
+ const st = statSync31(fromPath);
82588
83063
  if (st.isDirectory()) {
82589
83064
  files = loadFromDir(fromPath);
82590
83065
  } else if (isTarballPath(fromPath)) {
@@ -82641,11 +83116,11 @@ import {
82641
83116
  mkdirSync as mkdirSync46,
82642
83117
  mkdtempSync as mkdtempSync6,
82643
83118
  openSync as openSync16,
82644
- readFileSync as readFileSync66,
83119
+ readFileSync as readFileSync67,
82645
83120
  readdirSync as readdirSync31,
82646
83121
  renameSync as renameSync18,
82647
83122
  rmSync as rmSync17,
82648
- statSync as statSync30,
83123
+ statSync as statSync32,
82649
83124
  utimesSync,
82650
83125
  writeFileSync as writeFileSync39
82651
83126
  } from "node:fs";
@@ -82723,7 +83198,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
82723
83198
  if (ent.isDirectory())
82724
83199
  walk2(s, d);
82725
83200
  else if (ent.isFile()) {
82726
- writeFileSync39(d, readFileSync66(s));
83201
+ writeFileSync39(d, readFileSync67(s));
82727
83202
  }
82728
83203
  }
82729
83204
  };
@@ -82786,7 +83261,7 @@ function readStdinSync2() {
82786
83261
  }
82787
83262
  function loadFromDir2(dir) {
82788
83263
  const abs = resolve47(dir);
82789
- if (!statSync30(abs).isDirectory()) {
83264
+ if (!statSync32(abs).isDirectory()) {
82790
83265
  fail3(`--from path is not a directory: ${dir}`);
82791
83266
  }
82792
83267
  const files = {};
@@ -82802,7 +83277,7 @@ function loadFromDir2(dir) {
82802
83277
  }
82803
83278
  if (ent.isFile()) {
82804
83279
  const rel = relative3(abs, full).replace(/\\/g, "/");
82805
- files[rel] = readFileSync66(full, "utf-8");
83280
+ files[rel] = readFileSync67(full, "utf-8");
82806
83281
  }
82807
83282
  }
82808
83283
  };
@@ -82872,7 +83347,7 @@ function sweepTrash(agentsRoot, agent) {
82872
83347
  continue;
82873
83348
  const entPath = join79(trash, ent.name);
82874
83349
  try {
82875
- const st = statSync30(entPath);
83350
+ const st = statSync32(entPath);
82876
83351
  if (now - st.mtimeMs > TRASH_TTL_MS) {
82877
83352
  rmSync17(entPath, { recursive: true, force: true });
82878
83353
  }
@@ -82983,12 +83458,12 @@ function loadFiles(opts) {
82983
83458
  if (!existsSync81(p)) {
82984
83459
  fail3(`--from path does not exist: ${opts.from}`);
82985
83460
  }
82986
- const st = statSync30(p);
83461
+ const st = statSync32(p);
82987
83462
  if (st.isDirectory()) {
82988
83463
  return loadFromDir2(p);
82989
83464
  }
82990
83465
  if (p.endsWith(".md")) {
82991
- return { "SKILL.md": readFileSync66(p, "utf-8") };
83466
+ return { "SKILL.md": readFileSync67(p, "utf-8") };
82992
83467
  }
82993
83468
  fail3(`--from must be a directory or a .md file. Got: ${opts.from}`);
82994
83469
  }
@@ -83077,7 +83552,7 @@ function readSourceFiles(dir) {
83077
83552
  fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
83078
83553
  }
83079
83554
  } catch {}
83080
- files[rel] = readFileSync66(full, "utf-8");
83555
+ files[rel] = readFileSync67(full, "utf-8");
83081
83556
  }
83082
83557
  }
83083
83558
  };
@@ -83204,7 +83679,7 @@ function listPersonalAction(opts) {
83204
83679
  if (e.isFile()) {
83205
83680
  fileCount += 1;
83206
83681
  try {
83207
- totalBytes += statSync30(join79(sub, e.name)).size;
83682
+ totalBytes += statSync32(join79(sub, e.name)).size;
83208
83683
  } catch {}
83209
83684
  } else if (e.isDirectory()) {
83210
83685
  walk2(join79(sub, e.name));
@@ -83246,7 +83721,7 @@ function registerSkillPersonalCommands(program3) {
83246
83721
  // src/cli/skill-search.ts
83247
83722
  init_helpers();
83248
83723
  var import_yaml23 = __toESM(require_dist(), 1);
83249
- import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync67, statSync as statSync31 } from "node:fs";
83724
+ import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync68, statSync as statSync33 } from "node:fs";
83250
83725
  import { homedir as homedir46 } from "node:os";
83251
83726
  import { join as join80, resolve as resolve48 } from "node:path";
83252
83727
  var PERSONAL_PREFIX2 = "personal-";
@@ -83267,7 +83742,7 @@ function readSkillFrontmatter(skillDir) {
83267
83742
  return null;
83268
83743
  let content;
83269
83744
  try {
83270
- content = readFileSync67(mdPath, "utf-8");
83745
+ content = readFileSync68(mdPath, "utf-8");
83271
83746
  } catch {
83272
83747
  return null;
83273
83748
  }
@@ -83297,7 +83772,7 @@ function readSkillFrontmatter(skillDir) {
83297
83772
  function statSkillMd(skillDir) {
83298
83773
  const mdPath = join80(skillDir, "SKILL.md");
83299
83774
  try {
83300
- const st = statSync31(mdPath);
83775
+ const st = statSync33(mdPath);
83301
83776
  return { size: st.size, mtime: st.mtime.toISOString() };
83302
83777
  } catch {
83303
83778
  return null;
@@ -83321,7 +83796,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
83321
83796
  continue;
83322
83797
  const dirPath = join80(skillsDir, ent);
83323
83798
  try {
83324
- if (!statSync31(dirPath).isDirectory())
83799
+ if (!statSync33(dirPath).isDirectory())
83325
83800
  continue;
83326
83801
  } catch {
83327
83802
  continue;
@@ -83361,7 +83836,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
83361
83836
  continue;
83362
83837
  const dirPath = join80(sharedRoot, ent);
83363
83838
  try {
83364
- if (!statSync31(dirPath).isDirectory())
83839
+ if (!statSync33(dirPath).isDirectory())
83365
83840
  continue;
83366
83841
  } catch {
83367
83842
  continue;
@@ -83397,7 +83872,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
83397
83872
  continue;
83398
83873
  const dirPath = join80(bundledRoot, ent);
83399
83874
  try {
83400
- if (!statSync31(dirPath).isDirectory())
83875
+ if (!statSync33(dirPath).isDirectory())
83401
83876
  continue;
83402
83877
  } catch {
83403
83878
  continue;
@@ -83539,7 +84014,7 @@ function registerHostdMcpCommand(program3) {
83539
84014
  // src/cli/hostd.ts
83540
84015
  init_source();
83541
84016
  init_helpers();
83542
- import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync69, writeFileSync as writeFileSync40, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
84017
+ import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync70, writeFileSync as writeFileSync40, statSync as statSync34, copyFileSync as copyFileSync12 } from "node:fs";
83543
84018
  import { homedir as homedir47 } from "node:os";
83544
84019
  import { join as join81 } from "node:path";
83545
84020
  import { spawnSync as spawnSync14 } from "node:child_process";
@@ -83747,7 +84222,7 @@ function doStatus() {
83747
84222
  continue;
83748
84223
  const sockPath = join81(dir, name, "sock");
83749
84224
  if (existsSync84(sockPath)) {
83750
- const st = statSync32(sockPath);
84225
+ const st = statSync34(sockPath);
83751
84226
  if ((st.mode & 61440) === 49152) {
83752
84227
  entries.push(`${name} \u2192 ${sockPath}`);
83753
84228
  }
@@ -83794,7 +84269,7 @@ function registerHostdCommand(program3) {
83794
84269
  The log is created when hostd handles its first privileged-verb request.`));
83795
84270
  return;
83796
84271
  }
83797
- const raw = readFileSync69(logPath, "utf-8");
84272
+ const raw = readFileSync70(logPath, "utf-8");
83798
84273
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
83799
84274
  const filters = {
83800
84275
  agent: opts.agent,
@@ -84084,6 +84559,7 @@ registerDriveCommand(program3);
84084
84559
  registerDriveMcpLauncherCommand(program3);
84085
84560
  registerM365McpLauncherCommand(program3);
84086
84561
  registerNotionMcpLauncherCommand(program3);
84562
+ registerDeliverFileCommand(program3);
84087
84563
  registerNotionCommand(program3);
84088
84564
  registerApplyCommand(program3);
84089
84565
  registerSecretDetectCommand(program3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.86",
3
+ "version": "0.14.87",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52889,10 +52889,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
52889
52889
  }
52890
52890
 
52891
52891
  // ../src/build-info.ts
52892
- var VERSION = "0.14.86";
52893
- var COMMIT_SHA = "8c518120";
52894
- var COMMIT_DATE = "2026-06-07T05:43:21Z";
52895
- var LATEST_PR = 2223;
52892
+ var VERSION = "0.14.87";
52893
+ var COMMIT_SHA = "5ad3e254";
52894
+ var COMMIT_DATE = "2026-06-07T08:49:13Z";
52895
+ var LATEST_PR = 2227;
52896
52896
  var COMMITS_AHEAD_OF_TAG = 0;
52897
52897
 
52898
52898
  // gateway/boot-version.ts