svamp-cli 0.2.275 → 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-DGT-1wjo.mjs → agentCommands-DYduijxc.mjs} +5 -5
  2. package/dist/{auth-CT55Rse4.mjs → auth-DF4dUys9.mjs} +1 -1
  3. package/dist/{cli-DYTmO9JL.mjs → cli-BrZuHfQa.mjs} +65 -65
  4. package/dist/cli.mjs +2 -2
  5. package/dist/{commands-C0rRjhMD.mjs → commands-7WZLaYv2.mjs} +2 -2
  6. package/dist/{commands-BV_9mFkz.mjs → commands-BjkRYbLw.mjs} +2 -2
  7. package/dist/{commands-CXHZapLH.mjs → commands-Cxn4Cmts.mjs} +1 -1
  8. package/dist/{commands-D7fIWAgz.mjs → commands-DINv6746.mjs} +1 -1
  9. package/dist/{commands-CVPcbtWV.mjs → commands-DLlgY23R.mjs} +1 -1
  10. package/dist/{commands-BvmFXAmu.mjs → commands-DvD1mdMU.mjs} +2 -2
  11. package/dist/{commands-C8Zc5axO.mjs → commands-clA49kHW.mjs} +7 -7
  12. package/dist/{fleet-BCel6ZVc.mjs → fleet-xyz1ik_N.mjs} +2 -2
  13. package/dist/{frpc-D3faR6iR.mjs → frpc-BkGcc69x.mjs} +7 -5
  14. package/dist/{headlessCli-DVBFm7og.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-xlV-4-BG.mjs → rpc-CBa5WaBD.mjs} +1 -1
  19. package/dist/{rpc-BQ8zLSUc.mjs → rpc-HiJYPcOU.mjs} +1 -1
  20. package/dist/{run-DMPVho7i.mjs → run-BRHMebDp.mjs} +4 -3
  21. package/dist/{run-C3Fcbk-H.mjs → run-DjThStDG.mjs} +192 -46
  22. package/dist/{scheduler-DaFZY2ob.mjs → scheduler-D1JpZ5m8.mjs} +1 -1
  23. package/dist/{serveCommands-B9Yhx1fl.mjs → serveCommands-D-3LtTUy.mjs} +5 -5
  24. package/dist/{sideband-CYqmMG_m.mjs → sideband-DdcXgJHf.mjs} +1 -1
  25. package/package.json +2 -2
  26. package/dist/package-DIuZ7Wfp.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';
@@ -1506,6 +1506,9 @@ const COOKIE_NAME = "svamp_serve_token";
1506
1506
  const SVAMP_SERVE_TOKEN_PARAM = "__svamp_serve_token";
1507
1507
  const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1508
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
+ }
1509
1512
  class TokenCache {
1510
1513
  cache = /* @__PURE__ */ new Map();
1511
1514
  ttlMs;
@@ -1710,10 +1713,10 @@ a:hover{text-decoration:underline}
1710
1713
  <div class="status" id="status"></div>
1711
1714
  </div>
1712
1715
  <script>
1713
- const hyphaServer = ${JSON.stringify(this.hyphaServerUrl)};
1714
- const redirectUrl = ${JSON.stringify(redirectUrl)};
1715
- const cookieName = ${JSON.stringify(COOKIE_NAME)};
1716
- 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)};
1717
1720
 
1718
1721
  const btn = document.getElementById('login-btn');
1719
1722
  const statusEl = document.getElementById('status');
@@ -2210,7 +2213,7 @@ class ServeManager {
2210
2213
  throw new Error(`Path does not exist: ${resolvedDir}`);
2211
2214
  }
2212
2215
  if (this.mounts.has(spec.name)) {
2213
- await this.removeMount(spec.name);
2216
+ await this.removeMount(spec.name, { replacing: true });
2214
2217
  }
2215
2218
  const access = spec.access ?? "link";
