switchroom 0.14.86 → 0.14.88

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.88";
49819
+ var COMMIT_SHA = "b3c61552";
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,340 @@ 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 basename7 } 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/delivery/gdrive.ts
79173
+ import { basename as basename6 } from "node:path";
79174
+ var DRIVE = "https://www.googleapis.com/drive/v3";
79175
+ var UPLOAD = "https://www.googleapis.com/upload/drive/v3";
79176
+ var FOLDER_MIME = "application/vnd.google-apps.folder";
79177
+ function authHeaders2(token) {
79178
+ return { Authorization: `Bearer ${token}`, Accept: "application/json" };
79179
+ }
79180
+ async function readBody2(resp) {
79181
+ try {
79182
+ const t = await resp.text();
79183
+ return t.length > 300 ? `${t.slice(0, 300)}\u2026` : t;
79184
+ } catch {
79185
+ return "";
79186
+ }
79187
+ }
79188
+ async function ensureFolder2(deps, name, parentId) {
79189
+ const f = deps.fetchImpl ?? fetch;
79190
+ const safeName = name.replace(/'/g, "\\'");
79191
+ const q = `name='${safeName}' and mimeType='${FOLDER_MIME}' and '${parentId}' in parents and trashed=false`;
79192
+ const listUrl = `${DRIVE}/files?q=${encodeURIComponent(q)}&fields=files(id,name)&spaces=drive`;
79193
+ const list2 = await f(listUrl, { headers: authHeaders2(deps.accessToken) });
79194
+ if (!list2.ok) {
79195
+ throw new Error(`Drive folder lookup failed: HTTP ${list2.status} \u2014 ${await readBody2(list2)}`);
79196
+ }
79197
+ const found = await list2.json();
79198
+ if (found.files && found.files.length > 0)
79199
+ return found.files[0];
79200
+ const created = await f(`${DRIVE}/files?fields=id,name`, {
79201
+ method: "POST",
79202
+ headers: { ...authHeaders2(deps.accessToken), "Content-Type": "application/json" },
79203
+ body: JSON.stringify({ name, mimeType: FOLDER_MIME, parents: [parentId] })
79204
+ });
79205
+ if (!created.ok) {
79206
+ throw new Error(`Drive folder create failed: HTTP ${created.status} \u2014 ${await readBody2(created)}`);
79207
+ }
79208
+ return await created.json();
79209
+ }
79210
+ async function ensureSwitchroomFolder2(deps, agentName) {
79211
+ const top = await ensureFolder2(deps, "Switchroom", "root");
79212
+ return ensureFolder2(deps, agentName, top.id);
79213
+ }
79214
+ async function uploadFile2(deps, parentId, filename, bytes, mimeType = "application/octet-stream") {
79215
+ const f = deps.fetchImpl ?? fetch;
79216
+ const boundary = "switchroom-deliver-boundary";
79217
+ const metadata = JSON.stringify({ name: filename, parents: [parentId] });
79218
+ const enc = new TextEncoder;
79219
+ const head = enc.encode(`--${boundary}\r
79220
+ Content-Type: application/json; charset=UTF-8\r
79221
+ \r
79222
+ ${metadata}\r
79223
+ ` + `--${boundary}\r
79224
+ Content-Type: ${mimeType}\r
79225
+ \r
79226
+ `);
79227
+ const tail = enc.encode(`\r
79228
+ --${boundary}--`);
79229
+ const body = new Uint8Array(head.length + bytes.length + tail.length);
79230
+ body.set(head, 0);
79231
+ body.set(bytes, head.length);
79232
+ body.set(tail, head.length + bytes.length);
79233
+ const resp = await f(`${UPLOAD}/files?uploadType=multipart&fields=id,name,webViewLink`, {
79234
+ method: "POST",
79235
+ headers: {
79236
+ Authorization: `Bearer ${deps.accessToken}`,
79237
+ "Content-Type": `multipart/related; boundary=${boundary}`
79238
+ },
79239
+ body
79240
+ });
79241
+ if (!resp.ok) {
79242
+ throw new Error(`Drive upload failed: HTTP ${resp.status} \u2014 ${await readBody2(resp)}`);
79243
+ }
79244
+ return await resp.json();
79245
+ }
79246
+ async function createShareLink2(deps, file, scopes = ["anyone"]) {
79247
+ const f = deps.fetchImpl ?? fetch;
79248
+ for (const type of scopes) {
79249
+ const resp = await f(`${DRIVE}/files/${file.id}/permissions`, {
79250
+ method: "POST",
79251
+ headers: { ...authHeaders2(deps.accessToken), "Content-Type": "application/json" },
79252
+ body: JSON.stringify(type === "domain" ? { role: "reader", type: "domain" } : { role: "reader", type: "anyone" })
79253
+ });
79254
+ if (resp.ok)
79255
+ break;
79256
+ }
79257
+ if (file.webViewLink)
79258
+ return file.webViewLink;
79259
+ const meta = await f(`${DRIVE}/files/${file.id}?fields=webViewLink`, {
79260
+ headers: authHeaders2(deps.accessToken)
79261
+ });
79262
+ if (meta.ok) {
79263
+ const j = await meta.json();
79264
+ if (j.webViewLink)
79265
+ return j.webViewLink;
79266
+ }
79267
+ throw new Error("Drive: could not resolve a webViewLink for the uploaded file");
79268
+ }
79269
+ async function deliverToGoogleDrive(args) {
79270
+ const deps = { accessToken: args.accessToken, fetchImpl: args.fetchImpl };
79271
+ const folder = await ensureSwitchroomFolder2(deps, args.agentName);
79272
+ const filename = basename6(args.localPath);
79273
+ const file = await uploadFile2(deps, folder.id, filename, args.bytes);
79274
+ const link = await createShareLink2(deps, file, args.linkScopes);
79275
+ return { itemId: file.id, link, folderPath: `Switchroom/${args.agentName}` };
79276
+ }
79277
+
79278
+ // src/cli/deliver-file.ts
79279
+ function resolveGoogleLinkScopes(env2 = process.env) {
79280
+ const raw = (env2.SWITCHROOM_DELIVER_LINK_SCOPE ?? "").trim().toLowerCase();
79281
+ if (raw === "organization")
79282
+ return ["domain"];
79283
+ return ["anyone"];
79284
+ }
79285
+ function resolveLinkScopes(env2 = process.env) {
79286
+ const raw = (env2.SWITCHROOM_DELIVER_LINK_SCOPE ?? "").trim().toLowerCase();
79287
+ if (raw === "organization")
79288
+ return ["organization"];
79289
+ if (raw === "anonymous")
79290
+ return ["anonymous"];
79291
+ return ["anonymous", "organization"];
79292
+ }
79293
+ function safeAgentName(name) {
79294
+ const n = (name ?? "").trim();
79295
+ return /^[a-z0-9][a-z0-9_-]{0,50}$/.test(n) ? n : "agent";
79296
+ }
79297
+ async function brokerToken(provider) {
79298
+ const client2 = new AuthBrokerClient;
79299
+ try {
79300
+ const data = await client2.getCredentials(provider);
79301
+ const creds = data.credentials;
79302
+ const token = provider === "microsoft" ? creds?.microsoftOauth?.accessToken : creds?.googleOauth?.accessToken;
79303
+ return token ?? null;
79304
+ } catch {
79305
+ return null;
79306
+ } finally {
79307
+ await client2.close().catch(() => {});
79308
+ }
79309
+ }
79310
+ async function defaultResolveProvider() {
79311
+ const ms = await brokerToken("microsoft");
79312
+ if (ms) {
79313
+ return {
79314
+ name: "OneDrive",
79315
+ deliver: (a) => deliverToOneDrive({ ...a, accessToken: ms, linkScopes: resolveLinkScopes() })
79316
+ };
79317
+ }
79318
+ const g = await brokerToken("google");
79319
+ if (g) {
79320
+ return {
79321
+ name: "Google Drive",
79322
+ deliver: (a) => deliverToGoogleDrive({ ...a, accessToken: g, linkScopes: resolveGoogleLinkScopes() })
79323
+ };
79324
+ }
79325
+ return null;
79326
+ }
79327
+ async function runDeliverFile(localPath, deps = {}) {
79328
+ const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
79329
+ const sizeOf = deps.fileSize ?? ((p) => statSync28(p).size);
79330
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync58(p)));
79331
+ const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
79332
+ let size;
79333
+ try {
79334
+ size = sizeOf(localPath);
79335
+ } catch {
79336
+ return { ok: false, error: `file not found: ${localPath}` };
79337
+ }
79338
+ if (size === 0) {
79339
+ return { ok: false, error: `file is empty: ${localPath}` };
79340
+ }
79341
+ const provider = await resolveProvider();
79342
+ if (!provider) {
79343
+ return {
79344
+ ok: false,
79345
+ error: `no connected drive for delivery. Connect a Microsoft or Google ` + `account from the dashboard, or send the file directly with the ` + `reply tool (files: ["${localPath}"]) for files under 50MB.`
79346
+ };
79347
+ }
79348
+ try {
79349
+ const bytes = read(localPath);
79350
+ const out = await provider.deliver({ agentName, localPath, bytes });
79351
+ return { ok: true, provider: provider.name, link: out.link, folderPath: out.folderPath, filename: basename7(localPath) };
79352
+ } catch (err) {
79353
+ return { ok: false, provider: provider.name, error: `upload failed: ${err.message}` };
79354
+ }
79355
+ }
79356
+ function registerDeliverFileCommand(program3) {
79357
+ 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) => {
79358
+ const res = await runDeliverFile(path7);
79359
+ if (res.ok) {
79360
+ process.stdout.write(`Delivered ${res.filename} to the user's ${res.provider} \u2192 ${res.folderPath}/
79361
+ ` + `Share link (reply with this): ${res.link}
79362
+ `);
79363
+ return;
79364
+ }
79365
+ process.stderr.write(`deliver-file: ${res.error}
79366
+ `);
79367
+ process.exitCode = 1;
79368
+ });
79369
+ }
79370
+
78767
79371
  // src/cli/notion.ts
