svamp-cli 0.2.274 → 0.2.276

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/{agentCommands-iom4Qnpt.mjs → agentCommands-DYduijxc.mjs} +5 -5
  2. package/dist/{auth-C5eN8Hte.mjs → auth-DF4dUys9.mjs} +1 -1
  3. package/dist/{cli-P3Tx1nrm.mjs → cli-BrZuHfQa.mjs} +65 -65
  4. package/dist/cli.mjs +2 -2
  5. package/dist/{commands-CKhDT3cQ.mjs → commands-7WZLaYv2.mjs} +2 -2
  6. package/dist/{commands-CQHlltkU.mjs → commands-BjkRYbLw.mjs} +2 -2
  7. package/dist/{commands-B6LByF-g.mjs → commands-Cxn4Cmts.mjs} +1 -1
  8. package/dist/{commands-D606feNR.mjs → commands-DINv6746.mjs} +1 -1
  9. package/dist/{commands-CgrbLgCc.mjs → commands-DLlgY23R.mjs} +1 -1
  10. package/dist/{commands-C7J1hEg3.mjs → commands-DvD1mdMU.mjs} +2 -2
  11. package/dist/{commands-VvErX3uk.mjs → commands-clA49kHW.mjs} +7 -7
  12. package/dist/{fleet-B1PDO1l8.mjs → fleet-xyz1ik_N.mjs} +2 -2
  13. package/dist/{frpc-Dzhtizmd.mjs → frpc-BkGcc69x.mjs} +7 -5
  14. package/dist/{headlessCli-1WzEZVMV.mjs → headlessCli-Je1q_pDw.mjs} +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/package-D3rksT8O.mjs +64 -0
  17. package/dist/{pinnedClaudeCode-CgEtGkX2.mjs → pinnedClaudeCode-D8Tbz32G.mjs} +1 -1
  18. package/dist/{rpc-BMoQLUTg.mjs → rpc-CBa5WaBD.mjs} +1 -1
  19. package/dist/{rpc-CIs3wZAu.mjs → rpc-HiJYPcOU.mjs} +1 -1
  20. package/dist/{run-DxoIx34n.mjs → run-BRHMebDp.mjs} +4 -3
  21. package/dist/{run-l_8UB-9h.mjs → run-DjThStDG.mjs} +272 -68
  22. package/dist/{scheduler-uZ__o3N7.mjs → scheduler-D1JpZ5m8.mjs} +1 -1
  23. package/dist/{serveCommands-twuwB_gw.mjs → serveCommands-D-3LtTUy.mjs} +5 -5
  24. package/dist/{sideband-C8myzP2c.mjs → sideband-DdcXgJHf.mjs} +1 -1
  25. package/package.json +2 -2
  26. package/dist/package-BYElq4R_.mjs +0 -64
@@ -3,7 +3,7 @@ import fs$1, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as wr
3
3
  import * as fs from 'fs';
