svamp-cli 0.2.308 → 0.2.310

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 (29) hide show
  1. package/dist/{adminCommands-CtecEU3a.mjs → adminCommands-BcH3OlSL.mjs} +2 -2
  2. package/dist/{agentCommands-DEJpgdgc.mjs → agentCommands-9GHpj3mp.mjs} +6 -6
  3. package/dist/{auth-BTodo5Et.mjs → auth-CuN-K8i9.mjs} +2 -2
  4. package/dist/{cli-Ko4XpjRo.mjs → cli-Co_GXbrT.mjs} +70 -70
  5. package/dist/cli.mjs +3 -3
  6. package/dist/{commands-B-u0fICz.mjs → commands-B0cRa6cc.mjs} +3 -3
  7. package/dist/{commands-D3Nu5cYE.mjs → commands-D-RsuMqs.mjs} +3 -2
  8. package/dist/{commands-DA7rAZqn.mjs → commands-DBiIsRQ_.mjs} +2 -2
  9. package/dist/{commands-DfbOYRLs.mjs → commands-DiuHHJ6r.mjs} +3 -3
  10. package/dist/{commands-dBiQcDJ1.mjs → commands-DrVsg0Q1.mjs} +23 -11
  11. package/dist/{commands-B2ARVwag.mjs → commands-DuLlH97k.mjs} +3 -3
  12. package/dist/{commands-DKOYCcmo.mjs → commands-Dx4S6f_G.mjs} +2 -2
  13. package/dist/{commands-GNplzSQX.mjs → commands-Eo0kocpm.mjs} +37 -6
  14. package/dist/{fleet-CXixARxe.mjs → fleet-CFpET-T5.mjs} +3 -3
  15. package/dist/{frpc-DV_IIYpM.mjs → frpc-DxIjZ8o_.mjs} +5 -2
  16. package/dist/{headlessCli-DfkgeGnQ.mjs → headlessCli-BIY-YePw.mjs} +3 -3
  17. package/dist/index.mjs +2 -2
  18. package/dist/{notifyCommands-BUE_-OCX.mjs → notifyCommands-DldLQlDU.mjs} +2 -2
  19. package/dist/package-BKmrvBsb.mjs +64 -0
  20. package/dist/{pinnedClaudeCode-BaMR97BE.mjs → pinnedClaudeCode-zDzkOTi2.mjs} +1 -1
  21. package/dist/{rpc-BjKmyKdA.mjs → rpc-BVw8Tv9l.mjs} +2 -2
  22. package/dist/{rpc-wL-45Raf.mjs → rpc-D7_Gj0Kt.mjs} +2 -2
  23. package/dist/{run-VQIHMUdo.mjs → run-CqO23ro_.mjs} +420 -110
  24. package/dist/{run-ofbo9uzA.mjs → run-DLk7NGih.mjs} +2 -2
  25. package/dist/{scheduler-Dia9Akaw.mjs → scheduler-q7Refevo.mjs} +5 -5
  26. package/dist/{serveCommands-DehmO1cO.mjs → serveCommands-BJjvhDxp.mjs} +19 -12
  27. package/dist/{sideband-CGiNKfNE.mjs → sideband-Dv3qNlQN.mjs} +2 -2
  28. package/package.json +2 -2
  29. package/dist/package-GdCOuQCx.mjs +0 -64
@@ -18,9 +18,9 @@ import os, { homedir as homedir$1, platform } from 'node:os';
18
18
  import { join as join$1, extname, resolve, sep, basename, dirname } from 'node:path';
19
19
  import { EventEmitter } from 'node:events';
20
20
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
21
+ import { promisify as promisify$1 } from 'node:util';
21
22
  import { createInterface } from 'node:readline';
22
23
  import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, readdir, stat, readFile as readFile$1, rename as rename$1 } from 'node:fs/promises';
23
- import { promisify as promisify$1 } from 'node:util';
24
24
  import { parse, stringify } from 'yaml';
25
25
 
26
26
  let connectToServerFn = null;
@@ -2172,6 +2172,61 @@ function killOrphanedCaddy(log) {
2172
2172
  } catch {
2173
2173
  }
2174
2174
  }
2175
+ function reapOrphanedManagedProcesses(pidDir, log) {
2176
+ if (process.platform === "win32") return;
2177
+ let files;
2178
+ try {
2179
+ files = fs.readdirSync(pidDir).filter((f) => f.endsWith(".pid"));
2180
+ } catch {
2181
+ return;
2182
+ }
2183
+ if (!files.length) return;
2184
+ const cmdByPid = /* @__PURE__ */ new Map();
2185
+ try {
2186
+ const out = execSync("ps -axo pid=,command=", { encoding: "utf-8" });
2187
+ for (const line of out.split("\n")) {
2188
+ const m = /^\s*(\d+)\s+(.*)$/.exec(line);
2189
+ if (m) cmdByPid.set(m[1], m[2]);
2190
+ }
2191
+ } catch {
2192
+ }
2193
+ let reaped = 0;
2194
+ for (const f of files) {
2195
+ const full = path.join(pidDir, f);
2196
+ let rec = {};
2197
+ try {
2198
+ rec = JSON.parse(fs.readFileSync(full, "utf-8"));
2199
+ } catch {
2200
+ }
2201
+ const pid = Number(rec.pid);
2202
+ if (pid > 0 && pid !== process.pid) {
2203
+ const liveCmd = cmdByPid.get(String(pid));
2204
+ const base = rec.command ? path.basename(rec.command) : "";
2205
+ const args = Array.isArray(rec.args) ? rec.args : [];
2206
+ const argOk = args.length === 0 || args.some((a) => a && liveCmd?.includes(a));
2207
+ if (liveCmd && base && liveCmd.includes(base) && argOk) {
2208
+ try {
2209
+ process.kill(-pid, "SIGKILL");
2210
+ reaped++;
2211
+ } catch {
2212
+ try {
2213
+ process.kill(pid, "SIGKILL");
2214
+ reaped++;
2215
+ } catch {
2216
+ }
2217
+ }
2218
+ log(`Reaped orphaned managed process pid=${pid} (port ${rec.port ?? "?"}) from a prior daemon crash (#0758).`);
2219
+ } else if (liveCmd) {
2220
+ log(`Managed pidfile ${f}: live pid=${pid} command does not match recorded '${rec.command} ${args.join(" ")}' \u2014 NOT reaping (likely PID reuse); dropping stale pidfile.`);
2221
+ }
2222
+ }
2223
+ try {
2224
+ fs.unlinkSync(full);
2225
+ } catch {
2226
+ }
2227
+ }
2228
+ if (reaped) log(`Reaped ${reaped} orphaned managed process(es) on startup (#0758).`);
2229
+ }
2175
2230
  const MOUNT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