2216
2219
  if (access === "owner" && !spec.ownerEmail) {
@@ -2249,7 +2252,7 @@ class ServeManager {
2249
2252
  /**
2250
2253
  * Remove a mount. If no mounts remain, stop Caddy + auth proxy.
2251
2254
  */
2252
- async removeMount(name) {
2255
+ async removeMount(name, opts) {
2253
2256
  if (!this.mounts.has(name)) {
2254
2257
  throw new Error(`Mount '${name}' not found`);
2255
2258
  }
@@ -2269,7 +2272,7 @@ class ServeManager {
2269
2272
  }
2270
2273
  this.persist();
2271
2274
  this.log(`Mount removed: ${name}`);
2272
- if (this.mounts.size === 0) {
2275
+ if (this.mounts.size === 0 && !opts?.replacing) {
2273
2276
  await this.shutdown();
2274
2277
  }
2275
2278
  }
@@ -2708,12 +2711,27 @@ a{color:#0969da;text-decoration:none;font-weight:500}a:hover{text-decoration:und
2708
2711
  if (req.method === "PUT") {
2709
2712
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
2710
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
+ });
2711
2726
  req.pipe(ws);
2712
2727
  ws.on("finish", () => {
2728
+ done = true;
2713
2729
  res.writeHead(201);
2714
2730
  res.end();
2715
2731
  });
2716
2732
  ws.on("error", (err) => {
2733
+ if (done) return;
2734
+ done = true;
2717
2735
  res.writeHead(500);
2718
2736
  res.end(err.message);
2719
2737
  });
@@ -2955,7 +2973,7 @@ Connection: close\r
2955
2973
  const mount = this.mounts.get(mountName);
2956
2974
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
2957
2975
  try {
2958
- const { FrpcTunnel } = await import('./frpc-D3faR6iR.mjs');
2976
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
2959
2977
  let tunnel;
2960
2978
  tunnel = new FrpcTunnel({
2961
2979
  name: tunnelName,
@@ -4350,6 +4368,14 @@ class ChannelOutbox {
4350
4368
  file;
4351
4369
  byChannel = /* @__PURE__ */ new Map();
4352
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;
4353
4379
  emitter = new EventEmitter();
4354
4380
  appendsSinceCompact = 0;
4355
4381
  // #0362: change-detection for reload() — a long-poll receiver reuses ONE instance and only
@@ -4360,12 +4386,14 @@ class ChannelOutbox {
4360
4386
  constructor(projectDir) {
4361
4387
  const dir = join$1(projectDir, ".svamp", "channels");
4362
4388
  this.file = join$1(dir, "_outbox.jsonl");
4389
+ this.seqFile = join$1(dir, "_outbox_seq.json");
4363
4390
  this.emitter.setMaxListeners(0);
4364
4391
  try {
4365
4392
  mkdirSync$1(dir, { recursive: true });
4366
4393
  } catch {
4367
4394
  }
4368
4395
  this._load();
4396
+ this._loadHighWater();
4369
4397
  }
4370
4398
  /** Current on-disk (mtimeMs, size), or (0, -1) if the file does not exist. */
4371
4399
  _stat() {
@@ -4388,6 +4416,7 @@ class ChannelOutbox {
4388
4416
  this.byChannel.clear();
4389
4417
  this.seqByChannel.clear();
4390
4418
  this._load();
4419
+ this._loadHighWater();
4391
4420
  return true;
4392
4421
  }
4393
4422
  _load() {
@@ -4410,6 +4439,7 @@ class ChannelOutbox {
4410
4439
  arr.push(reply);
4411
4440
  this.byChannel.set(channelId, arr);
4412
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));
4413
4443
  }
4414
4444
  for (const [ch, arr] of this.byChannel) if (arr.length > MAX_PER_CHANNEL) this.byChannel.set(ch, arr.slice(-MAX_PER_CHANNEL));
4415
4445
  } catch {
@@ -4437,6 +4467,30 @@ class ChannelOutbox {
4437
4467
  } catch {
4438
4468
  }
4439
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
+ }
4440
4494
  _evict(channelId) {
4441
4495
  const cutoff = Date.now() - TTL_MS;
4442
4496
  let arr = this.byChannel.get(channelId);
@@ -4448,8 +4502,10 @@ class ChannelOutbox {
4448
4502
  /** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
4449
4503
  append(channelId, r) {
4450
4504
  this.reload();
4451
- const seq = (this.seqByChannel.get(channelId) || 0) + 1;
4505
+ const seq = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
4452
4506
  this.seqByChannel.set(channelId, seq);
4507
+ this.highWater.set(channelId, seq);
4508
+ this._persistHighWater();
4453
4509
  const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
4454
4510
  const arr = this.byChannel.get(channelId) || [];
4455
4511
  arr.push(reply);
@@ -5760,7 +5816,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5760
5816
  const tunnels = handlers.tunnels;
5761
5817
  if (!tunnels) throw new Error("Tunnel management not available");
5762
5818
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
5763
- const { FrpcTunnel } = await import('./frpc-D3faR6iR.mjs');
5819
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
5764
5820
  const tunnel = new FrpcTunnel({
5765
5821
  name: params.name,
5766
5822
  ports: params.ports,
@@ -5908,7 +5964,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5908
5964
  authorizeRequest(context, currentMetadata.sharing, "view");
5909
5965
  const sm = handlers.serveManager;
5910
5966
  if (!sm) throw new Error("Serve manager not available");
5911
- return sm.getInfo();
5967
+ const info = sm.getInfo();
5968
+ const isAdmin = serveCallerTrusted(context);
5969
+ return { ...info, mounts: (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin)) };
5912
5970
  },
5913
5971
  /**
5914
5972
  * Aggregate frpc tunnel health for all serve mounts.
@@ -6234,7 +6292,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6234
6292
  }
6235
6293
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6236
6294
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6237
- const { toolsForRole } = await import('./sideband-CYqmMG_m.mjs');
6295
+ const { toolsForRole } = await import('./sideband-DdcXgJHf.mjs');
6238
6296
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6239
6297
  return fmt(r2);
6240
6298
  }
@@ -6333,7 +6391,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6333
6391
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
6334
6392
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
6335
6393
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6336
- const { queryCore } = await import('./commands-D7fIWAgz.mjs');
6394
+ const { queryCore } = await import('./commands-DINv6746.mjs');
6337
6395
  const timeout = c.reply?.timeout_sec || 120;
6338
6396
  let result;
6339
6397
  try {
@@ -7142,7 +7200,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
7142
7200
  const skillCtxFor = (c) => ({
7143
7201
  channelsServiceId,
7144
7202
  baseUrl: channelsBaseUrl,
7145
- 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,
7146
7210
  // The session this skill is being copied FROM — baked into `dynamic` channel
7147
7211
  // instructions as the routing target so the copied link lands in THIS session.
7148
7212
  session: sessionId
@@ -7525,7 +7589,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
7525
7589
  name: c.name,
7526
7590
  skillUrl,
7527
7591
  sendUrl,
7528
- 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,
7529
7595
  hyphaKeyless: (c.identity?.hypha_allow || []).length > 0,
7530
7596
  bind: c.bind,
7531
7597
  bindMode: mode,
@@ -8771,6 +8837,23 @@ function handleClaudeMessage(ctx, msg) {
8771
8837
  }
8772
8838
  }
8773
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
+
8774
8857
  const INLINE_ARTIFACT_MAX_BYTES = 512 * 1024;
8775
8858
  const MAX_STORED_MESSAGE_BYTES = 2 * 1024 * 1024;
8776
8859
  const MIN_DATA_URI_SCAN_CHARS = INLINE_ARTIFACT_MAX_BYTES;
@@ -12135,7 +12218,31 @@ async function verifyIsolation(method, binaryPath) {
12135
12218
  return { passed: false, error: `${method} is not supported for external isolation` };
12136
12219
  }
12137
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
+ }
12138
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
+ }
12139
12246
  const checks = {
12140
12247
  nono: checkCommand("nono", ["--version"]),
12141
12248
  docker: checkCommand("docker", ["--version"]),
@@ -12625,6 +12732,7 @@ class ProcessSupervisor {
12625
12732
  throw new Error(`Process '${entry.spec.name}' is already running`);
12626
12733
  }
12627
12734
  entry.stopping = false;
12735
+ this.clearTimers(entry);
12628
12736
  if (entry.spec.desiredStopped) {
12629
12737
  entry.spec.desiredStopped = false;
12630
12738
  await this.persistSpec(entry.spec);
@@ -12683,6 +12791,8 @@ class ProcessSupervisor {
12683
12791
  this.clearTimers(entry);
12684
12792
  await this.killChild(entry.child);
12685
12793
  entry.child = void 0;
12794
+ } else {
12795
+ this.clearTimers(entry);
12686
12796
  }
12687
12797
  if (entry.deleted) return;
12688
12798
  entry.stopping = false;
@@ -12914,6 +13024,7 @@ class ProcessSupervisor {
12914
13024
  spawnProcess(entry) {
12915
13025
  const { spec, state } = entry;
12916
13026
  if (entry.deleted) return;
13027
+ state.consecutiveProbeFailures = 0;
12917
13028
  try {
12918
13029
  const env = { ...process.env, ...spec.env ?? {} };
12919
13030
  const child = spawn(spec.command, spec.args, {
@@ -13018,6 +13129,7 @@ class ProcessSupervisor {
13018
13129
  );
13019
13130
  entry.restartTimer = setTimeout(() => {
13020
13131
  if (entry.deleted || entry.stopping) return;
13132
+ if (entry.child) return;
13021
13133
  state.restartCount++;
13022
13134
  state.status = "starting";
13023
13135
  this.spawnProcess(entry);
@@ -13046,6 +13158,8 @@ class ProcessSupervisor {
13046
13158
  const timeoutMs = (probe.timeout ?? DEFAULT_PROBE_TIMEOUT_S) * 1e3;
13047
13159
  const threshold = probe.failureThreshold ?? DEFAULT_PROBE_FAILURE_THRESHOLD;
13048
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";
13049
13163
  try {
13050
13164
  const controller = new AbortController();
13051
13165
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -13055,6 +13169,7 @@ class ProcessSupervisor {
13055
13169
  } finally {
13056
13170
  clearTimeout(timer);
13057
13171
  }
13172
+ if (!stillSameChild()) return;
13058
13173
  const ok = resp.ok;
13059
13174
  entry.state.lastProbe = { ok, timestamp: Date.now(), statusCode: resp.status };
13060
13175
  if (ok) {
@@ -13069,6 +13184,7 @@ class ProcessSupervisor {
13069
13184
  }
13070
13185
  }
13071
13186
  } catch (err) {
13187
+ if (!stillSameChild()) return;
13072
13188
  entry.state.lastProbe = { ok: false, timestamp: Date.now(), error: err.message };
13073
13189
  entry.state.consecutiveProbeFailures++;
13074
13190
  console.warn(
@@ -13099,7 +13215,12 @@ class ProcessSupervisor {
13099
13215
  return;
13100
13216
  }
13101
13217
  console.log(`[SUPERVISOR] Process '${entry.spec.name}' TTL: expires in ${remainingS.toFixed(0)}s`);
13102
- 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
+ );
13103
13224
  }
13104
13225
  expireProcess(entry) {
13105
13226
  if (entry.deleted) return;
@@ -14031,7 +14152,7 @@ function summarizeExtensions(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENS
14031
14152
  const n = grantedExtensionCount(extensions, maxExtensions);
14032
14153
  if (n === 0) return "";
14033
14154
  const turns = extensionTurns(extensions, maxExtensions);
14034
- 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);
14035
14156
  return `${n} extension${n === 1 ? "" : "s"} granted (+${turns} turns)${reasons ? `: ${reasons}` : ""}`;
14036
14157
  }
14037
14158
  const DEFAULT_LOOP_STUCK_CHECKPOINTS = 3;
@@ -14039,7 +14160,7 @@ const DEFAULT_LOOP_MAX_AUTORESUMES = Number.POSITIVE_INFINITY;
14039
14160
  const PROGRESS_HISTORY_CAP = 12;
14040
14161
  function resolveStuckLimit(envValue) {
14041
14162
  const n = Number(envValue);
14042
- 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);
14043
14164
  return DEFAULT_LOOP_STUCK_CHECKPOINTS;
14044
14165
  }
14045
14166
  function resolveMaxAutoResumes(envValue) {
@@ -14097,7 +14218,8 @@ function resolveEvaluatorModel(envValue) {
14097
14218
  function parseEvaluatorVerdict(text) {
14098
14219
  if (!text) return null;
14099
14220
  const candidates = [];
14100
- let depth = 0, start = -1, inStr = false, esc = false;
14221
+ const openStack = [];
14222
+ let inStr = false, esc = false;
14101
14223
  for (let i = 0; i < text.length; i++) {
14102
14224
  const c = text[i];
14103
14225
  if (inStr) {
@@ -14113,15 +14235,11 @@ function parseEvaluatorVerdict(text) {
14113
14235
  if (c === '"') {
14114
14236
  inStr = true;
14115
14237
  } else if (c === "{") {
14116
- if (depth === 0) start = i;
14117
- depth++;
14238
+ openStack.push(i);
14118
14239
  } else if (c === "}") {
14119
- if (depth > 0) {
14120
- depth--;
14121
- if (depth === 0 && start >= 0) {
14122
- candidates.push(text.slice(start, i + 1));
14123
- start = -1;
14124
- }
14240
+ const open = openStack.pop();
14241
+ if (open !== void 0) {
14242
+ candidates.push(text.slice(open, i + 1));
14125
14243
  }
14126
14244
  }
14127
14245
  }
@@ -15387,7 +15505,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
15387
15505
  const key = s.started_at ?? "\u2205";
15388
15506
  if (key === announcedLoopStart) return;
15389
15507
  announcedLoopStart = key;
15390
- 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;
15391
15509
  const plural = iter === 1 ? "" : "s";
15392
15510
  const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
15393
15511
  const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
@@ -16015,7 +16133,7 @@ async function startDaemon(options) {
16015
16133
  try {
16016
16134
  const dir = loadSessionIndex()[sessionId]?.directory;
16017
16135
  if (!dir) return;
16018
- const { reconcileServiceLinks } = await import('./agentCommands-DGT-1wjo.mjs');
16136
+ const { reconcileServiceLinks } = await import('./agentCommands-DYduijxc.mjs');
16019
16137
  const configPath = getSvampConfigPath(dir, sessionId);
16020
16138
  const config = readSvampConfig(configPath);
16021
16139
  const entries = Array.from(urls.entries());
@@ -16033,7 +16151,7 @@ async function startDaemon(options) {
16033
16151
  }
16034
16152
  }
16035
16153
  async function createExposedTunnel(spec) {
16036
- const { FrpcTunnel } = await import('./frpc-D3faR6iR.mjs');
16154
+ const { FrpcTunnel } = await import('./frpc-BkGcc69x.mjs');
16037
16155
  const tunnel = new FrpcTunnel({
16038
16156
  name: spec.name,
16039
16157
  ports: spec.ports,
@@ -16061,7 +16179,7 @@ async function startDaemon(options) {
16061
16179
  ensureAutoInstalledCommands(logger);
16062
16180
  (async () => {
16063
16181
  try {
16064
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-CgEtGkX2.mjs');
16182
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
16065
16183
  beginClaudeVersionReconcile((msg) => logger.log(msg));
16066
16184
  } catch (e) {
16067
16185
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -16743,10 +16861,11 @@ ${parts.join("\n")}`);
16743
16861
  logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
16744
16862
  return;
16745
16863
  }
16746
- const ledger = ls.ledger;
16864
+ const curLs = readLoopState(directory, sessionId) || ls;
16865
+ const ledger = curLs.ledger ?? ls.ledger;
16747
16866
  const budget = ls.budget;
16748
- const progress_history = ls.progress_history;
16749
- const extensions = ls.extensions;
16867
+ const progress_history = curLs.progress_history ?? ls.progress_history;
16868
+ const extensions = curLs.extensions ?? ls.extensions;
16750
16869
  if (result.action === "rekick") {
16751
16870
  const cond = ls.task || "";
16752
16871
  writeGoalLoopState(directory, sessionId, {
@@ -16875,6 +16994,7 @@ ${parts.join("\n")}`);
16875
16994
  let consecutiveOverloadRetries = 0;
16876
16995
  let overloadBailedThisTurn = false;
16877
16996
  let pendingCompactTurn = false;
16997
+ let noIsolationWarned = false;
16878
16998
  const RATELIMIT_CFG = getRateLimitRetryConfig();
16879
16999
  let currentTurnMessage;
16880
17000
  let rateLimitRetryCount = 0;
@@ -16982,6 +17102,14 @@ ${parts.join("\n")}`);
16982
17102
  } else if (shouldIsolateSession()) {
16983
17103
  logger.log(`[Session ${sessionId}] WARNING: No isolation runtime (nono/docker/podman) available. Session is NOT sandboxed.`);
16984
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
+ }
16985
17113
  } else {
16986
17114
  sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
16987
17115
  }
@@ -17030,6 +17158,22 @@ ${parts.join("\n")}`);
17030
17158
  spawnEnv.SVAMP_SANDBOXED = "1";
17031
17159
  logger.log(`[Session ${sessionId}] Credential staging: HOME=${stagedCredentials.homePath}, SVAMP_SANDBOXED=1`);
17032
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
+ }
17033
17177
  const child = spawn(spawnCommand, spawnArgs, {
17034
17178
  cwd: directory,
17035
17179
  stdio: ["pipe", "pipe", "pipe"],
@@ -17046,7 +17190,7 @@ ${parts.join("\n")}`);
17046
17190
  child.on("error", (err) => {
17047
17191
  logger.log(`[Session ${sessionId}] Claude process error: ${err.message}`);
17048
17192
  sessionService.pushMessage(
17049
- { 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.` },
17050
17194
  "event"
17051
17195
  );
17052
17196
  sessionWasProcessing = false;
@@ -18223,18 +18367,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
18223
18367
  });
18224
18368
  },
18225
18369
  onIssue: async (params) => {
18226
- const { issueRpc } = await import('./rpc-BQ8zLSUc.mjs');
18370
+ const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
18227
18371
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
18228
18372
  },
18229
18373
  onWorkflow: async (params) => {
18230
- const { workflowRpc } = await import('./rpc-xlV-4-BG.mjs');
18374
+ const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
18231
18375
  return workflowRpc(params?.cwd || directory, params || {});
18232
18376
  },
18233
18377
  onRipgrep: async (args, cwd) => {
18234
- const { exec: exec2 } = await import('child_process');
18378
+ const { execFile } = await import('child_process');
18235
18379
  const rgCwd = cwd || directory;
18380
+ const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
18236
18381
  return new Promise((resolve2, reject) => {
18237
- 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) => {
18238
18383
  if (err && !stdout) {
18239
18384
  reject(new Error(err.message));
18240
18385
  } else {
@@ -18838,18 +18983,19 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
18838
18983
  });
18839
18984
  },
18840
18985
  onIssue: async (params) => {
18841
- const { issueRpc } = await import('./rpc-BQ8zLSUc.mjs');
18986
+ const { issueRpc } = await import('./rpc-HiJYPcOU.mjs');
18842
18987
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
18843
18988
  },
18844
18989
  onWorkflow: async (params) => {
18845
- const { workflowRpc } = await import('./rpc-xlV-4-BG.mjs');
18990
+ const { workflowRpc } = await import('./rpc-CBa5WaBD.mjs');
18846
18991
  return workflowRpc(params?.cwd || directory, params || {});
18847
18992
  },
18848
18993
  onRipgrep: async (args, cwd) => {
18849
- const { exec: exec2 } = await import('child_process');
18994
+ const { execFile } = await import('child_process');
18850
18995
  const rgCwd = cwd || directory;
18996
+ const argv = Array.isArray(args) ? args : String(args).split(/\s+/).filter(Boolean);
18851
18997
  return new Promise((resolve2, reject) => {
18852
- 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) => {
18853
18999
  if (err && !stdout) {
18854
19000
  reject(new Error(err.message));
18855
19001
  } else {
@@ -19734,7 +19880,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
19734
19880
  }
19735
19881
  if (persistedSessions.length > 0) {
19736
19882
  try {
19737
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-CgEtGkX2.mjs');
19883
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-D8Tbz32G.mjs');
19738
19884
  await awaitClaudeVersionReady();
19739
19885
  } catch {
19740
19886
  }
@@ -19931,7 +20077,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
19931
20077
  const PING_TIMEOUT_MS = 15e3;
19932
20078
  const POST_RECONNECT_GRACE_MS = 2e4;
19933
20079
  const RECONNECT_JITTER_MS = 2500;
19934
- const { WorkflowScheduler } = await import('./scheduler-DaFZY2ob.mjs');
20080
+ const { WorkflowScheduler } = await import('./scheduler-D1JpZ5m8.mjs');
19935
20081
  const workflowScheduler = new WorkflowScheduler({
19936
20082
  projectRoots: () => {
19937
20083
  const dirs = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import { j as resolveProjectRoot, D as listWorkflows, E as isWorkflowEnabled, F as workflowCrons, y as runWorkflow, G as cronMatches } from './run-C3Fcbk-H.mjs';
1
+ import { j as resolveProjectRoot, D as listWorkflows, E as isWorkflowEnabled, F as workflowCrons, y as runWorkflow, G as cronMatches } from './run-DjThStDG.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-D7fIWAgz.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-DINv6746.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-D7fIWAgz.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-DINv6746.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-D7fIWAgz.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-DINv6746.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-D7fIWAgz.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-DINv6746.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-D7fIWAgz.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-DINv6746.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, J as loadMachineContext, K as buildMachineInstructions, L as machineToolsForRole, M as buildMachineTools } from './run-C3Fcbk-H.mjs';
1
+ import { R as READ_ONLY_TOOLS, J as loadMachineContext, K as buildMachineInstructions, L as machineToolsForRole, M as buildMachineTools } from './run-DjThStDG.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';