78768
79372
  init_loader();
78769
79373
 
@@ -79013,7 +79617,7 @@ async function fetchToken(vaultKey) {
79013
79617
 
79014
79618
  // src/cli/apply.ts
79015
79619
  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";
79620
+ 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
79621
  import { mkdir, writeFile } from "node:fs/promises";
79018
79622
  import { spawnSync as childSpawnSync } from "node:child_process";
79019
79623
  import readline from "node:readline";
@@ -79410,7 +80014,7 @@ init_loader();
79410
80014
  init_loader();
79411
80015
 
79412
80016
  // 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";
80017
+ import { existsSync as existsSync70, readFileSync as readFileSync59, writeFileSync as writeFileSync35, chmodSync as chmodSync10, mkdirSync as mkdirSync40 } from "node:fs";
79414
80018
  import { join as join69 } from "node:path";
79415
80019
  var HOOK_FILENAME = "update-card-on-prompt.sh";
79416
80020
  function updatePromptHookScript() {
@@ -79482,7 +80086,7 @@ function installUpdatePromptHook(agentDir) {
79482
80086
  const scriptPath = join69(hooksDir, HOOK_FILENAME);
79483
80087
  const desired = updatePromptHookScript();
79484
80088
  let installed = false;
79485
- const existing = existsSync70(scriptPath) ? readFileSync58(scriptPath, "utf-8") : "";
80089
+ const existing = existsSync70(scriptPath) ? readFileSync59(scriptPath, "utf-8") : "";
79486
80090
  if (existing !== desired) {
79487
80091
  writeFileSync35(scriptPath, desired, { mode: 493 });
79488
80092
  chmodSync10(scriptPath, 493);
@@ -79496,7 +80100,7 @@ function installUpdatePromptHook(agentDir) {
79496
80100
  if (!existsSync70(settingsPath)) {
79497
80101
  return { scriptPath, settingsPath, installed };
79498
80102
  }
79499
- const raw = readFileSync58(settingsPath, "utf-8");
80103
+ const raw = readFileSync59(settingsPath, "utf-8");
79500
80104
  let parsed;
79501
80105
  try {
79502
80106
  parsed = JSON.parse(raw);
@@ -79616,7 +80220,7 @@ import {
79616
80220
  lstatSync as lstatSync7,
79617
80221
  readdirSync as readdirSync25,
79618
80222
  realpathSync as realpathSync6,
79619
- statSync as statSync27
80223
+ statSync as statSync29
79620
80224
  } from "node:fs";
79621
80225
  import { join as join71 } from "node:path";
79622
80226
  function resolveOperatorUid() {
@@ -79656,7 +80260,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
79656
80260
  });
79657
80261
  const isDir = deps.isDir ?? ((p) => {
79658
80262
  try {
79659
- return statSync27(p).isDirectory();
80263
+ return statSync29(p).isDirectory();
79660
80264
  } catch {
79661
80265
  return false;
79662
80266
  }
@@ -79790,7 +80394,7 @@ async function ensureHostMountSources(config) {
79790
80394
  await mkdir(fleetDir, { recursive: true });
79791
80395
  const invariantsPath = join72(fleetDir, "switchroom-invariants.md");
79792
80396
  const invariantsCanonical = renderFleetInvariants();
79793
- const invariantsCurrent = existsSync73(invariantsPath) ? readFileSync59(invariantsPath, "utf-8") : null;
80397
+ const invariantsCurrent = existsSync73(invariantsPath) ? readFileSync60(invariantsPath, "utf-8") : null;
79794
80398
  if (invariantsCurrent !== invariantsCanonical) {
79795
80399
  writeFileSync36(invariantsPath, invariantsCanonical, { mode: 420 });
79796
80400
  }
@@ -80327,7 +80931,7 @@ function runRedactStdin() {
80327
80931
  }
80328
80932
 
80329
80933
  // src/cli/status-ask.ts
80330
- import { readFileSync as readFileSync60, existsSync as existsSync74, readdirSync as readdirSync27 } from "node:fs";
80934
+ import { readFileSync as readFileSync61, existsSync as existsSync74, readdirSync as readdirSync27 } from "node:fs";
80331
80935
  import { join as join73 } from "node:path";
80332
80936
  import { homedir as homedir42 } from "node:os";
80333
80937
 
@@ -80603,7 +81207,7 @@ function runReport(opts) {
80603
81207
  for (const src of sources) {
80604
81208
  let content;
80605
81209
  try {
80606
- content = readFileSync60(src.path, "utf-8");
81210
+ content = readFileSync61(src.path, "utf-8");
80607
81211
  } catch (err) {
80608
81212
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
80609
81213
  `);
@@ -80704,7 +81308,7 @@ import {
80704
81308
  existsSync as existsSync75,
80705
81309
  mkdirSync as mkdirSync42,
80706
81310
  appendFileSync as appendFileSync4,
80707
- readFileSync as readFileSync61
81311
+ readFileSync as readFileSync62
80708
81312
  } from "node:fs";
80709
81313
  var AUDIT_ROOT = join74(homedir43(), ".switchroom", "audit");
80710
81314
  function auditPathFor(agent) {
@@ -80799,7 +81403,7 @@ function readAuditTail(agent, limit, opts = {}) {
80799
81403
  return [];
80800
81404
  let raw;
80801
81405
  try {
80802
- raw = readFileSync61(path8, "utf-8");
81406
+ raw = readFileSync62(path8, "utf-8");
80803
81407
  } catch {
80804
81408
  return [];
80805
81409
  }
@@ -80960,9 +81564,9 @@ import {
80960
81564
  mkdirSync as mkdirSync43,
80961
81565
  openSync as openSync13,
80962
81566
  readdirSync as readdirSync28,
80963
- readFileSync as readFileSync62,
81567
+ readFileSync as readFileSync63,
80964
81568
  renameSync as renameSync15,
80965
- statSync as statSync28,
81569
+ statSync as statSync30,
80966
81570
  unlinkSync as unlinkSync14,
80967
81571
  writeSync as writeSync8
80968
81572
  } from "node:fs";
@@ -81006,7 +81610,7 @@ function withAgentLock(paths, fn) {
81006
81610
  if (e.code !== "EEXIST")
81007
81611
  throw err;
81008
81612
  try {
81009
- const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
81613
+ const age = Date.now() - statSync30(paths.lockPath).mtimeMs;
81010
81614
  if (age > 30000) {
81011
81615
  unlinkSync14(paths.lockPath);
81012
81616
  continue;
@@ -81084,7 +81688,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
81084
81688
  continue;
81085
81689
  const full = join75(paths.skillsDir, name);
81086
81690
  try {
81087
- const raw = readFileSync62(full, "utf-8");
81691
+ const raw = readFileSync63(full, "utf-8");
81088
81692
  const slug = name.replace(/\.ya?ml$/i, "");
81089
81693
  out.push({ slug, path: full, raw });
81090
81694
  } catch {}
@@ -81111,7 +81715,7 @@ function listOverlayEntries(agent, opts = {}) {
81111
81715
  continue;
81112
81716
  const full = join75(paths.scheduleDir, name);
81113
81717
  try {
81114
- const raw = readFileSync62(full, "utf-8");
81718
+ const raw = readFileSync63(full, "utf-8");
81115
81719
  const slug = name.replace(/\.ya?ml$/i, "");
81116
81720
  out.push({ slug, path: full, raw });
81117
81721
  } catch {}
@@ -81259,7 +81863,7 @@ import {
81259
81863
  mkdirSync as mkdirSync44,
81260
81864
  openSync as openSync14,
81261
81865
  readdirSync as readdirSync29,
81262
- readFileSync as readFileSync63,
81866
+ readFileSync as readFileSync64,
81263
81867
  renameSync as renameSync16,
81264
81868
  unlinkSync as unlinkSync15,
81265
81869
  writeFileSync as writeFileSync37,
@@ -81323,7 +81927,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
81323
81927
  if (!existsSync77(yamlPath))
81324
81928
  continue;
81325
81929
  try {
81326
- const meta = JSON.parse(readFileSync63(metaPath, "utf-8"));
81930
+ const meta = JSON.parse(readFileSync64(metaPath, "utf-8"));
81327
81931
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
81328
81932
  continue;
81329
81933
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -81362,7 +81966,7 @@ function denyPendingScheduleEntry(opts) {
81362
81966
 
81363
81967
  // src/cli/agent-config-write.ts
81364
81968
  init_protocol3();
81365
- import { existsSync as existsSync78, readFileSync as readFileSync64 } from "node:fs";
81969
+ import { existsSync as existsSync78, readFileSync as readFileSync65 } from "node:fs";
81366
81970
  var MAX_ENTRIES_PER_AGENT = 20;
81367
81971
  var MIN_CRON_INTERVAL_MIN = 5;
81368
81972
  function extractCronSmallestGapMin(expr) {
@@ -81649,7 +82253,7 @@ function scheduleRemove(opts) {
81649
82253
  let priorContent = null;
81650
82254
  try {
81651
82255
  if (existsSync78(match.path))
81652
- priorContent = readFileSync64(match.path, "utf-8");
82256
+ priorContent = readFileSync65(match.path, "utf-8");
81653
82257
  } catch {}
81654
82258
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
81655
82259
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -82089,12 +82693,12 @@ import {
82089
82693
  mkdirSync as mkdirSync45,
82090
82694
  mkdtempSync as mkdtempSync5,
82091
82695
  openSync as openSync15,
82092
- readFileSync as readFileSync65,
82696
+ readFileSync as readFileSync66,
82093
82697
  readdirSync as readdirSync30,
82094
82698
  realpathSync as realpathSync7,
82095
82699
  renameSync as renameSync17,
82096
82700
  rmSync as rmSync16,
82097
- statSync as statSync29,
82701
+ statSync as statSync31,
82098
82702
  writeFileSync as writeFileSync38
82099
82703
  } from "node:fs";
82100
82704
  import { tmpdir as tmpdir5, homedir as homedir44 } from "node:os";
@@ -82324,7 +82928,7 @@ function isTarballPath(p) {
82324
82928
  }
82325
82929
  function loadFromDir(dir) {
82326
82930
  const abs = realpathSync7(dir);
82327
- if (!statSync29(abs).isDirectory()) {
82931
+ if (!statSync31(abs).isDirectory()) {
82328
82932
  fail2(`--from path is not a directory: ${dir}`);
82329
82933
  }
82330
82934
  const files = {};
@@ -82341,7 +82945,7 @@ function loadFromDir(dir) {
82341
82945
  continue;
82342
82946
  }
82343
82947
  if (ent.isFile()) {
82344
- const buf = readFileSync65(full);
82948
+ const buf = readFileSync66(full);
82345
82949
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
82346
82950
  }
82347
82951
  }
@@ -82390,7 +82994,7 @@ function loadFromTarball(tarPath) {
82390
82994
  }
82391
82995
  }
82392
82996
  function loadSingleFile(filePath) {
82393
- const content = readFileSync65(filePath, "utf-8");
82997
+ const content = readFileSync66(filePath, "utf-8");
82394
82998
  return { "SKILL.md": content };
82395
82999
  }
82396
83000
  function loadFromStdin() {
@@ -82481,7 +83085,7 @@ function diffSummary(currentDir, files) {
82481
83085
  if (ent.isDirectory()) {
82482
83086
  walk2(full);
82483
83087
  } else if (ent.isFile()) {
82484
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync65(full, "utf-8");
83088
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync66(full, "utf-8");
82485
83089
  }
82486
83090
  }
82487
83091
  };
@@ -82584,7 +83188,7 @@ function registerSkillCommand(program3) {
82584
83188
  if (!existsSync80(fromPath)) {
82585
83189
  fail2(`--from path does not exist: ${opts.from}`);
82586
83190
  }
82587
- const st = statSync29(fromPath);
83191
+ const st = statSync31(fromPath);
82588
83192
  if (st.isDirectory()) {
82589
83193
  files = loadFromDir(fromPath);
82590
83194
  } else if (isTarballPath(fromPath)) {
@@ -82641,11 +83245,11 @@ import {
82641
83245
  mkdirSync as mkdirSync46,
82642
83246
  mkdtempSync as mkdtempSync6,
82643
83247
  openSync as openSync16,
82644
- readFileSync as readFileSync66,
83248
+ readFileSync as readFileSync67,
82645
83249
  readdirSync as readdirSync31,
82646
83250
  renameSync as renameSync18,
82647
83251
  rmSync as rmSync17,
82648
- statSync as statSync30,
83252
+ statSync as statSync32,
82649
83253
  utimesSync,
82650
83254
  writeFileSync as writeFileSync39
82651
83255
  } from "node:fs";
@@ -82723,7 +83327,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
82723
83327
  if (ent.isDirectory())
82724
83328
  walk2(s, d);
82725
83329
  else if (ent.isFile()) {
82726
- writeFileSync39(d, readFileSync66(s));
83330
+ writeFileSync39(d, readFileSync67(s));
82727
83331
  }
82728
83332
  }
82729
83333
  };
@@ -82786,7 +83390,7 @@ function readStdinSync2() {
82786
83390
  }
82787
83391
  function loadFromDir2(dir) {
82788
83392
  const abs = resolve47(dir);
82789
- if (!statSync30(abs).isDirectory()) {
83393
+ if (!statSync32(abs).isDirectory()) {
82790
83394
  fail3(`--from path is not a directory: ${dir}`);
82791
83395
  }
82792
83396
  const files = {};
@@ -82802,7 +83406,7 @@ function loadFromDir2(dir) {
82802
83406
  }
82803
83407
  if (ent.isFile()) {
82804
83408
  const rel = relative3(abs, full).replace(/\\/g, "/");
82805
- files[rel] = readFileSync66(full, "utf-8");
83409
+ files[rel] = readFileSync67(full, "utf-8");
82806
83410
  }
82807
83411
  }
82808
83412
  };
@@ -82872,7 +83476,7 @@ function sweepTrash(agentsRoot, agent) {
82872
83476
  continue;
82873
83477
  const entPath = join79(trash, ent.name);
82874
83478
  try {
82875
- const st = statSync30(entPath);
83479
+ const st = statSync32(entPath);
82876
83480
  if (now - st.mtimeMs > TRASH_TTL_MS) {
82877
83481
  rmSync17(entPath, { recursive: true, force: true });
82878
83482
  }
@@ -82983,12 +83587,12 @@ function loadFiles(opts) {
82983
83587
  if (!existsSync81(p)) {
82984
83588
  fail3(`--from path does not exist: ${opts.from}`);
82985
83589
  }
82986
- const st = statSync30(p);
83590
+ const st = statSync32(p);
82987
83591
  if (st.isDirectory()) {
82988
83592
  return loadFromDir2(p);
82989
83593
  }
82990
83594
  if (p.endsWith(".md")) {
82991
- return { "SKILL.md": readFileSync66(p, "utf-8") };
83595
+ return { "SKILL.md": readFileSync67(p, "utf-8") };
82992
83596
  }
82993
83597
  fail3(`--from must be a directory or a .md file. Got: ${opts.from}`);
82994
83598
  }
@@ -83077,7 +83681,7 @@ function readSourceFiles(dir) {
83077
83681
  fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
83078
83682
  }
83079
83683
  } catch {}
83080
- files[rel] = readFileSync66(full, "utf-8");
83684
+ files[rel] = readFileSync67(full, "utf-8");
83081
83685
  }
83082
83686
  }
83083
83687
  };
@@ -83204,7 +83808,7 @@ function listPersonalAction(opts) {
83204
83808
  if (e.isFile()) {
83205
83809
  fileCount += 1;
83206
83810
  try {
83207
- totalBytes += statSync30(join79(sub, e.name)).size;
83811
+ totalBytes += statSync32(join79(sub, e.name)).size;
83208
83812
  } catch {}
83209
83813
  } else if (e.isDirectory()) {
83210
83814
  walk2(join79(sub, e.name));
@@ -83246,7 +83850,7 @@ function registerSkillPersonalCommands(program3) {
83246
83850
  // src/cli/skill-search.ts
83247
83851
  init_helpers();
83248
83852
  var import_yaml23 = __toESM(require_dist(), 1);
83249
- import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync67, statSync as statSync31 } from "node:fs";
83853
+ import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync68, statSync as statSync33 } from "node:fs";
83250
83854
  import { homedir as homedir46 } from "node:os";
83251
83855
  import { join as join80, resolve as resolve48 } from "node:path";
83252
83856
  var PERSONAL_PREFIX2 = "personal-";
@@ -83267,7 +83871,7 @@ function readSkillFrontmatter(skillDir) {
83267
83871
  return null;
83268
83872
  let content;
83269
83873
  try {
83270
- content = readFileSync67(mdPath, "utf-8");
83874
+ content = readFileSync68(mdPath, "utf-8");
83271
83875
  } catch {
83272
83876
  return null;
83273
83877
  }
@@ -83297,7 +83901,7 @@ function readSkillFrontmatter(skillDir) {
83297
83901
  function statSkillMd(skillDir) {
83298
83902
  const mdPath = join80(skillDir, "SKILL.md");
83299
83903
  try {
83300
- const st = statSync31(mdPath);
83904
+ const st = statSync33(mdPath);
83301
83905
  return { size: st.size, mtime: st.mtime.toISOString() };
83302
83906
  } catch {
83303
83907
  return null;
@@ -83321,7 +83925,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
83321
83925
  continue;
83322
83926
  const dirPath = join80(skillsDir, ent);
83323
83927
  try {
83324
- if (!statSync31(dirPath).isDirectory())
83928
+ if (!statSync33(dirPath).isDirectory())
83325
83929
  continue;
83326
83930
  } catch {
83327
83931
  continue;
@@ -83361,7 +83965,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
83361
83965
  continue;
83362
83966
  const dirPath = join80(sharedRoot, ent);
83363
83967
  try {
83364
- if (!statSync31(dirPath).isDirectory())
83968
+ if (!statSync33(dirPath).isDirectory())
83365
83969
  continue;
83366
83970
  } catch {
83367
83971
  continue;
@@ -83397,7 +84001,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
83397
84001
  continue;
83398
84002
  const dirPath = join80(bundledRoot, ent);
83399
84003
  try {
83400
- if (!statSync31(dirPath).isDirectory())
84004
+ if (!statSync33(dirPath).isDirectory())
83401
84005
  continue;
83402
84006
  } catch {
83403
84007
  continue;
@@ -83539,7 +84143,7 @@ function registerHostdMcpCommand(program3) {
83539
84143
  // src/cli/hostd.ts
83540
84144
  init_source();
83541
84145
  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";
84146
+ 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
84147
  import { homedir as homedir47 } from "node:os";
83544
84148
  import { join as join81 } from "node:path";
83545
84149
  import { spawnSync as spawnSync14 } from "node:child_process";
@@ -83747,7 +84351,7 @@ function doStatus() {
83747
84351
  continue;
83748
84352
  const sockPath = join81(dir, name, "sock");
83749
84353
  if (existsSync84(sockPath)) {
83750
- const st = statSync32(sockPath);
84354
+ const st = statSync34(sockPath);
83751
84355
  if ((st.mode & 61440) === 49152) {
83752
84356
  entries.push(`${name} \u2192 ${sockPath}`);
83753
84357
  }
@@ -83794,7 +84398,7 @@ function registerHostdCommand(program3) {
83794
84398
  The log is created when hostd handles its first privileged-verb request.`));
83795
84399
  return;
83796
84400
  }
83797
- const raw = readFileSync69(logPath, "utf-8");
84401
+ const raw = readFileSync70(logPath, "utf-8");
83798
84402
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
83799
84403
  const filters = {
83800
84404
  agent: opts.agent,
@@ -84084,6 +84688,7 @@ registerDriveCommand(program3);
84084
84688
  registerDriveMcpLauncherCommand(program3);
84085
84689
  registerM365McpLauncherCommand(program3);
84086
84690
  registerNotionMcpLauncherCommand(program3);
84691
+ registerDeliverFileCommand(program3);
84087
84692
  registerNotionCommand(program3);
84088
84693
  registerApplyCommand(program3);
84089
84694
  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.88",
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.88";
52893
+ var COMMIT_SHA = "b3c61552";
52894
+ var COMMIT_DATE = "2026-06-07T16:17:55Z";
52895
+ var LATEST_PR = 2229;
52896
52896
  var COMMITS_AHEAD_OF_TAG = 0;
52897
52897
 
52898
52898
  // gateway/boot-version.ts