4
4
  import fs__default, { existsSync as existsSync$1, rmSync, readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync as renameSync$1, realpathSync as realpathSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
5
5
  import * as path from 'path';
6
- import path__default, { join, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
6
+ import path__default, { join, delimiter, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { execFile, spawn, execSync, exec as exec$1 } from 'child_process';
9
9
  import * as crypto from 'crypto';
@@ -1158,8 +1158,14 @@ async function readProcessTable() {
1158
1158
  let stdout;
1159
1159
  try {
1160
1160
  const result = await execFileAsync$1("ps", ["-A", "-o", "pid=,ppid=,etime=,%cpu=,rss=,command="], {
1161
- maxBuffer: 8 * 1024 * 1024
1161
+ maxBuffer: 8 * 1024 * 1024,
1162
1162
  // 8MB — plenty for any realistic process table
1163
+ // Hard wall-clock deadline: on a wedged host (D-state / stuck FS) `ps` can block
1164
+ // forever; maxBuffer bounds size, not time. Without this the awaited promise never
1165
+ // settles and callers (killDescendant admin RPC, Tasks-panel detectDescendants) hang.
1166
+ // On timeout execFile rejects → the catch below returns [] (best-effort, no crash).
1167
+ timeout: 5e3,
1168
+ killSignal: "SIGKILL"
1163
1169
  });
1164
1170
  stdout = result.stdout;
1165
1171
  } catch {
@@ -1500,6 +1506,9 @@ const COOKIE_NAME = "svamp_serve_token";
1500
1506
  const SVAMP_SERVE_TOKEN_PARAM = "__svamp_serve_token";
1501
1507
  const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1502
1508
  const DEFAULT_CACHE_MAX_SIZE = 1e3;
1509
+ function jsStringLiteral(value) {
1510
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1511
+ }
1503
1512
  class TokenCache {
1504
1513
  cache = /* @__PURE__ */ new Map();
1505
1514
  ttlMs;
@@ -1602,16 +1611,19 @@ function isStructurallyLiveJwt(token) {
1602
1611
  async function verifyTokenViaHypha(token, hyphaServerUrl) {
1603
1612
  try {
1604
1613
  const baseUrl = hyphaServerUrl.replace(/\/$/, "");
1605
- const url = `${baseUrl}/public/services/ws/get_user_info`;
1614
+ const url = `${baseUrl}/public/services/ws/parse_token`;
1606
1615
  const resp = await fetch(url, {
1607
- method: "GET",
1616
+ method: "POST",
1608
1617
  headers: {
1609
- Authorization: `Bearer ${token}`
1618
+ Authorization: `Bearer ${token}`,
1619
+ "Content-Type": "application/json"
1610
1620
  },
1621
+ body: JSON.stringify({ token }),
1611
1622
  signal: AbortSignal.timeout(1e4)
1612
1623
  });
1613
1624
  if (!resp.ok) return null;
1614
1625
  const data = await resp.json();
1626
+ if (data?.is_anonymous === true) return null;
1615
1627
  const email = data?.email;
1616
1628
  if (typeof email === "string" && email) return email.toLowerCase();
1617
1629
  return null;
@@ -1701,10 +1713,10 @@ a:hover{text-decoration:underline}
1701
1713
  <div class="status" id="status"></div>
1702
1714
  </div>
1703
1715
  <script>
1704
- const hyphaServer = ${JSON.stringify(this.hyphaServerUrl)};
1705
- const redirectUrl = ${JSON.stringify(redirectUrl)};
1706
- const cookieName = ${JSON.stringify(COOKIE_NAME)};
1707
- const tokenParam = ${JSON.stringify(SVAMP_SERVE_TOKEN_PARAM)};
1716
+ const hyphaServer = ${jsStringLiteral(this.hyphaServerUrl)};
1717
+ const redirectUrl = ${jsStringLiteral(redirectUrl)};
1718
+ const cookieName = ${jsStringLiteral(COOKIE_NAME)};
1719
+ const tokenParam = ${jsStringLiteral(SVAMP_SERVE_TOKEN_PARAM)};
1708
1720
 
1709
1721
  const btn = document.getElementById('login-btn');
1710
1722
  const statusEl = document.getElementById('status');
@@ -2201,7 +2213,7 @@ class ServeManager {
2201
2213
  throw new Error(`Path does not exist: ${resolvedDir}`);
2202
2214
  }
2203
2215
  if (this.mounts.has(spec.name)) {
2204
- await this.removeMount(spec.name);
2216
+ await this.removeMount(spec.name, { replacing: true });
2205
2217
  }
2206
2218
  const access = spec.access ?? "link";
2207
2219
  if (access === "owner" && !spec.ownerEmail) {
@@ -2240,7 +2252,7 @@ class ServeManager {
2240
2252
  /**
2241
2253
  * Remove a mount. If no mounts remain, stop Caddy + auth proxy.
2242
2254
  */
2243
- async removeMount(name) {
2255
+ async removeMount(name, opts) {
2244
2256
  if (!this.mounts.has(name)) {
2245
2257
  throw new Error(`Mount '${name}' not found`);
2246
2258
  }
@@ -2260,7 +2272,7 @@ class ServeManager {
2260
2272
  }
2261
2273
  this.persist();
2262
2274
  this.log(`Mount removed: ${name}`);
2263
- if (this.mounts.size === 0) {
2275
+ if (this.mounts.size === 0 && !opts?.replacing) {
2264
2276
  await this.shutdown();
2265
2277
  }
2266
2278
  }
@@ -2403,7 +2415,12 @@ class ServeManager {
2403
2415
  cwd: cfg.workdir ?? process.cwd(),
2404
2416
  env: { ...process.env, ...cfg.env ?? {} },
2405
2417
  stdio: ["ignore", "pipe", "pipe"],
2406
- detached: false
2418
+ // #0483: run in its OWN process group so we can kill the whole tree. Managed mounts
2419
+ // are typically wrappers (npm start / npm run dev / python -m …) whose real listener
2420
+ // is a GRANDCHILD; killing only the direct pid leaves the grandchild holding the port
2421
+ // → next warmup hits EADDRINUSE → mount permanently broken + orphan accumulation.
2422
+ // killManagedTree() below signals -pid (the group).
2423
+ detached: true
2407
2424
  });
2408
2425
  child.stdout?.on("data", (d) => {
2409
2426
  const line = d.toString().trimEnd();
@@ -2429,10 +2446,7 @@ class ServeManager {
2429
2446
  this.log(`Managed process '${name}' ready on 127.0.0.1:${cfg.port}`);
2430
2447
  }).catch((err) => {
2431
2448
  this.log(`Managed process '${name}' warmup failed: ${err.message}`);
2432
- try {
2433
- child.kill("SIGTERM");
2434
- } catch {
2435
- }
2449
+ this.killManagedTree(child, "SIGTERM");
2436
2450
  this.managedProcs.delete(name);
2437
2451
  throw err;
2438
2452
  });
@@ -2440,12 +2454,33 @@ class ServeManager {
2440
2454
  child,
2441
2455
  pid: child.pid ?? 0,
2442
2456
  lastRequestAt: Date.now(),
2457
+ activeConns: 0,
2443
2458
  warmupPromise
2444
2459
  };
2445
2460
  this.managedProcs.set(name, newHandle);
2446
2461
  this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
2447
2462
  return warmupPromise;
2448
2463
  }
2464
+ /**
2465
+ * #0483: kill the managed child's whole PROCESS GROUP (it was spawned detached, so it leads
2466
+ * its own group). This reaps grandchildren (the real listener behind an npm/python wrapper)
2467
+ * that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
2468
+ * child.kill() if the group signal fails (e.g. pid already gone).
2469
+ */
2470
+ killManagedTree(child, signal) {
2471
+ const pid = child.pid;
2472
+ if (pid && pid > 0) {
2473
+ try {
2474
+ process.kill(-pid, signal);
2475
+ return;
2476
+ } catch {
2477
+ }
2478
+ }
2479
+ try {
2480
+ child.kill(signal);
2481
+ } catch {
2482
+ }
2483
+ }
2449
2484
  /** Poll a URL until it returns <500 or the deadline passes. */
2450
2485
  async warmupProbe(url, timeoutMs) {
2451
2486
  const deadline = Date.now() + timeoutMs;
@@ -2478,17 +2513,11 @@ class ServeManager {
2478
2513
  const h = this.managedProcs.get(name);
2479
2514
  if (!h) return;
2480
2515
  this.managedProcs.delete(name);
2481
- try {
2482
- h.child.kill("SIGTERM");
2483
- } catch {
2484
- }
2485
2516
  const child = h.child;
2517
+ this.killManagedTree(child, "SIGTERM");
2486
2518
  await new Promise((resolve) => {
2487
2519
  const t = setTimeout(() => {
2488
- try {
2489
- child.kill("SIGKILL");
2490
- } catch {
2491
- }
2520
+ this.killManagedTree(child, "SIGKILL");
2492
2521
  resolve();
2493
2522
  }, 5e3);
2494
2523
  child.once("exit", () => {
@@ -2508,6 +2537,10 @@ class ServeManager {
2508
2537
  if (!cfg || !cfg.idleTimeoutSec || cfg.idleTimeoutSec <= 0) continue;
2509
2538
  const h = this.managedProcs.get(name);
2510
2539
  if (!h || h.warmupPromise) continue;
2540
+ if (h.activeConns > 0) {
2541
+ h.lastRequestAt = now;
2542
+ continue;
2543
+ }
2511
2544
  if (now - h.lastRequestAt >= cfg.idleTimeoutSec * 1e3) {
2512
2545
  this.log(`Idle eviction: stopping '${name}' (idle ${Math.round((now - h.lastRequestAt) / 1e3)}s \u2265 ${cfg.idleTimeoutSec}s)`);
2513
2546
  this.stopManagedProcess(name).catch(() => {
@@ -2538,7 +2571,9 @@ class ServeManager {
2538
2571
  ...this.port > 0 ? { port: this.port } : {}
2539
2572
  };
2540
2573
  try {
2541
- fs.writeFileSync(this.persistFile, JSON.stringify(state, null, 2));
2574
+ const tmp = `${this.persistFile}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
2575
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
2576
+ fs.renameSync(tmp, this.persistFile);
2542
2577
  } catch (err) {
2543
2578
  this.log(`Error persisting serve state: ${err.message}`);
2544
2579
  }
@@ -2676,12 +2711,27 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
2676
2711
  if (req.method === "PUT") {
2677
2712
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
2678
2713
  const ws = fs.createWriteStream(filePath);
2714
+ let done = false;
2715
+ const cleanupPartial = () => {
2716
+ if (done) return;
2717
+ done = true;
2718
+ ws.destroy();
2719
+ fs.unlink(filePath, () => {
2720
+ });
2721
+ };
2722
+ req.on("aborted", cleanupPartial);
2723
+ req.on("close", () => {
2724
+ if (!ws.writableFinished) cleanupPartial();
2725
+ });
2679
2726
  req.pipe(ws);
2680
2727
  ws.on("finish", () => {
2728
+ done = true;
2681
2729
  res.writeHead(201);
2682
2730
  res.end();
2683
2731
  });
2684
2732
  ws.on("error", (err) => {
2733
+ if (done) return;
2734
+ done = true;
2685
2735
  res.writeHead(500);
2686
2736
  res.end(err.message);
2687
2737
  });
@@ -2709,7 +2759,18 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
2709
2759
  }
2710
2760
  }
2711
2761
  const handle = this.managedProcs.get(mount.name);
2712
- if (handle) handle.lastRequestAt = Date.now();
2762
+ if (handle) {
2763
+ handle.lastRequestAt = Date.now();
2764
+ handle.activeConns++;
2765
+ let released = false;
2766
+ const release = () => {
2767
+ if (released) return;
2768
+ released = true;
2769
+ handle.activeConns = Math.max(0, handle.activeConns - 1);
2770
+ handle.lastRequestAt = Date.now();
2771
+ };
2772
+ res.on("close", release);
2773
+ }
2713
2774
  const targetPath = mountResolvedByHost ? req.url || "/" : (basePath || "/") + (url.search || "");
2714
2775
  const proxyReq = http.request({
2715
2776
  hostname: "127.0.0.1",
@@ -2810,7 +2871,17 @@ Connection: close\r
2810
2871
  }
2811
2872
  }
2812
2873
  const handle = this.managedProcs.get(mount.name);
2813
- if (handle) handle.lastRequestAt = Date.now();
2874
+ if (handle) {
2875
+ handle.lastRequestAt = Date.now();
2876
+ handle.activeConns++;
2877
+ let released = false;
2878
+ clientSocket.on("close", () => {
2879
+ if (released) return;
2880
+ released = true;
2881
+ handle.activeConns = Math.max(0, handle.activeConns - 1);
2882
+ handle.lastRequestAt = Date.now();
2883
+ });
2884
+ }
2814
2885
  const targetPath = mountResolvedByHost ? req.url || "/" : (url.pathname.slice(`/${mountName}`.length) || "/") + (url.search || "");
2815
2886
  const upstream = net.connect(cfg.port, "127.0.0.1", () => {
2816
2887
  const lines = [`${req.method} ${targetPath} HTTP/1.1`];
@@ -2902,7 +2973,7 @@ Connection: close\r
2902
2973
  const mount = this.mounts.get(mountName);
2903
2974
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
2904
2975
  try {
2905
- const { FrpcTunnel } = await import('./frpc-Dzhtizmd.mjs');
2976
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
2906
2977
  let tunnel;
2907
2978
  tunnel = new FrpcTunnel({
2908
2979
  name: tunnelName,
@@ -4297,6 +4368,14 @@ class ChannelOutbox {
4297
4368
  file;
4298
4369
  byChannel = /* @__PURE__ */ new Map();
4299
4370
  seqByChannel = /* @__PURE__ */ new Map();
4371
+ // #0500: per-channel MONOTONIC high-water seq that survives TTL eviction/compaction. seqByChannel
4372
+ // is rebuilt from RETAINED records on every reload, so once a channel's replies all age out (>1h
4373
+ // idle) its seq would reset to 1 — a long-poll receiver holding a pre-eviction cursor N would then
4374
+ // silently drop the new seq=1..N replies (since() filters seq > cursor). highWater is never lowered
4375
+ // and is persisted to a small sidecar (_outbox_seq.json) so seq stays monotonic across eviction AND
4376
+ // daemon restarts. Cross-process safe: re-read + max before each bump (via reload()).
4377
+ highWater = /* @__PURE__ */ new Map();
4378
+ seqFile;
4300
4379
  emitter = new EventEmitter();
4301
4380
  appendsSinceCompact = 0;
4302
4381
  // #0362: change-detection for reload() — a long-poll receiver reuses ONE instance and only
@@ -4307,12 +4386,14 @@ class ChannelOutbox {
4307
4386
  constructor(projectDir) {
4308
4387
  const dir = join$1(projectDir, ".svamp", "channels");
4309
4388
  this.file = join$1(dir, "_outbox.jsonl");
4389
+ this.seqFile = join$1(dir, "_outbox_seq.json");
4310
4390
  this.emitter.setMaxListeners(0);
4311
4391
  try {
4312
4392
  mkdirSync$1(dir, { recursive: true });
4313
4393
  } catch {
4314
4394
  }
4315
4395
  this._load();
4396
+ this._loadHighWater();
4316
4397
  }
4317
4398
  /** Current on-disk (mtimeMs, size), or (0, -1) if the file does not exist. */
4318
4399
  _stat() {
@@ -4335,6 +4416,7 @@ class ChannelOutbox {
4335
4416
  this.byChannel.clear();
4336
4417
  this.seqByChannel.clear();
4337
4418
  this._load();
4419
+ this._loadHighWater();
4338
4420
  return true;
4339
4421
  }
4340
4422
  _load() {
@@ -4357,6 +4439,7 @@ class ChannelOutbox {
4357
4439
  arr.push(reply);
4358
4440
  this.byChannel.set(channelId, arr);
4359
4441
  this.seqByChannel.set(channelId, Math.max(this.seqByChannel.get(channelId) || 0, reply.seq));
4442
+ this.highWater.set(channelId, Math.max(this.highWater.get(channelId) || 0, reply.seq));
4360
4443
  }
4361
4444
  for (const [ch, arr] of this.byChannel) if (arr.length > MAX_PER_CHANNEL) this.byChannel.set(ch, arr.slice(-MAX_PER_CHANNEL));
4362
4445
  } catch {
@@ -4384,6 +4467,30 @@ class ChannelOutbox {
4384
4467
  } catch {
4385
4468
  }
4386
4469
  }
4470
+ /** #0500: merge the durable per-channel high-water sidecar into memory (never lowers a value). */
4471
+ _loadHighWater() {
4472
+ try {
4473
+ if (!existsSync(this.seqFile)) return;
4474
+ const obj = JSON.parse(readFileSync(this.seqFile, "utf8"));
4475
+ for (const [ch, seq] of Object.entries(obj)) {
4476
+ if (typeof seq === "number" && Number.isFinite(seq)) {
4477
+ this.highWater.set(ch, Math.max(this.highWater.get(ch) || 0, seq));
4478
+ }
4479
+ }
4480
+ } catch {
4481
+ }
4482
+ }
4483
+ /** #0500: atomically persist the high-water map so seq survives eviction + restart. Best-effort. */
4484
+ _persistHighWater() {
4485
+ try {
4486
+ const obj = {};
4487
+ for (const [ch, seq] of this.highWater) obj[ch] = seq;
4488
+ const tmp = `${this.seqFile}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
4489
+ writeFileSync$1(tmp, JSON.stringify(obj));
4490
+ renameSync(tmp, this.seqFile);
4491
+ } catch {
4492
+ }
4493
+ }
4387
4494
  _evict(channelId) {
4388
4495
  const cutoff = Date.now() - TTL_MS;
4389
4496
  let arr = this.byChannel.get(channelId);
@@ -4394,8 +4501,11 @@ class ChannelOutbox {
4394
4501
  }
4395
4502
  /** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
4396
4503
  append(channelId, r) {
4397
- const seq = (this.seqByChannel.get(channelId) || 0) + 1;
4504
+ this.reload();
4505
+ const seq = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
4398
4506
  this.seqByChannel.set(channelId, seq);
4507
+ this.highWater.set(channelId, seq);
4508
+ this._persistHighWater();
4399
4509
  const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
4400
4510
  const arr = this.byChannel.get(channelId) || [];
4401
4511
  arr.push(reply);
@@ -4412,6 +4522,7 @@ class ChannelOutbox {
4412
4522
  }
4413
4523
  if (++this.appendsSinceCompact >= COMPACT_EVERY) {
4414
4524
  this.appendsSinceCompact = 0;
4525
+ this.reload();
4415
4526
  this._compact();
4416
4527
  }
4417
4528
  this.emitter.emit(channelId, reply);
@@ -4469,6 +4580,7 @@ class ChannelOutbox {
4469
4580
  }
4470
4581
  /** Drop a channel's outbox (on channel delete) — best-effort rewrite of the log. */
4471
4582
  purge(channelId) {
4583
+ if (existsSync(this.file)) this.reload();
4472
4584
  this.byChannel.delete(channelId);
4473
4585
  this.seqByChannel.delete(channelId);
4474
4586
  if (!existsSync(this.file)) return;
@@ -5704,7 +5816,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5704
5816
  const tunnels = handlers.tunnels;
5705
5817
  if (!tunnels) throw new Error("Tunnel management not available");
5706
5818
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
5707
- const { FrpcTunnel } = await import('./frpc-Dzhtizmd.mjs');
5819
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
5708
5820
  const tunnel = new FrpcTunnel({
5709
5821
  name: params.name,
5710
5822
  ports: params.ports,
@@ -5852,7 +5964,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5852
5964
  authorizeRequest(context, currentMetadata.sharing, "view");
5853
5965
  const sm = handlers.serveManager;
5854
5966
  if (!sm) throw new Error("Serve manager not available");
5855
- return sm.getInfo();
5967
+ const info = sm.getInfo();
5968
+ const isAdmin = serveCallerTrusted(context);
5969
+ return { ...info, mounts: (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin)) };
5856
5970
  },
5857
5971
  /**
5858
5972
  * Aggregate frpc tunnel health for all serve mounts.
@@ -6178,7 +6292,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6178
6292
  }
6179
6293
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6180
6294
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6181
- const { toolsForRole } = await import('./sideband-C8myzP2c.mjs');
6295
+ const { toolsForRole } = await import('./sideband-DdcXgJHf.mjs');
6182
6296
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6183
6297
  return fmt(r2);
6184
6298
  }
@@ -6277,7 +6391,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6277
6391
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
6278
6392
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
6279
6393
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6280
- const { queryCore } = await import('./commands-D606feNR.mjs');
6394
+ const { queryCore } = await import('./commands-DINv6746.mjs');
6281
6395
  const timeout = c.reply?.timeout_sec || 120;
6282
6396
  let result;
6283
6397
  try {
@@ -6372,7 +6486,9 @@ ${d?.error || "not found"}`;
6372
6486
  });
6373
6487
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
6374
6488
  const cursor = Math.max(0, Number(kwargs.cursor) || 0);
6375
- const waitMs = Math.min(Math.max(0, Number(kwargs.wait ?? 25) * 1e3), 6e4);
6489
+ const waitRaw = Number(kwargs.wait ?? 25);
6490
+ const waitSec = Number.isFinite(waitRaw) ? waitRaw : 25;
6491
+ const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
6376
6492
  const outbox = new ChannelOutbox(dir);
6377
6493
  const deadline = Date.now() + waitMs;
6378
6494
  for (; ; ) {
@@ -6483,7 +6599,7 @@ function writeInboundContext(sessionId, ctx) {
6483
6599
  try {
6484
6600
  const p = ctxPath(sessionId);
6485
6601
  mkdirSync$1(join$1(SVAMP_HOME$2, "inbound"), { recursive: true });
6486
- const tmp = p + ".tmp";
6602
+ const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;
6487
6603
  writeFileSync$1(tmp, JSON.stringify({ ...ctx, deliveredAt: Date.now() }));
6488
6604
  renameSync(tmp, p);
6489
6605
  } catch {
@@ -7084,7 +7200,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
7084
7200
  const skillCtxFor = (c) => ({
7085
7201
  channelsServiceId,
7086
7202
  baseUrl: channelsBaseUrl,
7087
- key: c.identity?.shared_key || c.identity?.callers?.[0]?.key,
7203
+ // #0512: embed ONLY the channel-wide shared_key. The previous `|| callers?.[0]?.key`
7204
+ // fallback leaked one specific caller's PRIVATE per-key credential into the skill body,
7205
+ // which is served by the UNAUTHENTICATED channelDescribe (and view-tier getChannelSkill) —
7206
+ // an anonymous reader could then impersonate that caller (send as them + read their outbox).
7207
+ // For a per-key channel (no shared_key) key is undefined and generateSkillBody renders the
7208
+ // <your-key> placeholder, which is the correct instruction for a per-key caller.
7209
+ key: c.identity?.shared_key,
7088
7210
  // The session this skill is being copied FROM — baked into `dynamic` channel
7089
7211
  // instructions as the routing target so the copied link lands in THIS session.
7090
7212
  session: sessionId
@@ -7467,7 +7589,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
7467
7589
  name: c.name,
7468
7590
  skillUrl,
7469
7591
  sendUrl,
7470
- key: c.identity?.shared_key || c.identity?.callers?.[0]?.key,
7592
+ // #0512: only the channel-wide shared_key never an individual caller's private key
7593
+ // (this getChannelSkill result is reachable at view tier).
7594
+ key: c.identity?.shared_key,
7471
7595
  hyphaKeyless: (c.identity?.hypha_allow || []).length > 0,
7472
7596
  bind: c.bind,
7473
7597
  bindMode: mode,
@@ -8713,6 +8837,23 @@ function handleClaudeMessage(ctx, msg) {
8713
8837
  }
8714
8838
  }
8715
8839
 
8840
+ const KNOWN_AGENT_LAUNCHERS = ["claude", "codex", "gemini"];
8841
+ function agentLauncherExists(cmd, env = process.env) {
8842
+ if (!cmd) return false;
8843
+ if (cmd.includes("/") || cmd.includes("\\")) return existsSync$1(cmd);
8844
+ const dirs = (env.PATH || "").split(delimiter).filter(Boolean);
8845
+ const exts = process.platform === "win32" ? ["", ...(env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""];
8846
+ for (const dir of dirs) {
8847
+ for (const ext of exts) {
8848
+ if (existsSync$1(join(dir, cmd + ext))) return true;
8849
+ }
8850
+ }
8851
+ return false;
8852
+ }
8853
+ function detectAvailableAgents(env = process.env) {
8854
+ return KNOWN_AGENT_LAUNCHERS.filter((c) => agentLauncherExists(c, env));
8855
+ }
8856
+
8716
8857
  const INLINE_ARTIFACT_MAX_BYTES = 512 * 1024;
8717
8858
  const MAX_STORED_MESSAGE_BYTES = 2 * 1024 * 1024;
8718
8859
  const MIN_DATA_URI_SCAN_CHARS = INLINE_ARTIFACT_MAX_BYTES;
@@ -12077,7 +12218,31 @@ async function verifyIsolation(method, binaryPath) {
12077
12218
  return { passed: false, error: `${method} is not supported for external isolation` };
12078
12219
  }
12079
12220
  }
12221
+ function isolationDisabledByEnv(env = process.env) {
12222
+ const v = (env.SVAMP_ISOLATION || "").trim().toLowerCase();
12223
+ if (v && ["none", "off", "disabled", "0", "false", "no"].includes(v)) return true;
12224
+ const truthy = (s) => !!s && !["", "0", "false", "no", "off"].includes(s.trim().toLowerCase());
12225
+ return truthy(env.SVAMP_NO_ISOLATION) || truthy(env.SVAMP_SKIP_NONO);
12226
+ }
12080
12227
  async function detectIsolationCapabilities() {
12228
+ if (isolationDisabledByEnv()) {
12229
+ console.log(
12230
+ "[isolation] Disabled via env (SVAMP_ISOLATION=none) \u2014 skipping nono auto-install + runtime detection. Sessions run WITHOUT per-session sandboxing; session sharing is allowed (rely on the container/host boundary)."
12231
+ );
12232
+ const notFound = () => ({ found: false });
12233
+ return {
12234
+ available: [],
12235
+ preferred: null,
12236
+ disabled: true,
12237
+ details: {
12238
+ nono: notFound(),
12239
+ docker: notFound(),
12240
+ podman: notFound(),
12241
+ srt: notFound(),
12242
+ bwrap: notFound()
12243
+ }
12244
+ };
12245
+ }
12081
12246
  const checks = {
12082
12247
  nono: checkCommand("nono", ["--version"]),
12083
12248
  docker: checkCommand("docker", ["--version"]),
@@ -12567,6 +12732,7 @@ class ProcessSupervisor {
12567
12732
  throw new Error(`Process '${entry.spec.name}' is already running`);
12568
12733
  }
12569
12734
  entry.stopping = false;
12735
+ this.clearTimers(entry);
12570
12736
  if (entry.spec.desiredStopped) {
12571
12737
  entry.spec.desiredStopped = false;
12572
12738
  await this.persistSpec(entry.spec);
@@ -12625,6 +12791,8 @@ class ProcessSupervisor {
12625
12791
  this.clearTimers(entry);
12626
12792
  await this.killChild(entry.child);
12627
12793
  entry.child = void 0;
12794
+ } else {
12795
+ this.clearTimers(entry);
12628
12796
  }
12629
12797
  if (entry.deleted) return;
12630
12798
  entry.stopping = false;
@@ -12856,6 +13024,7 @@ class ProcessSupervisor {
12856
13024
  spawnProcess(entry) {
12857
13025
  const { spec, state } = entry;
12858
13026
  if (entry.deleted) return;
13027
+ state.consecutiveProbeFailures = 0;
12859
13028
  try {
12860
13029
  const env = { ...process.env, ...spec.env ?? {} };
12861
13030
  const child = spawn(spec.command, spec.args, {
@@ -12960,6 +13129,7 @@ class ProcessSupervisor {
12960
13129
  );
12961
13130
  entry.restartTimer = setTimeout(() => {
12962
13131
  if (entry.deleted || entry.stopping) return;
13132
+ if (entry.child) return;
12963
13133
  state.restartCount++;
12964
13134
  state.status = "starting";
12965
13135
  this.spawnProcess(entry);
@@ -12988,6 +13158,8 @@ class ProcessSupervisor {
12988
13158
  const timeoutMs = (probe.timeout ?? DEFAULT_PROBE_TIMEOUT_S) * 1e3;
12989
13159
  const threshold = probe.failureThreshold ?? DEFAULT_PROBE_FAILURE_THRESHOLD;
12990
13160
  const url = `http://localhost:${probe.port}${urlPath}`;
13161
+ const probedChild = entry.child;
13162
+ const stillSameChild = () => !entry.deleted && !entry.stopping && entry.child === probedChild && entry.state.status === "running";
12991
13163
  try {
12992
13164
  const controller = new AbortController();
12993
13165
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -12997,6 +13169,7 @@ class ProcessSupervisor {
12997
13169
  } finally {
12998
13170
  clearTimeout(timer);
12999
13171
  }
13172
+ if (!stillSameChild()) return;
13000
13173
  const ok = resp.ok;
13001
13174
  entry.state.lastProbe = { ok, timestamp: Date.now(), statusCode: resp.status };
13002
13175
  if (ok) {
@@ -13011,6 +13184,7 @@ class ProcessSupervisor {
13011
13184
  }
13012
13185
  }
13013
13186
  } catch (err) {
13187
+ if (!stillSameChild()) return;
13014
13188
  entry.state.lastProbe = { ok: false, timestamp: Date.now(), error: err.message };
13015
13189
  entry.state.consecutiveProbeFailures++;
13016
13190
  console.warn(
@@ -13041,7 +13215,12 @@ class ProcessSupervisor {
13041
13215
  return;
13042
13216
  }
13043
13217
  console.log(`[SUPERVISOR] Process '${entry.spec.name}' TTL: expires in ${remainingS.toFixed(0)}s`);
13044
- entry.ttlTimer = setTimeout(() => this.expireProcess(entry), remainingS * 1e3);
13218
+ const MAX_DELAY = 2147483647;
13219
+ const delayMs = remainingS * 1e3;
13220
+ entry.ttlTimer = setTimeout(
13221
+ () => delayMs > MAX_DELAY ? this.setupTTL(entry) : this.expireProcess(entry),
13222
+ Math.min(delayMs, MAX_DELAY)
13223
+ );
13045
13224
  }
13046
13225
  expireProcess(entry) {
13047
13226
  if (entry.deleted) return;
@@ -13973,7 +14152,7 @@ function summarizeExtensions(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENS
13973
14152
  const n = grantedExtensionCount(extensions, maxExtensions);
13974
14153
  if (n === 0) return "";
13975
14154
  const turns = extensionTurns(extensions, maxExtensions);
13976
- const reasons = extensions.slice(0, maxExtensions).map((e) => (e.reason || "").trim()).filter(Boolean).join("; ").slice(0, 300);
14155
+ const reasons = extensions.slice(0, maxExtensions).map((e) => (e?.reason || "").trim()).filter(Boolean).join("; ").slice(0, 300);
13977
14156
  return `${n} extension${n === 1 ? "" : "s"} granted (+${turns} turns)${reasons ? `: ${reasons}` : ""}`;
13978
14157
  }
13979
14158
  const DEFAULT_LOOP_STUCK_CHECKPOINTS = 3;
@@ -13981,7 +14160,7 @@ const DEFAULT_LOOP_MAX_AUTORESUMES = Number.POSITIVE_INFINITY;
13981
14160
  const PROGRESS_HISTORY_CAP = 12;
13982
14161
  function resolveStuckLimit(envValue) {
13983
14162
  const n = Number(envValue);
13984
- if (Number.isFinite(n) && n >= 1) return Math.floor(n);
14163
+ if (Number.isFinite(n) && n >= 1) return Math.min(Math.floor(n), PROGRESS_HISTORY_CAP - 1);
13985
14164
  return DEFAULT_LOOP_STUCK_CHECKPOINTS;
13986
14165
  }
13987
14166
  function resolveMaxAutoResumes(envValue) {
@@ -14039,7 +14218,8 @@ function resolveEvaluatorModel(envValue) {
14039
14218
  function parseEvaluatorVerdict(text) {
14040
14219
  if (!text) return null;
14041
14220
  const candidates = [];
14042
- let depth = 0, start = -1, inStr = false, esc = false;
14221
+ const openStack = [];
14222
+ let inStr = false, esc = false;
14043
14223
  for (let i = 0; i < text.length; i++) {
14044
14224
  const c = text[i];
14045
14225
  if (inStr) {
@@ -14055,15 +14235,11 @@ function parseEvaluatorVerdict(text) {
14055
14235
  if (c === '"') {
14056
14236
  inStr = true;
14057
14237
  } else if (c === "{") {
14058
- if (depth === 0) start = i;
14059
- depth++;
14238
+ openStack.push(i);
14060
14239
  } else if (c === "}") {
14061
- if (depth > 0) {
14062
- depth--;
14063
- if (depth === 0 && start >= 0) {
14064
- candidates.push(text.slice(start, i + 1));
14065
- start = -1;
14066
- }
14240
+ const open = openStack.pop();
14241
+ if (open !== void 0) {
14242
+ candidates.push(text.slice(open, i + 1));
14067
14243
  }
14068
14244
  }
14069
14245
  }
@@ -15329,7 +15505,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
15329
15505
  const key = s.started_at ?? "\u2205";
15330
15506
  if (key === announcedLoopStart) return;
15331
15507
  announcedLoopStart = key;
15332
- const iter = typeof s.iteration === "number" ? s.iteration : 0;
15508
+ const iter = s.ledger && typeof s.ledger.turns === "number" && s.ledger.turns > 0 ? s.ledger.turns : typeof s.iteration === "number" ? s.iteration : 0;
15333
15509
  const plural = iter === 1 ? "" : "s";
15334
15510
  const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
15335
15511
  const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
@@ -15957,7 +16133,7 @@ async function startDaemon(options) {
15957
16133
  try {
15958
16134
  const dir = loadSessionIndex()[sessionId]?.directory;
15959
16135
  if (!dir) return;
15960
- const { reconcileServiceLinks } = await import('./agentCommands-iom4Qnpt.mjs');
16136
+ const { reconcileServiceLinks } = await import('./agentCommands-DYduijxc.mjs');
15961
16137
  const configPath = getSvampConfigPath(dir, sessionId);
15962
16138
  const config = readSvampConfig(configPath);
15963
16139
  const entries = Array.from(urls.entries());
@@ -15975,7 +16151,7 @@ async function startDaemon(options) {
15975
16151
  }
15976
16152
  }
15977
16153
  async function createExposedTunnel(spec) {
15978
- const { FrpcTunnel } = await import('./frpc-Dzhtizmd.mjs');
16154
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
15979
16155
  const tunnel = new FrpcTunnel({
15980
16156
  name: spec.name,
15981
16157
  ports: spec.ports,
@@ -16003,7 +16179,7 @@ async function startDaemon(options) {
16003
16179
  ensureAutoInstalledCommands(logger);
16004
16180
  (async () => {
16005
16181
  try {
16006
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-CgEtGkX2.mjs');
16182
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
16007
16183
  beginClaudeVersionReconcile((msg) => logger.log(msg));
16008
16184
  } catch (e) {
16009
16185
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -16685,10 +16861,11 @@ ${parts.join("\n")}`);
16685
16861
  logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
16686
16862
  return;
16687
16863
  }
16688
- const ledger = ls.ledger;
16864
+ const curLs = readLoopState(directory, sessionId) || ls;
16865
+ const ledger = curLs.ledger ?? ls.ledger;
16689
16866
  const budget = ls.budget;
16690
- const progress_history = ls.progress_history;
16691
- const extensions = ls.extensions;
16867
+ const progress_history = curLs.progress_history ?? ls.progress_history;
16868
+ const extensions = curLs.extensions ?? ls.extensions;
16692
16869
  if (result.action === "rekick") {
16693
16870
  const cond = ls.task || "";
16694
16871
  writeGoalLoopState(directory, sessionId, {
@@ -16817,6 +16994,7 @@ ${parts.join("\n")}`);
16817
16994
  let consecutiveOverloadRetries = 0;
16818
16995
  let overloadBailedThisTurn = false;
16819
16996
  let pendingCompactTurn = false;
16997
+ let noIsolationWarned = false;
16820
16998
  const RATELIMIT_CFG = getRateLimitRetryConfig();
16821
16999
  let currentTurnMessage;
16822
17000
  let rateLimitRetryCount = 0;
@@ -16924,6 +17102,14 @@ ${parts.join("\n")}`);
16924
17102
  } else if (shouldIsolateSession()) {
16925
17103
  logger.log(`[Session ${sessionId}] WARNING: No isolation runtime (nono/docker/podman) available. Session is NOT sandboxed.`);
16926
17104
  sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
17105
+ if (!isolationCapabilities.disabled && !noIsolationWarned) {
17106
+ noIsolationWarned = true;
17107
+ sessionService.pushMessage({
17108
+ type: "message",
17109
+ level: "warning",
17110
+ message: "This session requested sandboxing (a security context / --isolate) but no isolation runtime (nono, docker, or podman) is available on this machine \u2014 it is running WITHOUT a sandbox. Install a runtime, or set SVAMP_ISOLATION=none to run unsandboxed intentionally (e.g. when the machine is already a container)."
17111
+ }, "event");
17112
+ }
16927
17113
  } else {
16928
17114
  sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
16929
17115
  }
@@ -16972,6 +17158,22 @@ ${parts.join("\n")}`);
16972
17158
  spawnEnv.SVAMP_SANDBOXED = "1";
16973
17159
  logger.log(`[Session ${sessionId}] Credential staging: HOME=${stagedCredentials.homePath}, SVAMP_SANDBOXED=1`);
16974
17160
  }
17161
+ if (!agentLauncherExists("claude")) {
17162
+ const others = detectAvailableAgents().filter((a) => a !== "claude");
17163
+ const hint = others.length ? ` This machine has: ${others.join(", ")} \u2014 start a ${others[0]} session instead.` : " No agent launchers were found on this machine.";
17164
+ logger.log(`[Session ${sessionId}] Claude launcher ('claude') not found on PATH \u2014 aborting spawn (available: ${detectAvailableAgents().join(", ") || "none"})`);
17165
+ sessionService.pushMessage({
17166
+ type: "message",
17167
+ level: "error",
17168
+ message: `Claude Code (\`claude\`) isn't installed on this machine (${os$1.hostname()}), so this Claude session can't run.${hint}`
17169
+ }, "event");
17170
+ sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
17171
+ sessionService.updateMetadata(sessionMetadata);
17172
+ sessionWasProcessing = false;
17173
+ signalProcessing(false);
17174
+ sessionService.sendSessionEnd();
17175
+ return void 0;
17176
+ }
16975
17177
  const child = spawn(spawnCommand, spawnArgs, {
16976
17178
  cwd: directory,
16977
17179
  stdio: ["pipe", "pipe", "pipe"],
@@ -16988,7 +17190,7 @@ ${parts.join("\n")}`);
16988
17190
  child.on("error", (err) => {
16989
17191
  logger.log(`[Session ${sessionId}] Claude process error: ${err.message}`);
16990
17192
  sessionService.pushMessage(
16991
- { type: "message", message: `Agent process exited unexpectedly: ${err.message}. Please ensure Claude Code CLI is installed.` },
17193
+ { type: "message", level: "error", message: `Agent process failed to start: ${err.message}. Please ensure the Claude Code CLI is installed on this machine.` },
16992
17194
  "event"
16993
17195
  );
16994
17196
  sessionWasProcessing = false;
@@ -18165,18 +18367,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
18165
18367
  });
18166
18368
  },
18167
18369
  onIssue: async (params) => {
18168
- const { issueRpc } = await import('./rpc-CIs3wZAu.mjs');
18370
+ const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
18169
18371
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
18170
18372
  },
18171
18373
  onWorkflow: async (params) => {
18172
- const { workflowRpc } = await import('./rpc-BMoQLUTg.mjs');
18374
+ const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
18173
18375
  return workflowRpc(params?.cwd || directory, params || {});
18174
18376
  },
18175
18377
  onRipgrep: async (args, cwd) => {
18176
- const { exec: exec2 } = await import('child_process');
18378
+ const { execFile } = await import('child_process');
18177
18379
  const rgCwd = cwd || directory;
18380
+ const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
18178
18381
  return new Promise((resolve2, reject) => {
18179
- exec2(`rg ${args}`, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
18382
+ execFile("rg", argv, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
18180
18383
  if (err && !stdout) {
18181
18384
  reject(new Error(err.message));
18182
18385
  } else {
@@ -18780,18 +18983,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
18780
18983
  });
18781
18984
  },
18782
18985
  onIssue: async (params) => {
18783
- const { issueRpc } = await import('./rpc-CIs3wZAu.mjs');
18986
+ const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
18784
18987
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
18785
18988
  },
18786
18989
  onWorkflow: async (params) => {
18787
- const { workflowRpc } = await import('./rpc-BMoQLUTg.mjs');
18990
+ const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
18788
18991
  return workflowRpc(params?.cwd || directory, params || {});
18789
18992
  },
18790
18993
  onRipgrep: async (args, cwd) => {
18791
- const { exec: exec2 } = await import('child_process');
18994
+ const { execFile } = await import('child_process');
18792
18995
  const rgCwd = cwd || directory;
18996
+ const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
18793
18997
  return new Promise((resolve2, reject) => {
18794
- exec2(`rg ${args}`, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
18998
+ execFile("rg", argv, { cwd: rgCwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
18795
18999
  if (err && !stdout) {
18796
19000
  reject(new Error(err.message));
18797
19001
  } else {
@@ -19676,7 +19880,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
19676
19880
  }
19677
19881
  if (persistedSessions.length > 0) {
19678
19882
  try {
19679
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-CgEtGkX2.mjs');
19883
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
19680
19884
  await awaitClaudeVersionReady();
19681
19885
  } catch {
19682
19886
  }
@@ -19873,7 +20077,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
19873
20077
  const PING_TIMEOUT_MS = 15e3;
19874
20078
  const POST_RECONNECT_GRACE_MS = 2e4;
19875
20079
  const RECONNECT_JITTER_MS = 2500;
19876
- const { WorkflowScheduler } = await import('./scheduler-uZ__o3N7.mjs');
20080
+ const { WorkflowScheduler } = await import('./scheduler-D1JpZ5m8.mjs');
19877
20081
  const workflowScheduler = new WorkflowScheduler({
19878
20082
  projectRoots: () => {
19879
20083
  const dirs = /* @__PURE__ */ new Set();