2176
2231
  function validateMountName(name) {
2177
2232
  if (!name || name.length > 128) {
@@ -2196,13 +2251,19 @@ class ServeManager {
2196
2251
  auth = null;
2197
2252
  /** Live child processes for managed mounts. Keyed by mount name. */
2198
2253
  managedProcs = /* @__PURE__ */ new Map();
2254
+ /** #0783: per-mount 'stopping' barriers — a wake (ensureManagedRunning) awaits any pending stop
2255
+ * so it never spawns a replacement on a port the terminating process still holds (EADDRINUSE). */
2256
+ stoppingProcs = /* @__PURE__ */ new Map();
2199
2257
  /** Single timer that scans managed mounts every 30s for idle eviction. */
2200
2258
  idleTimer = null;
2201
2259
  persistFile;
2260
+ /** #0758: dir holding per-managed-mount pidfiles so a crash-orphaned process is reapable on restart. */
2261
+ managedPidDir;
2202
2262
  log;
2203
2263
  hyphaServerUrl;
2204
2264
  constructor(svampHome, logger, hyphaServerUrl) {
2205
2265
  this.persistFile = path.join(svampHome, "serve-mounts.json");
2266
+ this.managedPidDir = path.join(svampHome, "serve-managed");
2206
2267
  this.log = logger || ((msg) => console.log(`[SERVE] ${msg}`));
2207
2268
  const resolvedServerUrl = hyphaServerUrl || getHyphaServerUrl();
2208
2269
  if (!resolvedServerUrl) {
@@ -2318,10 +2379,8 @@ class ServeManager {
2318
2379
  */
2319
2380
  listMounts(sessionId) {
2320
2381
  const all = Array.from(this.mounts.values());
2321
- if (sessionId) {
2322
- return all.filter((m) => m.sessionId === sessionId);
2323
- }
2324
- return all;
2382
+ const filtered = sessionId ? all.filter((m) => m.sessionId === sessionId) : all;
2383
+ return filtered.map((m) => ({ ...m, url: this.getMountUrl(m.name) || void 0 }));
2325
2384
  }
2326
2385
  /** #0649: look up a single mount by name (for ownership checks before remove/replace). */
2327
2386
  getMount(name) {
@@ -2332,10 +2391,8 @@ class ServeManager {
2332
2391
  */
2333
2392
  getInfo() {
2334
2393
  const running = this.proxyServer != null;
2335
- const firstMount = this.mounts.values().next().value;
2336
- const firstUrl = firstMount ? this.getMountUrl(firstMount.name) : null;
2337
2394
  return {
2338
- url: firstUrl,
2395
+ url: null,
2339
2396
  port: running ? this.port : 0,
2340
2397
  authProxyPort: running ? this.port : 0,
2341
2398
  running,
@@ -2443,6 +2500,13 @@ class ServeManager {
2443
2500
  async ensureManagedRunning(name) {
2444
2501
  const mount = this.mounts.get(name);
2445
2502
  if (!mount?.process) return;
2503
+ const pendingStop = this.stoppingProcs.get(name);
2504
+ if (pendingStop) {
2505
+ try {
2506
+ await pendingStop;
2507
+ } catch {
2508
+ }
2509
+ }
2446
2510
  const handle = this.managedProcs.get(name);
2447
2511
  if (handle && handle.warmupPromise) {
2448
2512
  return handle.warmupPromise;
@@ -2474,7 +2538,10 @@ class ServeManager {
2474
2538
  child.on("exit", (code, signal) => {
2475
2539
  this.log(`Managed process '${name}' exited (code=${code}, signal=${signal})`);
2476
2540
  const h = this.managedProcs.get(name);
2477
- if (h && h.child === child) this.managedProcs.delete(name);
2541
+ if (h && h.child === child) {
2542
+ this.managedProcs.delete(name);
2543
+ this.removeManagedPidFile(name);
2544
+ }
2478
2545
  });
2479
2546
  const warmupPath = cfg.warmupPath ?? "/";
2480
2547
  const warmupTimeoutMs = cfg.warmupTimeoutMs ?? 3e4;
@@ -2500,6 +2567,7 @@ class ServeManager {
2500
2567
  warmupPromise
2501
2568
  };
2502
2569
  this.managedProcs.set(name, newHandle);
2570
+ this.writeManagedPidFile(name, child, cfg);
2503
2571
  this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
2504
2572
  return warmupPromise;
2505
2573
  }
@@ -2509,6 +2577,25 @@ class ServeManager {
2509
2577
  * that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
2510
2578
  * child.kill() if the group signal fails (e.g. pid already gone).
2511
2579
  */
2580
+ // ── #0758: managed-process pidfiles (crash-orphan reaping) ──────────────
2581
+ managedPidFile(name) {
2582
+ const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
2583
+ return path.join(this.managedPidDir, `${safe}.pid`);
2584
+ }
2585
+ writeManagedPidFile(name, child, cfg) {
2586
+ try {
2587
+ fs.mkdirSync(this.managedPidDir, { recursive: true, mode: 448 });
2588
+ const rec = { pid: child.pid ?? 0, command: cfg.command, args: cfg.args ?? [], port: cfg.port, startedAt: Date.now() };
2589
+ fs.writeFileSync(this.managedPidFile(name), JSON.stringify(rec), { mode: 384 });
2590
+ } catch {
2591
+ }
2592
+ }
2593
+ removeManagedPidFile(name) {
2594
+ try {
2595
+ fs.unlinkSync(this.managedPidFile(name));
2596
+ } catch {
2597
+ }
2598
+ }
2512
2599
  killManagedTree(child, signal) {
2513
2600
  const pid = child.pid;
2514
2601
  if (pid && pid > 0) {
@@ -2558,18 +2645,30 @@ class ServeManager {
2558
2645
  const h = this.managedProcs.get(name);
2559
2646
  if (!h) return;
2560
2647
  this.managedProcs.delete(name);
2648
+ this.removeManagedPidFile(name);
2561
2649
  const child = h.child;
2562
- this.killManagedTree(child, "SIGTERM");
2563
- await new Promise((resolve) => {
2564
- const t = setTimeout(() => {
2565
- this.killManagedTree(child, "SIGKILL");
2566
- resolve();
2567
- }, 5e3);
2568
- child.once("exit", () => {
2569
- clearTimeout(t);
2570
- resolve();
2650
+ const done = (async () => {
2651
+ if (child.exitCode !== null) return;
2652
+ this.killManagedTree(child, "SIGTERM");
2653
+ await new Promise((resolve) => {
2654
+ const onExit = () => {
2655
+ clearTimeout(t);
2656
+ clearTimeout(cap);
2657
+ resolve();
2658
+ };
2659
+ const t = setTimeout(() => {
2660
+ this.killManagedTree(child, "SIGKILL");
2661
+ }, 5e3);
2662
+ const cap = setTimeout(onExit, 8e3);
2663
+ child.once("exit", onExit);
2571
2664
  });
2572
- });
2665
+ })();
2666
+ this.stoppingProcs.set(name, done);
2667
+ try {
2668
+ await done;
2669
+ } finally {
2670
+ if (this.stoppingProcs.get(name) === done) this.stoppingProcs.delete(name);
2671
+ }
2573
2672
  this.log(`Managed process '${name}' stopped`);
2574
2673
  }
2575
2674
  /** Idle eviction loop — stops processes that have been idle longer than configured. */
@@ -2631,6 +2730,7 @@ class ServeManager {
2631
2730
  async ensureRunning() {
2632
2731
  if (this.proxyServer) return;
2633
2732
  killOrphanedCaddy((m) => this.log(m));
2733
+ reapOrphanedManagedProcesses(this.managedPidDir, (m) => this.log(m));
2634
2734
  this.port = await tryReservePort(this.persistedPort);
2635
2735
  if (this.persistedPort && this.port !== this.persistedPort) {
2636
2736
  this.log(`\u26A0 Previous serve port ${this.persistedPort} unavailable \u2014 using ${this.port}. Downstream configs referencing the old port will need updating.`);
@@ -3047,7 +3147,7 @@ Connection: close\r
3047
3147
  const mount = this.mounts.get(mountName);
3048
3148
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
3049
3149
  try {
3050
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
3150
+ const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
3051
3151
  let tunnel;
3052
3152
  tunnel = new FrpcTunnel({
3053
3153
  name: tunnelName,
@@ -3974,6 +4074,27 @@ function cronMatches(expr, date) {
3974
4074
  if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
3975
4075
  return domOk && dowOk;
3976
4076
  }
4077
+ function inZone(date, tz) {
4078
+ if (!tz) return date;
4079
+ try {
4080
+ const p = new Intl.DateTimeFormat("en-US", {
4081
+ timeZone: tz,
4082
+ hour12: false,
4083
+ year: "numeric",
4084
+ month: "2-digit",
4085
+ day: "2-digit",
4086
+ hour: "2-digit",
4087
+ minute: "2-digit"
4088
+ }).formatToParts(date).reduce((o, x) => {
4089
+ o[x.type] = x.value;
4090
+ return o;
4091
+ }, {});
4092
+ const midnight24 = p.hour === "24";
4093
+ return new Date(+p.year, +p.month - 1, +p.day + (midnight24 ? 1 : 0), midnight24 ? 0 : +p.hour, +p.minute);
4094
+ } catch {
4095
+ return date;
4096
+ }
4097
+ }
3977
4098
  function resolvePath(ctx, path) {
3978
4099
  return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
3979
4100
  }
@@ -3992,8 +4113,8 @@ function sleepSync$1(ms) {
3992
4113
  }
3993
4114
  }
3994
4115
  function withFileLock(lockPath, fn, opts) {
3995
- const deadlineMs = 50;
3996
- const staleMs = 5e3;
4116
+ const deadlineMs = opts?.deadlineMs ?? 50;
4117
+ const staleMs = opts?.staleMs ?? 5e3;
3997
4118
  const deadline = Date.now() + deadlineMs;
3998
4119
  let held = false;
3999
4120
  while (Date.now() < deadline) {
@@ -4277,12 +4398,14 @@ class ChannelStore {
4277
4398
  return this._writeChannel(channel);
4278
4399
  }
4279
4400
  remove(id) {
4280
- const p = this._path(id);
4281
- if (existsSync(p)) {
4282
- rmSync$1(p);
4283
- return true;
4284
- }
4285
- return false;
4401
+ return withFileLock(this._lock(id), () => {
4402
+ const p = this._path(id);
4403
+ if (existsSync(p)) {
4404
+ rmSync$1(p);
4405
+ return true;
4406
+ }
4407
+ return false;
4408
+ });
4286
4409
  }
4287
4410
  // #0679: setEnabled/recordCall/addCaller are read-modify-write mutators — multiple ChannelStore
4288
4411
  // instances (one per session) point at the same .svamp/channels/<id>.json, so without
@@ -4711,10 +4834,14 @@ class ChannelOutbox {
4711
4834
  /** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
4712
4835
  append(channelId, r) {
4713
4836
  this.reload();
4714
- const seq = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
4715
- this.seqByChannel.set(channelId, seq);
4716
- this.highWater.set(channelId, seq);
4717
- this._persistHighWater();
4837
+ const seq = withFileLock(`${this.seqFile}.lock`, () => {
4838
+ this._loadHighWater();
4839
+ const s = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
4840
+ this.seqByChannel.set(channelId, s);
4841
+ this.highWater.set(channelId, s);
4842
+ this._persistHighWater();
4843
+ return s;
4844
+ });
4718
4845
  const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
4719
4846
  const arr = this.byChannel.get(channelId) || [];
4720
4847
  arr.push(reply);
@@ -4796,10 +4923,16 @@ class ChannelOutbox {
4796
4923
  this.emitter.on(channelId, onReply);
4797
4924
  });
4798
4925
  }
4799
- /** Push subscription for SSE: calls onReply for each new reply addressed to `to`. */
4800
- subscribe(channelId, to, onReply) {
4926
+ /**
4927
+ * Push subscription for SSE: calls onReply for each new matching reply.
4928
+ * Same key rule as since()/wait(): correlationId-first (knowing the unguessable reply
4929
+ * key proves ownership of that conversation), identity (`to`) only as the fallback.
4930
+ * Without this, several callers sharing a `to` (e.g. caller-supplied/'anonymous'
4931
+ * senders) would receive each other's replies — the cross-caller leak class of #0786.
4932
+ */
4933
+ subscribe(channelId, to, onReply, correlationId) {
4801
4934
  const handler = (r) => {
4802
- if (r.to === to) onReply(r);
4935
+ if (correlationId ? r.correlationId === correlationId : r.to === to) onReply(r);
4803
4936
  };
4804
4937
  this.emitter.on(channelId, handler);
4805
4938
  return () => this.emitter.off(channelId, handler);
@@ -4841,6 +4974,7 @@ const SESSION_METHOD_MIN_ROLE = {
4841
4974
  archiveSession: "admin",
4842
4975
  readFile: "admin",
4843
4976
  writeFile: "admin",
4977
+ writeFileChunk: "admin",
4844
4978
  listDirectory: "admin",
4845
4979
  bash: "admin",
4846
4980
  issue: "admin",
@@ -4889,6 +5023,17 @@ const SESSION_VIEW_METHODS = /* @__PURE__ */ new Set([
4889
5023
  "reregister",
4890
5024
  "recordMeetingActivity"
4891
5025
  ]);
5026
+ const MACHINE_MEMBER_READABLE = /* @__PURE__ */ new Set([
5027
+ "getMetadata",
5028
+ "getAgentState",
5029
+ "getActivityState",
5030
+ "getMessages",
5031
+ "getLatestMessages",
5032
+ "getMessageCount",
5033
+ "getSharing",
5034
+ "listChannels",
5035
+ "registerListener"
5036
+ ]);
4892
5037
  function sessionMethodMinRole(method) {
4893
5038
  const mapped = SESSION_METHOD_MIN_ROLE[method];
4894
5039
  if (mapped) return mapped;
@@ -5231,11 +5376,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5231
5376
  const rpc = handlers.getSessionRPCHandlers?.(sid);
5232
5377
  if (!rpc) continue;
5233
5378
  if (!hasMachineAccess && !await hasExplicitSessionAccess(rpc, "view", context)) continue;
5379
+ const readCtx = hasMachineAccess ? void 0 : context;
5234
5380
  try {
5235
5381
  const [metaResult, stateResult, activity] = await Promise.all([
5236
- rpc.getMetadata(context),
5237
- rpc.getAgentState(context),
5238
- rpc.getActivityState(context).catch(() => ({
5382
+ rpc.getMetadata(readCtx),
5383
+ rpc.getAgentState(readCtx),
5384
+ rpc.getActivityState(readCtx).catch(() => ({
5239
5385
  active: false,
5240
5386
  thinking: false,
5241
5387
  time: Date.now()
@@ -5251,7 +5397,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5251
5397
  thinking: activity.thinking ?? false,
5252
5398
  activeAt: activity.time || Date.now()
5253
5399
  });
5254
- } catch {
5400
+ } catch (err) {
5401
+ console.debug(
5402
+ `[HYPHA MACHINE] getSessions: skipped session ${sid} (${hasMachineAccess ? "machine-access" : "session-shared"}): ${err?.message ?? err}`
5403
+ );
5255
5404
  }
5256
5405
  }
5257
5406
  return sessions;
@@ -5271,8 +5420,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5271
5420
  throw new Error(`Session ${sessionId} not found on this machine`);
5272
5421
  }
5273
5422
  const requiredRole = sessionMethodMinRole(method);
5423
+ let machineAuthorized = false;
5274
5424
  try {
5275
5425
  authorizeRequest(context, currentMetadata.sharing, requiredRole);
5426
+ machineAuthorized = true;
5276
5427
  } catch (machineErr) {
5277
5428
  if (!await hasExplicitSessionAccess(rpc, requiredRole, context)) throw machineErr;
5278
5429
  }
@@ -5280,9 +5431,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5280
5431
  if (typeof handler !== "function") {
5281
5432
  throw new Error(`Unknown session method: ${method}`);
5282
5433
  }
5434
+ const dispatchCtx = machineAuthorized && MACHINE_MEMBER_READABLE.has(method) ? void 0 : context;
5283
5435
  const paramNames = getParamNames(handler);
5284
5436
  const callArgs = paramNames.map(
5285
- (name) => name === "context" ? context : kwargs?.[name] ?? void 0
5437
+ (name) => name === "context" ? dispatchCtx : kwargs?.[name] ?? void 0
5286
5438
  );
5287
5439
  return await handler(...callArgs);
5288
5440
  },
@@ -5296,14 +5448,16 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5296
5448
  if (!rpc) {
5297
5449
  throw new Error(`Session ${sessionId} not found on this machine`);
5298
5450
  }
5451
+ let machineAuthorized = false;
5299
5452
  try {
5300
5453
  authorizeRequest(context, currentMetadata.sharing, "view");
5454
+ machineAuthorized = true;
5301
5455
  } catch (machineErr) {
5302
5456
  if (!await hasExplicitSessionAccess(rpc, "view", context)) {
5303
5457
  throw machineErr;
5304
5458
  }
5305
5459
  }
5306
- return await rpc.registerListener(callback, context);
5460
+ return await rpc.registerListener(callback, machineAuthorized ? void 0 : context);
5307
5461
  },
5308
5462
  // Spawn a new session
5309
5463
  spawnSession: async (options, context) => {
@@ -5679,9 +5833,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5679
5833
  const { exec } = await import('child_process');
5680
5834
  const { homedir } = await import('os');
5681
5835
  return new Promise((resolve) => {
5682
- exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
5836
+ exec(command, { cwd: cwd || homedir(), timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
5683
5837
  if (err) {
5684
- resolve({ success: false, stdout: stdout || "", stderr: stderr || err.message, exitCode: err.code ?? 1 });
5838
+ const enobufs = err.code === "ENOBUFS";
5839
+ const errText = enobufs ? `output exceeded the 16 MB buffer and was truncated (the command may have completed): ${stderr || err.message}` : stderr || err.message;
5840
+ resolve({ success: false, stdout: stdout || "", stderr: errText, exitCode: err.code ?? 1 });
5685
5841
  } else {
5686
5842
  resolve({ success: true, stdout, stderr: stderr || "", exitCode: 0 });
5687
5843
  }
@@ -5692,6 +5848,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5692
5848
  /** Start a new terminal PTY session. Returns { sessionId, cols, rows }. */
5693
5849
  terminalStart: async (params = {}, context) => {
5694
5850
  authorizeRequest(context, currentMetadata.sharing, "admin");
5851
+ const terminalClient = context?.from;
5695
5852
  const pty = await getPtyModule();
5696
5853
  const { homedir: getHomedir } = await import('os');
5697
5854
  const cols = params.cols || 80;
@@ -5746,7 +5903,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5746
5903
  server.emit({
5747
5904
  type: "svamp:terminal-output",
5748
5905
  data: { type: "output", content: batch, sessionId },
5749
- to: "*"
5906
+ to: terminalClient ?? "*"
5750
5907
  });
5751
5908
  } catch {
5752
5909
  }
@@ -5765,7 +5922,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
5765
5922
  server.emit({
5766
5923
  type: "svamp:terminal-output",
5767
5924
  data: { type: "exit", content: "", sessionId, exitCode, signal },
5768
- to: "*"
5925
+ to: terminalClient ?? "*"
5769
5926
  });
5770
5927
  } catch {
5771
5928
  }
@@ -6123,7 +6280,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6123
6280
  const tunnels = handlers.tunnels;
6124
6281
  if (!tunnels) throw new Error("Tunnel management not available");
6125
6282
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
6126
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
6283
+ const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
6127
6284
  const tunnel = new FrpcTunnel({
6128
6285
  name: params.name,
6129
6286
  ports: params.ports,
@@ -6290,8 +6447,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6290
6447
  const info = sm.getInfo();
6291
6448
  const isAdmin = serveCallerTrusted(context);
6292
6449
  const mounts = (info.mounts || []).map((m) => sanitizeMountForRole(m, isAdmin));
6293
- const topUrl = isAdmin ? info.url : mounts[0]?.url ?? null;
6294
- return { ...info, url: topUrl, mounts };
6450
+ return { ...info, url: null, mounts };
6295
6451
  },
6296
6452
  /**
6297
6453
  * Aggregate frpc tunnel health for all serve mounts.
@@ -6617,7 +6773,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6617
6773
  }
6618
6774
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6619
6775
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6620
- const { toolsForRole } = await import('./sideband-CGiNKfNE.mjs');
6776
+ const { toolsForRole } = await import('./sideband-Dv3qNlQN.mjs');
6621
6777
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6622
6778
  return fmt(r2);
6623
6779
  }
@@ -6722,7 +6878,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6722
6878
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
6723
6879
  }
6724
6880
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6725
- const { queryCore } = await import('./commands-D3Nu5cYE.mjs');
6881
+ const { queryCore } = await import('./commands-D-RsuMqs.mjs');
6726
6882
  const timeout = c.reply?.timeout_sec || 120;
6727
6883
  let result;
6728
6884
  try {
@@ -7232,9 +7388,17 @@ function getRateLimitRetryConfig() {
7232
7388
  return {
7233
7389
  maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
7234
7390
  baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
7235
- capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
7391
+ capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4),
7392
+ maxElapsedMs: envInt("SVAMP_RATELIMIT_MAX_ELAPSED_MS", 36e5)
7236
7393
  };
7237
7394
  }
7395
+ function retryStreakExhausted(attempts, streakStartedAtMs, nowMs, cfg = getRateLimitRetryConfig()) {
7396
+ if (attempts >= cfg.maxRetries) return { exhausted: true, reason: "count" };
7397
+ if (cfg.maxElapsedMs > 0 && streakStartedAtMs && streakStartedAtMs > 0 && nowMs - streakStartedAtMs >= cfg.maxElapsedMs) {
7398
+ return { exhausted: true, reason: "elapsed" };
7399
+ }
7400
+ return { exhausted: false };
7401
+ }
7238
7402
  function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
7239
7403
  const a = Math.max(0, Math.floor(attempt));
7240
7404
  const expo = Math.min(a, 30);
@@ -7413,6 +7577,9 @@ async function deletePairingArtifact(am, artifactId, log) {
7413
7577
  }
7414
7578
  }
7415
7579
  async function sweepExpiredPairings(am, svampHome, log) {
7580
+ return withMintLock("*sweep*", () => _sweepExpiredPairingsUnlocked(am, svampHome, log));
7581
+ }
7582
+ async function _sweepExpiredPairingsUnlocked(am, svampHome, log) {
7416
7583
  const now = Date.now();
7417
7584
  const all = loadPairings(svampHome);
7418
7585
  const expired = all.filter((p) => p.expiresAt <= now);
@@ -7420,18 +7587,40 @@ async function sweepExpiredPairings(am, svampHome, log) {
7420
7587
  for (const p of expired) await deletePairingArtifact(am, p.artifactId, log);
7421
7588
  savePairings(all.filter((p) => p.expiresAt > now), svampHome);
7422
7589
  }
7590
+ let _lastSweepAt = 0;
7591
+ async function sweepExpiredPairingsThrottled(am, svampHome, log, minIntervalMs = 5 * 6e4) {
7592
+ const now = Date.now();
7593
+ if (now - _lastSweepAt < minIntervalMs) return;
7594
+ _lastSweepAt = now;
7595
+ await sweepExpiredPairings(am, svampHome, log);
7596
+ }
7597
+ let _pairingChain = Promise.resolve();
7598
+ function withMintLock(_session, fn) {
7599
+ const prior = _pairingChain.catch(() => {
7600
+ });
7601
+ const result = prior.then(fn);
7602
+ _pairingChain = result.catch(() => {
7603
+ });
7604
+ return result;
7605
+ }
7423
7606
  async function burnSessionPairings(am, session, svampHome, log) {
7424
- const all = loadPairings(svampHome);
7425
- const mine = all.filter((p) => p.session === session);
7426
- if (mine.length === 0) return;
7427
- for (const p of mine) await deletePairingArtifact(am, p.artifactId, log);
7428
- savePairings(all.filter((p) => p.session !== session), svampHome);
7607
+ return withMintLock(session, async () => {
7608
+ const all = loadPairings(svampHome);
7609
+ const mine = all.filter((p) => p.session === session);
7610
+ if (mine.length === 0) return;
7611
+ for (const p of mine) await deletePairingArtifact(am, p.artifactId, log);
7612
+ savePairings(all.filter((p) => p.session !== session), svampHome);
7613
+ });
7429
7614
  }
7430
7615
  async function mintPairingCode(opts) {
7431
- const { am, session, payload, log } = opts;
7616
+ const { session} = opts;
7432
7617
  const resolver = (opts.resolverBase || "https://hypha.aicell.io").replace(/\/$/, "");
7433
7618
  const ttlMs = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : DEFAULT_PAIRING_TTL_MS;
7434
- await sweepExpiredPairings(am, opts.svampHome, log).catch(() => {
7619
+ return withMintLock(session, () => _mintPairingCodeLocked(opts, resolver, ttlMs));
7620
+ }
7621
+ async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
7622
+ const { am, session, payload, log } = opts;
7623
+ await _sweepExpiredPairingsUnlocked(am, opts.svampHome, log).catch(() => {
7435
7624
  });
7436
7625
  const now = Date.now();
7437
7626
  const existing = loadPairings(opts.svampHome).find((p) => p.session === session && p.expiresAt > now + 3e4);
@@ -8072,6 +8261,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8072
8261
  const channelOutbox = new ChannelOutbox(initialMetadata.path);
8073
8262
  const outpostCoordinator = new OutpostCoordinator();
8074
8263
  outpostCoordinator.startSweeper();
8264
+ void (async () => {
8265
+ try {
8266
+ const am = await server.getService("public/artifact-manager");
8267
+ if (am) await sweepExpiredPairingsThrottled(am, void 0, (m) => console.log(m));
8268
+ } catch {
8269
+ }
8270
+ })();
8075
8271
  const outpostProjectDir = () => metadata.path || process.cwd();
8076
8272
  const announceOutpost = (conn) => {
8077
8273
  const who = conn.label || conn.machine;
@@ -8720,7 +8916,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8720
8916
  const ownerCtx = ownerEmail ? { user: { email: ownerEmail, id: ownerEmail } } : void 0;
8721
8917
  try {
8722
8918
  if (c.system && c.action?.kind !== "agent") {
8723
- const role = ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
8919
+ const role = r.sender.verified && ownerEmail && r.sender.name.toLowerCase() === ownerEmail.toLowerCase() ? "owner" : "collaborator";
8724
8920
  const envelope = renderMessage(c, {
8725
8921
  sender: r.sender,
8726
8922
  body: { message: params.message },
@@ -8847,7 +9043,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8847
9043
  });
8848
9044
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
8849
9045
  const cursor = Math.max(0, Number(params.cursor) || 0);
8850
- const waitMs = Math.min(Math.max(0, Number(params.wait ?? 25) * 1e3), 6e4);
9046
+ const waitSecRaw = Number(params.wait);
9047
+ const waitSec = Number.isFinite(waitSecRaw) ? waitSecRaw : 25;
9048
+ const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
8851
9049
  const out = await channelOutbox.wait(c.id, cursor, r.sender.name, waitMs, params.correlationId);
8852
9050
  return { ok: true, replies: out.replies, cursor: out.cursor };
8853
9051
  },
@@ -8958,6 +9156,12 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8958
9156
  await callbacks.onWriteFile(path, content);
8959
9157
  return { success: true };
8960
9158
  },
9159
+ writeFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast, context) => {
9160
+ authorizeRequest(context, metadata.sharing, "admin");
9161
+ if (!callbacks.onWriteFileChunk) throw new Error("writeFileChunk not supported");
9162
+ await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
9163
+ return { success: true };
9164
+ },
8961
9165
  listDirectory: async (path, context) => {
8962
9166
  authorizeRequest(context, metadata.sharing, "admin");
8963
9167
  if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
@@ -12291,15 +12495,30 @@ function codexAppServerAvailable() {
12291
12495
  return null;
12292
12496
  }
12293
12497
  }
12498
+ const _execFileAsync = promisify$1(execFile$1);
12294
12499
  let _steerSupport = null;
12295
- function codexSupportsSteerInstalled() {
12296
- if (_steerSupport !== null) return _steerSupport;
12297
- try {
12298
- _steerSupport = codexSupportsSteer(parseCodexVersion(execSync$1("codex --version", { encoding: "utf8" }).trim()));
12299
- } catch {
12300
- _steerSupport = false;
12301
- }
12302
- return _steerSupport;
12500
+ let _steerProbedAt = 0;
12501
+ let _steerProbe = null;
12502
+ const _STEER_TTL_MS = 3e4;
12503
+ const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
12504
+ function _steerFresh() {
12505
+ return _steerSupport !== null && Date.now() - _steerProbedAt < _STEER_TTL_MS;
12506
+ }
12507
+ async function codexSupportsSteerInstalledAsync() {
12508
+ if (_steerFresh()) return _steerSupport;
12509
+ if (_steerProbe) return _steerProbe;
12510
+ _steerProbe = (async () => {
12511
+ try {
12512
+ const { stdout } = await _execFileAsync("codex", ["--version"], { encoding: "utf8" });
12513
+ _steerSupport = codexSupportsSteer(parseCodexVersion(stdout.trim()));
12514
+ } catch {
12515
+ _steerSupport = false;
12516
+ }
12517
+ _steerProbedAt = Date.now();
12518
+ _steerProbe = null;
12519
+ return _steerSupport;
12520
+ })();
12521
+ return _steerProbe;
12303
12522
  }
12304
12523
  class CodexAppServerClient {
12305
12524
  constructor(opts) {
@@ -12474,7 +12693,7 @@ class CodexAppServerClient {
12474
12693
  */
12475
12694
  async injectInput(prompt, _o) {
12476
12695
  if (!this._threadId || !this.pendingTurn || !this._turnId) return false;
12477
- if (!codexSupportsSteerInstalled()) return false;
12696
+ if (!await codexSupportsSteerInstalledAsync()) return false;
12478
12697
  const params = {
12479
12698
  threadId: this._threadId,
12480
12699
  expectedTurnId: this._turnId,
@@ -12529,17 +12748,16 @@ class CodexAppServerClient {
12529
12748
  // ── JSON-RPC plumbing ──────────────────────────────────────────────────────
12530
12749
  request(method, params, timeoutMs) {
12531
12750
  const id = this.nextId++;
12751
+ const effTimeout = timeoutMs ?? this.opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
12532
12752
  return new Promise((resolve, reject) => {
12533
12753
  this.pending.set(id, { resolve, reject, method });
12534
- if (timeoutMs) {
12535
- const t = setTimeout(() => {
12536
- if (this.pending.has(id)) {
12537
- this.pending.delete(id);
12538
- reject(new Error(`codex ${method} timed out after ${timeoutMs}ms`));
12539
- }
12540
- }, timeoutMs);
12541
- t.unref?.();
12542
- }
12754
+ const t = setTimeout(() => {
12755
+ if (this.pending.has(id)) {
12756
+ this.pending.delete(id);
12757
+ reject(new Error(`codex ${method} timed out after ${effTimeout}ms`));
12758
+ }
12759
+ }, effTimeout);
12760
+ t.unref?.();
12543
12761
  this.write({ jsonrpc: "2.0", id, method, params });
12544
12762
  });
12545
12763
  }
@@ -14839,8 +15057,8 @@ function isWorkflowEnabled(wf) {
14839
15057
  function workflowSteps(wf) {
14840
15058
  return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
14841
15059
  }
14842
- function workflowCrons(wf) {
14843
- return (wf.on?.schedule || []).map((s) => s?.cron).filter((c) => !!c);
15060
+ function workflowSchedules(wf) {
15061
+ return (wf.on?.schedule || []).filter((s) => !!s?.cron);
14844
15062
  }
14845
15063
  function workflowsDir(projectRoot) {
14846
15064
  return join$1(projectRoot, ".svamp", "workflows");
@@ -14860,7 +15078,11 @@ function normalizeOn(on) {
14860
15078
  }
14861
15079
  const out = {};
14862
15080
  if (Array.isArray(on.schedule)) {
14863
- const sched = on.schedule.map((s) => ({ cron: String(s?.cron ?? s ?? "") })).filter((s) => s.cron);
15081
+ const sched = on.schedule.map((s) => {
15082
+ const entry = { cron: String(s?.cron ?? s ?? "") };
15083
+ if (s && typeof s === "object" && s.tz != null && String(s.tz).trim()) entry.tz = String(s.tz).trim();
15084
+ return entry;
15085
+ }).filter((s) => s.cron);
14864
15086
  if (sched.length) out.schedule = sched;
14865
15087
  } else if (typeof on.schedule === "string" && on.schedule.trim()) {
14866
15088
  out.schedule = [{ cron: on.schedule.trim() }];
@@ -14908,7 +15130,7 @@ function serializeWorkflow(wf) {
14908
15130
  if (wf.session) clean.session = wf.session;
14909
15131
  if (wf.enabled === false) clean.enabled = false;
14910
15132
  const on = {};
14911
- if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => ({ cron: s.cron }));
15133
+ if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => s.tz ? { cron: s.cron, tz: s.tz } : { cron: s.cron });
14912
15134
  if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
14913
15135
  if (wf.on?.channel) on.channel = wf.on.channel;
14914
15136
  if (wf.on?.issue?.length) on.issue = wf.on.issue;
@@ -15022,6 +15244,7 @@ function runsFile(projectRoot, workflow) {
15022
15244
  const safe = workflow.replace(/[^\w.-]+/g, "_");
15023
15245
  return join$1(runsDir(projectRoot), `${safe}.jsonl`);
15024
15246
  }
15247
+ let _runSeq = 0;
15025
15248
  function shortRunId(seed) {
15026
15249
  let h = 2166136261;
15027
15250
  for (let i = 0; i < seed.length; i++) {
@@ -15174,7 +15397,7 @@ function escalateWorkflowFailure(projectRoot, wf, run) {
15174
15397
  const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
15175
15398
  if (open.length > 0) return void 0;
15176
15399
  const failed = run.steps.find((s) => s.timedOut || s.exitCode !== 0 && s.exitCode !== null) || run.steps[run.steps.length - 1];
15177
- const failCause = run.error ? `runner error: ${run.error}` : failed?.timedOut ? `step timed out after ${STEP_TIMEOUT_MS / 1e3}s` : `step exited with code ${failed?.exitCode ?? "null (killed/signal)"}`;
15400
+ const failCause = run.error ? `runner error: ${run.error}` : failed?.timedOut ? `step timed out after ${stepTimeoutMs() / 1e3}s` : `step exited with code ${failed?.exitCode ?? "null (killed/signal)"}`;
15178
15401
  const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
15179
15402
  const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
15180
15403
  const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
@@ -15247,7 +15470,7 @@ async function runWorkflowInner(projectRoot, wf, opts) {
15247
15470
  });
15248
15471
  const exec = opts.execStep || defaultExecStep;
15249
15472
  const start = now();
15250
- const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}`);
15473
+ const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}:${_runSeq++}`);
15251
15474
  const env = wf.session ? { ...process.env, SVAMP_SESSION_ID: wf.session } : process.env;
15252
15475
  const run = {
15253
15476
  id,
@@ -15358,7 +15581,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
15358
15581
  } catch {
15359
15582
  return [];
15360
15583
  }
15361
- const matches = workflows.filter((wf) => wf.on?.idle === true && isWorkflowEnabled(wf));
15584
+ const matches = workflows.filter(
15585
+ (wf) => wf.on?.idle === true && isWorkflowEnabled(wf) && (!wf.session || wf.session === sessionId)
15586
+ );
15362
15587
  return matches.map((wf) => {
15363
15588
  deps.log?.(`[workflow] idle fired "${wf.name}" in ${root} for session ${sessionId}`);
15364
15589
  return runWorkflow(root, wf, {
@@ -16114,6 +16339,24 @@ async function readSessionFileBase64(resolvedPath) {
16114
16339
  const buffer = await fs$1.readFile(resolvedPath);
16115
16340
  return buffer.toString("base64");
16116
16341
  }
16342
+ async function writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast) {
16343
+ const resolvedPath = resolve$1(directory, path);
16344
+ if (sessionMetadata?.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
16345
+ throw new Error("Path outside working directory");
16346
+ }
16347
+ const safeUploadId = String(uploadId).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "upload";
16348
+ const tempPath = `${resolvedPath}.svamp-upload-${safeUploadId}.part`;
16349
+ const buffer = Buffer.from(content || "", "base64");
16350
+ if (chunkIndex === 0) {
16351
+ await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
16352
+ await fs$1.writeFile(tempPath, buffer);
16353
+ } else {
16354
+ await fs$1.appendFile(tempPath, buffer);
16355
+ }
16356
+ if (isLast) {
16357
+ await fs$1.rename(tempPath, resolvedPath);
16358
+ }
16359
+ }
16117
16360
 
16118
16361
  const __filename$1 = fileURLToPath(import.meta.url);
16119
16362
  const __dirname$1 = dirname$1(__filename$1);
@@ -16989,7 +17232,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
16989
17232
  const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
16990
17233
  const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
16991
17234
  const nextMax = Math.max(curMax * 2, iter + 10);
16992
- const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-resume ${sessionId} --max ${nextMax}`;
17235
+ const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 did NOT finish after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-extend ${sessionId} --reason "<why more turns are needed>" --turns ${Math.max(10, nextMax - iter)}`;
16993
17236
  sessionService.pushMessage(
16994
17237
  s.phase === "gave_up" ? { type: "message", message: msg, level: "warning" } : { type: "message", message: msg },
16995
17238
  "event"
@@ -17058,7 +17301,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
17058
17301
  maxExtensions
17059
17302
  });
17060
17303
  if (!grant.granted) {
17061
- sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-resume ${sessionId} --max <N>`, level: "warning" }, "event");
17304
+ sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Extension refused \u2014 this loop already used all ${grant.max} allowed extension${grant.max === 1 ? "" : "s"} (${grant.used}/${grant.max}). A hard cap is a hard cap; do the remaining work or raise the limit manually: svamp session loop-extend ${sessionId} --reason "<why>" --turns <N>`, level: "warning" }, "event");
17062
17305
  logger.log(`[svampConfig] loop-extend refused (bounded ${grant.used}/${grant.max})`);
17063
17306
  } else {
17064
17307
  const wasStopped = existing.active === false || existing.phase === "done" || existing.phase === "gave_up" || existing.phase === "cancelled";
@@ -17612,7 +17855,7 @@ async function startDaemon(options) {
17612
17855
  try {
17613
17856
  const dir = loadSessionIndex()[sessionId]?.directory;
17614
17857
  if (!dir) return;
17615
- const { reconcileServiceLinks } = await import('./agentCommands-DEJpgdgc.mjs');
17858
+ const { reconcileServiceLinks } = await import('./agentCommands-9GHpj3mp.mjs');
17616
17859
  const configPath = getSvampConfigPath(dir, sessionId);
17617
17860
  const config = readSvampConfig(configPath);
17618
17861
  const entries = Array.from(urls.entries());
@@ -17630,7 +17873,7 @@ async function startDaemon(options) {
17630
17873
  }
17631
17874
  }
17632
17875
  async function createExposedTunnel(spec) {
17633
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
17876
+ const { FrpcTunnel } = await import('./frpc-DxIjZ8o_.mjs');
17634
17877
  const tunnel = new FrpcTunnel({
17635
17878
  name: spec.name,
17636
17879
  ports: spec.ports,
@@ -17658,7 +17901,7 @@ async function startDaemon(options) {
17658
17901
  ensureAutoInstalledCommands(logger);
17659
17902
  (async () => {
17660
17903
  try {
17661
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BaMR97BE.mjs');
17904
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
17662
17905
  beginClaudeVersionReconcile((msg) => logger.log(msg));
17663
17906
  } catch (e) {
17664
17907
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -17941,8 +18184,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
17941
18184
  if (RATELIMIT_CFG.maxRetries <= 0) return false;
17942
18185
  if (trackedSession?.stopped) return false;
17943
18186
  if (currentTurnMessage === void 0) return false;
17944
- if (rateLimitRetryCount >= RATELIMIT_CFG.maxRetries) {
17945
- logger.log(`[Session ${sessionId}] Rate-limit retries exhausted (${rateLimitRetryCount}/${RATELIMIT_CFG.maxRetries}) \u2014 surfacing error`);
18187
+ const nowTs = Date.now();
18188
+ if (rateLimitStreakStartedAt === 0) {
18189
+ rateLimitStreakStartedAt = nowTs;
18190
+ sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: nowTs };
18191
+ }
18192
+ const budget = retryStreakExhausted(rateLimitRetryCount, rateLimitStreakStartedAt, nowTs, RATELIMIT_CFG);
18193
+ if (budget.exhausted) {
18194
+ const elapsedMin = Math.round((nowTs - rateLimitStreakStartedAt) / 6e4);
18195
+ logger.log(`[Session ${sessionId}] Rate-limit retries exhausted (${budget.reason}: ${rateLimitRetryCount}/${RATELIMIT_CFG.maxRetries} attempts, ${elapsedMin}m elapsed) \u2014 surfacing error`);
18196
+ resetRateLimitStreak();
17946
18197
  return false;
17947
18198
  }
17948
18199
  const attempt = rateLimitRetryCount++;
@@ -18410,18 +18661,18 @@ ${parts.join("\n")}`);
18410
18661
  enqueueLoopMessage(push, label);
18411
18662
  if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
18412
18663
  } else {
18413
- writeGoalLoopState(directory, sessionId, { active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: decision.reason, holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18664
+ writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: decision.reason, holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18414
18665
  logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
18415
18666
  checkSvampConfig?.();
18416
18667
  }
18417
18668
  } else {
18418
- writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18669
+ writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18419
18670
  checkSvampConfig?.();
18420
18671
  }
18421
18672
  } catch (e) {
18422
18673
  logger.log(`[Session ${sessionId}] verifyGoalCompletion error \u2014 failing open (done): ${e?.message || e}`);
18423
18674
  try {
18424
- writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
18675
+ writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now() });
18425
18676
  checkSvampConfig?.();
18426
18677
  } catch {
18427
18678
  }
@@ -18479,8 +18730,16 @@ ${parts.join("\n")}`);
18479
18730
  const RATELIMIT_CFG = getRateLimitRetryConfig();
18480
18731
  let currentTurnMessage;
18481
18732
  let rateLimitRetryCount = 0;
18733
+ let rateLimitStreakStartedAt = Number(sessionMetadata?.rateLimitStreakStartedAt) || 0;
18482
18734
  let rateLimitRetryTimer = null;
18483
18735
  let rateLimitRetryScheduled = false;
18736
+ const resetRateLimitStreak = () => {
18737
+ rateLimitRetryCount = 0;
18738
+ rateLimitStreakStartedAt = 0;
18739
+ if (sessionMetadata?.rateLimitStreakStartedAt) {
18740
+ sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: 0 };
18741
+ }
18742
+ };
18484
18743
  let checkSvampConfig;
18485
18744
  let cleanupSvampConfig;
18486
18745
  const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
@@ -18895,7 +19154,7 @@ ${parts.join("\n")}`);
18895
19154
  "event"
18896
19155
  );
18897
19156
  }
18898
- if (!msg.is_error) rateLimitRetryCount = 0;
19157
+ if (!msg.is_error) resetRateLimitStreak();
18899
19158
  if (msg.session_id) {
18900
19159
  claudeResumeId = msg.session_id;
18901
19160
  if (sessionMetadata.claudeSessionId !== msg.session_id) {
@@ -18961,7 +19220,7 @@ ${parts.join("\n")}`);
18961
19220
  auto_resumes: prog.auto_resumes
18962
19221
  });
18963
19222
  const extNote = summarizeExtensions(ls.extensions, maxExt);
18964
- const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" \xB7 or set a new cap: svamp session loop-resume ${sessionId} --max <N>` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
19223
+ const resumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
18965
19224
  const detail = `${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${extNote ? ` [${extNote}]` : ""}. ${resumeHint}`;
18966
19225
  sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${detail}`, level: "warning" }, "event");
18967
19226
  checkSvampConfig?.();
@@ -19467,7 +19726,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19467
19726
  return;
19468
19727
  }
19469
19728
  currentTurnMessage = text;
19470
- rateLimitRetryCount = 0;
19729
+ resetRateLimitStreak();
19471
19730
  if (rateLimitRetryTimer) {
19472
19731
  clearTimeout(rateLimitRetryTimer);
19473
19732
  rateLimitRetryTimer = null;
@@ -19491,7 +19750,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19491
19750
  clearTimeout(rateLimitRetryTimer);
19492
19751
  rateLimitRetryTimer = null;
19493
19752
  }
19494
- rateLimitRetryCount = 0;
19753
+ resetRateLimitStreak();
19495
19754
  if (claudeProcess && !claudeProcess.killed) {
19496
19755
  try {
19497
19756
  const interruptMsg = JSON.stringify({ type: "control_request", request: { type: "interrupt" } });
@@ -19848,11 +20107,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19848
20107
  });
19849
20108
  },
19850
20109
  onIssue: async (params) => {
19851
- const { issueRpc } = await import('./rpc-wL-45Raf.mjs');
20110
+ const { issueRpc } = await import('./rpc-D7_Gj0Kt.mjs');
19852
20111
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
19853
20112
  },
19854
20113
  onWorkflow: async (params) => {
19855
- const { workflowRpc } = await import('./rpc-BjKmyKdA.mjs');
20114
+ const { workflowRpc } = await import('./rpc-BVw8Tv9l.mjs');
19856
20115
  return workflowRpc(params?.cwd || directory, params || {});
19857
20116
  },
19858
20117
  onRipgrep: async (args, cwd) => {
@@ -19884,6 +20143,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19884
20143
  await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
19885
20144
  await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
19886
20145
  },
20146
+ onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
20147
+ await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
20148
+ },
19887
20149
  onListDirectory: async (path) => {
19888
20150
  const resolvedDir = resolve$1(directory, path || ".");
19889
20151
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -19893,8 +20155,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19893
20155
  return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
19894
20156
  },
19895
20157
  onGetDirectoryTree: async (treePath, maxDepth) => {
20158
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
20159
+ const MAX_TREE_DEPTH = 8;
20160
+ const MAX_TREE_NODES = 2e4;
20161
+ const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
20162
+ let treeNodeCount = 0;
19896
20163
  async function buildTree(p, name, depth) {
19897
20164
  try {
20165
+ if (treeNodeCount >= MAX_TREE_NODES) return null;
20166
+ treeNodeCount++;
19898
20167
  const stats = await fs$1.stat(p);
19899
20168
  const node = {
19900
20169
  name,
@@ -19903,11 +20172,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19903
20172
  size: stats.size,
19904
20173
  modified: stats.mtime.getTime()
19905
20174
  };
19906
- if (stats.isDirectory() && depth < maxDepth) {
20175
+ if (stats.isDirectory() && depth < effectiveMaxDepth) {
19907
20176
  const entries = await fs$1.readdir(p, { withFileTypes: true });
19908
20177
  const children = [];
19909
20178
  await Promise.all(entries.map(async (entry) => {
19910
20179
  if (entry.isSymbolicLink()) return;
20180
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
20181
+ if (treeNodeCount >= MAX_TREE_NODES) return;
19911
20182
  const childPath = join(p, entry.name);
19912
20183
  const childNode = await buildTree(childPath, entry.name, depth + 1);
19913
20184
  if (childNode) children.push(childNode);
@@ -19978,7 +20249,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19978
20249
  userMessagePending = true;
19979
20250
  turnInitiatedByUser = true;
19980
20251
  currentTurnMessage = next.text;
19981
- rateLimitRetryCount = 0;
20252
+ resetRateLimitStreak();
19982
20253
  if (rateLimitRetryTimer) {
19983
20254
  clearTimeout(rateLimitRetryTimer);
19984
20255
  rateLimitRetryTimer = null;
@@ -20257,6 +20528,30 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20257
20528
  };
20258
20529
  sessionService.updateMetadata(sessionMetadata);
20259
20530
  };
20531
+ const drainQueueHeadIfIdle = () => {
20532
+ if (acpStopped || !acpBackendReady) return;
20533
+ if (sessionMetadata.lifecycleState !== "idle") return;
20534
+ const queue = sessionMetadata.messageQueue;
20535
+ if (!queue || queue.length === 0) return;
20536
+ const next = queue[0];
20537
+ const remaining = queue.slice(1);
20538
+ sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
20539
+ sessionService.updateMetadata(sessionMetadata);
20540
+ if (typeof next.inboxHop === "number") {
20541
+ writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
20542
+ }
20543
+ sessionService.markInboxHandled(next.id);
20544
+ if (!next.alreadyStored) sessionService.pushMessage(next.displayText || next.text, "user");
20545
+ sessionService.sendKeepAlive(true);
20546
+ agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
20547
+ logger.error(`[${agentName} Session ${sessionId}] Error draining queued message after inject miss: ${err?.message ?? err}`);
20548
+ if (!acpStopped) {
20549
+ sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
20550
+ sessionService.updateMetadata(sessionMetadata);
20551
+ sessionService.sendSessionEnd();
20552
+ }
20553
+ });
20554
+ };
20260
20555
  const injectFn = agentBackend.injectInput;
20261
20556
  if (typeof injectFn === "function") {
20262
20557
  Promise.resolve(injectFn.call(agentBackend, text)).then((injected) => {
@@ -20265,10 +20560,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20265
20560
  } else {
20266
20561
  logger.log(`[${agentName} Session ${sessionId}] No active turn to inject \u2014 queuing message`);
20267
20562
  enqueueBusy();
20563
+ drainQueueHeadIfIdle();
20268
20564
  }
20269
20565
  }).catch((err) => {
20270
20566
  logger.error(`[${agentName} Session ${sessionId}] Mid-turn inject failed \u2014 queuing:`, err?.message ?? err);
20271
20567
  enqueueBusy();
20568
+ drainQueueHeadIfIdle();
20272
20569
  });
20273
20570
  return;
20274
20571
  }
@@ -20484,11 +20781,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20484
20781
  });
20485
20782
  },
20486
20783
  onIssue: async (params) => {
20487
- const { issueRpc } = await import('./rpc-wL-45Raf.mjs');
20784
+ const { issueRpc } = await import('./rpc-D7_Gj0Kt.mjs');
20488
20785
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
20489
20786
  },
20490
20787
  onWorkflow: async (params) => {
20491
- const { workflowRpc } = await import('./rpc-BjKmyKdA.mjs');
20788
+ const { workflowRpc } = await import('./rpc-BVw8Tv9l.mjs');
20492
20789
  return workflowRpc(params?.cwd || directory, params || {});
20493
20790
  },
20494
20791
  onRipgrep: async (args, cwd) => {
@@ -20520,6 +20817,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20520
20817
  await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
20521
20818
  await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
20522
20819
  },
20820
+ onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
20821
+ await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
20822
+ },
20523
20823
  onListDirectory: async (path) => {
20524
20824
  const resolvedDir = resolve$1(directory, path || ".");
20525
20825
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -20529,8 +20829,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20529
20829
  return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
20530
20830
  },
20531
20831
  onGetDirectoryTree: async (treePath, maxDepth) => {
20832
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
20833
+ const MAX_TREE_DEPTH = 8;
20834
+ const MAX_TREE_NODES = 2e4;
20835
+ const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
20836
+ let treeNodeCount = 0;
20532
20837
  async function buildTree(p, name, depth) {
20533
20838
  try {
20839
+ if (treeNodeCount >= MAX_TREE_NODES) return null;
20840
+ treeNodeCount++;
20534
20841
  const stats = await fs$1.stat(p);
20535
20842
  const node = {
20536
20843
  name,
@@ -20539,11 +20846,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20539
20846
  size: stats.size,
20540
20847
  modified: stats.mtime.getTime()
20541
20848
  };
20542
- if (stats.isDirectory() && depth < maxDepth) {
20849
+ if (stats.isDirectory() && depth < effectiveMaxDepth) {
20543
20850
  const entries = await fs$1.readdir(p, { withFileTypes: true });
20544
20851
  const children = [];
20545
20852
  await Promise.all(entries.map(async (entry) => {
20546
20853
  if (entry.isSymbolicLink()) return;
20854
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
20855
+ if (treeNodeCount >= MAX_TREE_NODES) return;
20547
20856
  const childPath = join(p, entry.name);
20548
20857
  const childNode = await buildTree(childPath, entry.name, depth + 1);
20549
20858
  if (childNode) children.push(childNode);
@@ -20806,7 +21115,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20806
21115
  const iterStop = !isCostCeiling(budgetCheck.kind);
20807
21116
  writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: now, gave_up_reason: `resource budget exhausted \u2014 ${budgetCheck.reason}`, ledger });
20808
21117
  const acpExtNote = summarizeExtensions(ls.extensions, acpMaxExt);
20809
- const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" \xB7 or set a new cap: svamp session loop-resume ${sessionId} --max <N>` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
21118
+ const acpResumeHint = iterStop ? `Extend with a reason: svamp session loop-extend ${sessionId} --reason "<why>" [--turns <N>]` : `Raise the cost cap to resume (--max won't \u2014 it only raises the iteration cap): svamp session loop ${sessionId} --max-runtime-sec <N> and/or --max-tokens-per-hour <N>`;
20810
21119
  sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${budgetCheck.reason} (hard ${iterStop ? "iteration ceiling" : "cost ceiling"} \u2014 will not auto-resume)${acpExtNote ? ` [${acpExtNote}]` : ""}. ${acpResumeHint}`, level: "warning" }, "event");
20811
21120
  checkSvampConfig?.();
20812
21121
  return;
@@ -21434,7 +21743,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21434
21743
  }
21435
21744
  if (persistedSessions.length > 0) {
21436
21745
  try {
21437
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BaMR97BE.mjs');
21746
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-zDzkOTi2.mjs');
21438
21747
  await awaitClaudeVersionReady();
21439
21748
  } catch {
21440
21749
  }
@@ -21639,7 +21948,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21639
21948
  const PING_TIMEOUT_MS = 15e3;
21640
21949
  const POST_RECONNECT_GRACE_MS = 2e4;
21641
21950
  const RECONNECT_JITTER_MS = 2500;
21642
- const { WorkflowScheduler } = await import('./scheduler-Dia9Akaw.mjs');
21951
+ const { WorkflowScheduler } = await import('./scheduler-q7Refevo.mjs');
21643
21952
  const workflowProjectRoots = () => {
21644
21953
  const dirs = /* @__PURE__ */ new Set();
21645
21954
  for (const s of pidToTrackedSession.values()) {
@@ -21928,6 +22237,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21928
22237
  logger.log(`Cleaning up (source: ${source})...`);
21929
22238
  clearInterval(heartbeatInterval);
21930
22239
  clearInterval(workflowSchedulerInterval);
22240
+ clearInterval(backendAccountRefreshInterval);
21931
22241
  if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
21932
22242
  if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
21933
22243
  if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
@@ -22272,4 +22582,4 @@ var run = /*#__PURE__*/Object.freeze({
22272
22582
  writeStopMarker: writeStopMarker
22273
22583
  });
22274
22584
 
22275
- export { getSkillInfo as $, removeWorkflow as A, validateWorkflowName as B, saveWorkflow as C, rawWorkflow as D, listWorkflows as E, isWorkflowEnabled as F, workflowCrons as G, cronMatches as H, summarize as I, workflowSteps as J, parseJwtEmail as K, computeCollectionConfigUpdate as L, SYSTEM_COLLECTION_CONFIG as M, loadMachineContext as N, buildMachineInstructions as O, machineToolsForRole as P, buildMachineTools as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, PINNED_CODEX_VERSION as T, parseFrontmatter as U, getSkillsServer as V, getSkillsWorkspaceName as W, getSkillsCollectionName as X, fetchWithTimeout as Y, searchSkills as Z, SKILLS_DIR as _, createSessionStore as a, downloadSkillFile as a0, listSkillFiles as a1, resolveModel as a2, clearStopMarker as a3, stopMarkerExists as a4, formatHandle as a5, normalizeAllowedUser as a6, loadSecurityContextConfig as a7, resolveSecurityContext as a8, buildSecurityContextFromFlags as a9, mergeSecurityContexts as aa, buildSessionShareUrl as ab, computeOutboundHop as ac, registerAwaitingReply as ad, buildMachineShareUrl as ae, parseHandle as af, handleMatchesMetadata as ag, describeMisconfiguration as ah, buildMachineDeps as ai, applyClaudeProxyEnv as aj, composeSessionId as ak, generateFriendlyName as al, generateHookSettings as am, staticFileServer as an, instanceConfig as ao, claudeAuth as ap, codexProvider as aq, projectInfo as ar, DefaultTransport$1 as as, acpBackend as at, acpAgentConfig as au, codexAppServerBackend as av, GeminiTransport$1 as aw, api as ax, run as ay, stopDaemon as b, connectToHypha as c, daemonStatus as d, getFrpsSubdomainHost as e, getFrpsServerPort as f, getHyphaServerUrl$1 as g, getFrpsServerAddr as h, shortId as i, resolveProjectRoot as j, getIssue as k, resumeIssue as l, addComment as m, addIssue as n, listIssues as o, pauseIssue as p, searchIssues as q, registerMachineService as r, startDaemon as s, isVisibleTo as t, updateIssue as u, getRun as v, listRuns as w, getWorkflow as x, runWorkflow as y, setWorkflowEnabled as z };
22585
+ export { SKILLS_DIR as $, removeWorkflow as A, validateWorkflowName as B, saveWorkflow as C, rawWorkflow as D, listWorkflows as E, isWorkflowEnabled as F, workflowSchedules as G, inZone as H, cronMatches as I, summarize as J, workflowSteps as K, parseJwtEmail as L, computeCollectionConfigUpdate as M, SYSTEM_COLLECTION_CONFIG as N, loadMachineContext as O, buildMachineInstructions as P, machineToolsForRole as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, buildMachineTools as T, PINNED_CODEX_VERSION as U, parseFrontmatter as V, getSkillsServer as W, getSkillsWorkspaceName as X, getSkillsCollectionName as Y, fetchWithTimeout as Z, searchSkills as _, createSessionStore as a, getSkillInfo as a0, downloadSkillFile as a1, listSkillFiles as a2, resolveModel as a3, clearStopMarker as a4, stopMarkerExists as a5, formatHandle as a6, normalizeAllowedUser as a7, loadSecurityContextConfig as a8, resolveSecurityContext as a9, run as aA, buildSecurityContextFromFlags as aa, mergeSecurityContexts as ab, buildSessionShareUrl as ac, computeOutboundHop as ad, registerAwaitingReply as ae, buildMachineShareUrl as af, parseHandle as ag, handleMatchesMetadata as ah, withFileLock as ai, describeMisconfiguration as aj, buildMachineDeps as ak, applyClaudeProxyEnv as al, composeSessionId as am, generateFriendlyName as an, generateHookSettings as ao, staticFileServer as ap, instanceConfig as aq, claudeAuth as ar, codexProvider as as, projectInfo as at, DefaultTransport$1 as au, acpBackend as av, acpAgentConfig as aw, codexAppServerBackend as ax, GeminiTransport$1 as ay, api as az, stopDaemon as b, connectToHypha as c, daemonStatus as d, getFrpsSubdomainHost as e, getFrpsServerPort as f, getHyphaServerUrl$1 as g, getFrpsServerAddr as h, shortId as i, resolveProjectRoot as j, getIssue as k, resumeIssue as l, addComment as m, addIssue as n, listIssues as o, pauseIssue as p, searchIssues as q, registerMachineService as r, startDaemon as s, isVisibleTo as t, updateIssue as u, getRun as v, listRuns as w, getWorkflow as x, runWorkflow as y, setWorkflowEnabled as z };