svamp-cli 0.2.308 → 0.2.309

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 (28) hide show
  1. package/dist/{adminCommands-CtecEU3a.mjs → adminCommands-BTLHQXmA.mjs} +2 -2
  2. package/dist/{agentCommands-DEJpgdgc.mjs → agentCommands-DdNUACV8.mjs} +6 -6
  3. package/dist/{auth-BTodo5Et.mjs → auth-Bn8TbaOP.mjs} +2 -2
  4. package/dist/{cli-Ko4XpjRo.mjs → cli-BaDWWDiz.mjs} +70 -70
  5. package/dist/cli.mjs +3 -3
  6. package/dist/{commands-DKOYCcmo.mjs → commands-2uOorIjB.mjs} +2 -2
  7. package/dist/{commands-B2ARVwag.mjs → commands-BPgxgkXW.mjs} +3 -3
  8. package/dist/{commands-D3Nu5cYE.mjs → commands-BTN9XxVx.mjs} +3 -2
  9. package/dist/{commands-GNplzSQX.mjs → commands-Bb0oF3eZ.mjs} +37 -6
  10. package/dist/{commands-B-u0fICz.mjs → commands-C2u988x1.mjs} +3 -3
  11. package/dist/{commands-dBiQcDJ1.mjs → commands-DGbLwT_h.mjs} +23 -11
  12. package/dist/{commands-DA7rAZqn.mjs → commands-DPvy0iVT.mjs} +2 -2
  13. package/dist/{commands-DfbOYRLs.mjs → commands-r86haNJD.mjs} +3 -3
  14. package/dist/{fleet-CXixARxe.mjs → fleet-CeLE0t97.mjs} +3 -3
  15. package/dist/{frpc-DV_IIYpM.mjs → frpc-Cv6J2Mex.mjs} +5 -2
  16. package/dist/{headlessCli-DfkgeGnQ.mjs → headlessCli-BXc9Fvzb.mjs} +3 -3
  17. package/dist/index.mjs +2 -2
  18. package/dist/{notifyCommands-BUE_-OCX.mjs → notifyCommands-iVnI_Lb2.mjs} +2 -2
  19. package/dist/{package-GdCOuQCx.mjs → package-CeseSmYI.mjs} +2 -2
  20. package/dist/{pinnedClaudeCode-BaMR97BE.mjs → pinnedClaudeCode-C6MSkqS9.mjs} +1 -1
  21. package/dist/{rpc-BjKmyKdA.mjs → rpc-BwxwusfB.mjs} +2 -2
  22. package/dist/{rpc-wL-45Raf.mjs → rpc-CkcBAtcs.mjs} +2 -2
  23. package/dist/{run-ofbo9uzA.mjs → run-2Stz5l7J.mjs} +2 -2
  24. package/dist/{run-VQIHMUdo.mjs → run-DLG-_xa7.mjs} +366 -82
  25. package/dist/{scheduler-Dia9Akaw.mjs → scheduler-CurN4UtP.mjs} +5 -5
  26. package/dist/{serveCommands-DehmO1cO.mjs → serveCommands-CtVZhR-V.mjs} +5 -5
  27. package/dist/{sideband-CGiNKfNE.mjs → sideband-C_3PVGh-.mjs} +2 -2
  28. package/package.json +2 -2
@@ -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) {
@@ -2443,6 +2504,13 @@ class ServeManager {
2443
2504
  async ensureManagedRunning(name) {
2444
2505
  const mount = this.mounts.get(name);
2445
2506
  if (!mount?.process) return;
2507
+ const pendingStop = this.stoppingProcs.get(name);
2508
+ if (pendingStop) {
2509
+ try {
2510
+ await pendingStop;
2511
+ } catch {
2512
+ }
2513
+ }
2446
2514
  const handle = this.managedProcs.get(name);
2447
2515
  if (handle && handle.warmupPromise) {
2448
2516
  return handle.warmupPromise;
@@ -2474,7 +2542,10 @@ class ServeManager {
2474
2542
  child.on("exit", (code, signal) => {
2475
2543
  this.log(`Managed process '${name}' exited (code=${code}, signal=${signal})`);
2476
2544
  const h = this.managedProcs.get(name);
2477
- if (h && h.child === child) this.managedProcs.delete(name);
2545
+ if (h && h.child === child) {
2546
+ this.managedProcs.delete(name);
2547
+ this.removeManagedPidFile(name);
2548
+ }
2478
2549
  });
2479
2550
  const warmupPath = cfg.warmupPath ?? "/";
2480
2551
  const warmupTimeoutMs = cfg.warmupTimeoutMs ?? 3e4;
@@ -2500,6 +2571,7 @@ class ServeManager {
2500
2571
  warmupPromise
2501
2572
  };
2502
2573
  this.managedProcs.set(name, newHandle);
2574
+ this.writeManagedPidFile(name, child, cfg);
2503
2575
  this.log(`Managed process '${name}' starting: ${cfg.command} ${(cfg.args ?? []).join(" ")} (port ${cfg.port})`);
2504
2576
  return warmupPromise;
2505
2577
  }
@@ -2509,6 +2581,25 @@ class ServeManager {
2509
2581
  * that a direct child.kill() would orphan, leaving the port bound. Falls back to a direct
2510
2582
  * child.kill() if the group signal fails (e.g. pid already gone).
2511
2583
  */
2584
+ // ── #0758: managed-process pidfiles (crash-orphan reaping) ──────────────
2585
+ managedPidFile(name) {
2586
+ const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
2587
+ return path.join(this.managedPidDir, `${safe}.pid`);
2588
+ }
2589
+ writeManagedPidFile(name, child, cfg) {
2590
+ try {
2591
+ fs.mkdirSync(this.managedPidDir, { recursive: true, mode: 448 });
2592
+ const rec = { pid: child.pid ?? 0, command: cfg.command, args: cfg.args ?? [], port: cfg.port, startedAt: Date.now() };
2593
+ fs.writeFileSync(this.managedPidFile(name), JSON.stringify(rec), { mode: 384 });
2594
+ } catch {
2595
+ }
2596
+ }
2597
+ removeManagedPidFile(name) {
2598
+ try {
2599
+ fs.unlinkSync(this.managedPidFile(name));
2600
+ } catch {
2601
+ }
2602
+ }
2512
2603
  killManagedTree(child, signal) {
2513
2604
  const pid = child.pid;
2514
2605
  if (pid && pid > 0) {
@@ -2558,18 +2649,30 @@ class ServeManager {
2558
2649
  const h = this.managedProcs.get(name);
2559
2650
  if (!h) return;
2560
2651
  this.managedProcs.delete(name);
2652
+ this.removeManagedPidFile(name);
2561
2653
  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();
2654
+ const done = (async () => {
2655
+ if (child.exitCode !== null) return;
2656
+ this.killManagedTree(child, "SIGTERM");
2657
+ await new Promise((resolve) => {
2658
+ const onExit = () => {
2659
+ clearTimeout(t);
2660
+ clearTimeout(cap);
2661
+ resolve();
2662
+ };
2663
+ const t = setTimeout(() => {
2664
+ this.killManagedTree(child, "SIGKILL");
2665
+ }, 5e3);
2666
+ const cap = setTimeout(onExit, 8e3);
2667
+ child.once("exit", onExit);
2571
2668
  });
2572
- });
2669
+ })();
2670
+ this.stoppingProcs.set(name, done);
2671
+ try {
2672
+ await done;
2673
+ } finally {
2674
+ if (this.stoppingProcs.get(name) === done) this.stoppingProcs.delete(name);
2675
+ }
2573
2676
  this.log(`Managed process '${name}' stopped`);
2574
2677
  }
2575
2678
  /** Idle eviction loop — stops processes that have been idle longer than configured. */
@@ -2631,6 +2734,7 @@ class ServeManager {
2631
2734
  async ensureRunning() {
2632
2735
  if (this.proxyServer) return;
2633
2736
  killOrphanedCaddy((m) => this.log(m));
2737
+ reapOrphanedManagedProcesses(this.managedPidDir, (m) => this.log(m));
2634
2738
  this.port = await tryReservePort(this.persistedPort);
2635
2739
  if (this.persistedPort && this.port !== this.persistedPort) {
2636
2740
  this.log(`\u26A0 Previous serve port ${this.persistedPort} unavailable \u2014 using ${this.port}. Downstream configs referencing the old port will need updating.`);
@@ -3047,7 +3151,7 @@ Connection: close\r
3047
3151
  const mount = this.mounts.get(mountName);
3048
3152
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
3049
3153
  try {
3050
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
3154
+ const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
3051
3155
  let tunnel;
3052
3156
  tunnel = new FrpcTunnel({
3053
3157
  name: tunnelName,
@@ -3974,6 +4078,26 @@ function cronMatches(expr, date) {
3974
4078
  if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
3975
4079
  return domOk && dowOk;
3976
4080
  }
4081
+ function inZone(date, tz) {
4082
+ if (!tz) return date;
4083
+ try {
4084
+ const p = new Intl.DateTimeFormat("en-US", {
4085
+ timeZone: tz,
4086
+ hour12: false,
4087
+ year: "numeric",
4088
+ month: "2-digit",
4089
+ day: "2-digit",
4090
+ hour: "2-digit",
4091
+ minute: "2-digit"
4092
+ }).formatToParts(date).reduce((o, x) => {
4093
+ o[x.type] = x.value;
4094
+ return o;
4095
+ }, {});
4096
+ return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
4097
+ } catch {
4098
+ return date;
4099
+ }
4100
+ }
3977
4101
  function resolvePath(ctx, path) {
3978
4102
  return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
3979
4103
  }
@@ -3992,8 +4116,8 @@ function sleepSync$1(ms) {
3992
4116
  }
3993
4117
  }
3994
4118
  function withFileLock(lockPath, fn, opts) {
3995
- const deadlineMs = 50;
3996
- const staleMs = 5e3;
4119
+ const deadlineMs = opts?.deadlineMs ?? 50;
4120
+ const staleMs = opts?.staleMs ?? 5e3;
3997
4121
  const deadline = Date.now() + deadlineMs;
3998
4122
  let held = false;
3999
4123
  while (Date.now() < deadline) {
@@ -4277,12 +4401,14 @@ class ChannelStore {
4277
4401
  return this._writeChannel(channel);
4278
4402
  }
4279
4403
  remove(id) {
4280
- const p = this._path(id);
4281
- if (existsSync(p)) {
4282
- rmSync$1(p);
4283
- return true;
4284
- }
4285
- return false;
4404
+ return withFileLock(this._lock(id), () => {
4405
+ const p = this._path(id);
4406
+ if (existsSync(p)) {
4407
+ rmSync$1(p);
4408
+ return true;
4409
+ }
4410
+ return false;
4411
+ });
4286
4412
  }
4287
4413
  // #0679: setEnabled/recordCall/addCaller are read-modify-write mutators — multiple ChannelStore
4288
4414
  // instances (one per session) point at the same .svamp/channels/<id>.json, so without
@@ -4711,10 +4837,14 @@ class ChannelOutbox {
4711
4837
  /** Append a reply addressed to `to`. Assigns seq + ts, persists, and wakes waiters. */
4712
4838
  append(channelId, r) {
4713
4839
  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();
4840
+ const seq = withFileLock(`${this.seqFile}.lock`, () => {
4841
+ this._loadHighWater();
4842
+ const s = Math.max(this.highWater.get(channelId) || 0, this.seqByChannel.get(channelId) || 0) + 1;
4843
+ this.seqByChannel.set(channelId, s);
4844
+ this.highWater.set(channelId, s);
4845
+ this._persistHighWater();
4846
+ return s;
4847
+ });
4718
4848
  const reply = { seq, ts: Date.now(), to: r.to, body: r.body, ...r.correlationId ? { correlationId: r.correlationId } : {} };
4719
4849
  const arr = this.byChannel.get(channelId) || [];
4720
4850
  arr.push(reply);
@@ -4841,6 +4971,7 @@ const SESSION_METHOD_MIN_ROLE = {
4841
4971
  archiveSession: "admin",
4842
4972
  readFile: "admin",
4843
4973
  writeFile: "admin",
4974
+ writeFileChunk: "admin",
4844
4975
  listDirectory: "admin",
4845
4976
  bash: "admin",
4846
4977
  issue: "admin",
@@ -6123,7 +6254,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6123
6254
  const tunnels = handlers.tunnels;
6124
6255
  if (!tunnels) throw new Error("Tunnel management not available");
6125
6256
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
6126
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
6257
+ const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
6127
6258
  const tunnel = new FrpcTunnel({
6128
6259
  name: params.name,
6129
6260
  ports: params.ports,
@@ -6617,7 +6748,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6617
6748
  }
6618
6749
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
6619
6750
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
6620
- const { toolsForRole } = await import('./sideband-CGiNKfNE.mjs');
6751
+ const { toolsForRole } = await import('./sideband-C_3PVGh-.mjs');
6621
6752
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
6622
6753
  return fmt(r2);
6623
6754
  }
@@ -6722,7 +6853,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
6722
6853
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
6723
6854
  }
6724
6855
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
6725
- const { queryCore } = await import('./commands-D3Nu5cYE.mjs');
6856
+ const { queryCore } = await import('./commands-BTN9XxVx.mjs');
6726
6857
  const timeout = c.reply?.timeout_sec || 120;
6727
6858
  let result;
6728
6859
  try {
@@ -7232,9 +7363,17 @@ function getRateLimitRetryConfig() {
7232
7363
  return {
7233
7364
  maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 200),
7234
7365
  baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
7235
- capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
7366
+ capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4),
7367
+ maxElapsedMs: envInt("SVAMP_RATELIMIT_MAX_ELAPSED_MS", 36e5)
7236
7368
  };
7237
7369
  }
7370
+ function retryStreakExhausted(attempts, streakStartedAtMs, nowMs, cfg = getRateLimitRetryConfig()) {
7371
+ if (attempts >= cfg.maxRetries) return { exhausted: true, reason: "count" };
7372
+ if (cfg.maxElapsedMs > 0 && streakStartedAtMs && streakStartedAtMs > 0 && nowMs - streakStartedAtMs >= cfg.maxElapsedMs) {
7373
+ return { exhausted: true, reason: "elapsed" };
7374
+ }
7375
+ return { exhausted: false };
7376
+ }
7238
7377
  function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
7239
7378
  const a = Math.max(0, Math.floor(attempt));
7240
7379
  const expo = Math.min(a, 30);
@@ -7413,6 +7552,9 @@ async function deletePairingArtifact(am, artifactId, log) {
7413
7552
  }
7414
7553
  }
7415
7554
  async function sweepExpiredPairings(am, svampHome, log) {
7555
+ return withMintLock("*sweep*", () => _sweepExpiredPairingsUnlocked(am, svampHome, log));
7556
+ }
7557
+ async function _sweepExpiredPairingsUnlocked(am, svampHome, log) {
7416
7558
  const now = Date.now();
7417
7559
  const all = loadPairings(svampHome);
7418
7560
  const expired = all.filter((p) => p.expiresAt <= now);
@@ -7420,18 +7562,40 @@ async function sweepExpiredPairings(am, svampHome, log) {
7420
7562
  for (const p of expired) await deletePairingArtifact(am, p.artifactId, log);
7421
7563
  savePairings(all.filter((p) => p.expiresAt > now), svampHome);
7422
7564
  }
7565
+ let _lastSweepAt = 0;
7566
+ async function sweepExpiredPairingsThrottled(am, svampHome, log, minIntervalMs = 5 * 6e4) {
7567
+ const now = Date.now();
7568
+ if (now - _lastSweepAt < minIntervalMs) return;
7569
+ _lastSweepAt = now;
7570
+ await sweepExpiredPairings(am, svampHome, log);
7571
+ }
7572
+ let _pairingChain = Promise.resolve();
7573
+ function withMintLock(_session, fn) {
7574
+ const prior = _pairingChain.catch(() => {
7575
+ });
7576
+ const result = prior.then(fn);
7577
+ _pairingChain = result.catch(() => {
7578
+ });
7579
+ return result;
7580
+ }
7423
7581
  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);
7582
+ return withMintLock(session, async () => {
7583
+ const all = loadPairings(svampHome);
7584
+ const mine = all.filter((p) => p.session === session);
7585
+ if (mine.length === 0) return;
7586
+ for (const p of mine) await deletePairingArtifact(am, p.artifactId, log);
7587
+ savePairings(all.filter((p) => p.session !== session), svampHome);
7588
+ });
7429
7589
  }
7430
7590
  async function mintPairingCode(opts) {
7431
- const { am, session, payload, log } = opts;
7591
+ const { session} = opts;
7432
7592
  const resolver = (opts.resolverBase || "https://hypha.aicell.io").replace(/\/$/, "");
7433
7593
  const ttlMs = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : DEFAULT_PAIRING_TTL_MS;
7434
- await sweepExpiredPairings(am, opts.svampHome, log).catch(() => {
7594
+ return withMintLock(session, () => _mintPairingCodeLocked(opts, resolver, ttlMs));
7595
+ }
7596
+ async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
7597
+ const { am, session, payload, log } = opts;
7598
+ await _sweepExpiredPairingsUnlocked(am, opts.svampHome, log).catch(() => {
7435
7599
  });
7436
7600
  const now = Date.now();
7437
7601
  const existing = loadPairings(opts.svampHome).find((p) => p.session === session && p.expiresAt > now + 3e4);
@@ -8072,6 +8236,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8072
8236
  const channelOutbox = new ChannelOutbox(initialMetadata.path);
8073
8237
  const outpostCoordinator = new OutpostCoordinator();
8074
8238
  outpostCoordinator.startSweeper();
8239
+ void (async () => {
8240
+ try {
8241
+ const am = await server.getService("public/artifact-manager");
8242
+ if (am) await sweepExpiredPairingsThrottled(am, void 0, (m) => console.log(m));
8243
+ } catch {
8244
+ }
8245
+ })();
8075
8246
  const outpostProjectDir = () => metadata.path || process.cwd();
8076
8247
  const announceOutpost = (conn) => {
8077
8248
  const who = conn.label || conn.machine;
@@ -8847,7 +9018,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8847
9018
  });
8848
9019
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
8849
9020
  const cursor = Math.max(0, Number(params.cursor) || 0);
8850
- const waitMs = Math.min(Math.max(0, Number(params.wait ?? 25) * 1e3), 6e4);
9021
+ const waitSecRaw = Number(params.wait);
9022
+ const waitSec = Number.isFinite(waitSecRaw) ? waitSecRaw : 25;
9023
+ const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
8851
9024
  const out = await channelOutbox.wait(c.id, cursor, r.sender.name, waitMs, params.correlationId);
8852
9025
  return { ok: true, replies: out.replies, cursor: out.cursor };
8853
9026
  },
@@ -8958,6 +9131,12 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8958
9131
  await callbacks.onWriteFile(path, content);
8959
9132
  return { success: true };
8960
9133
  },
9134
+ writeFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast, context) => {
9135
+ authorizeRequest(context, metadata.sharing, "admin");
9136
+ if (!callbacks.onWriteFileChunk) throw new Error("writeFileChunk not supported");
9137
+ await callbacks.onWriteFileChunk(path, content, uploadId, chunkIndex, totalChunks, isLast);
9138
+ return { success: true };
9139
+ },
8961
9140
  listDirectory: async (path, context) => {
8962
9141
  authorizeRequest(context, metadata.sharing, "admin");
8963
9142
  if (!callbacks.onListDirectory) throw new Error("listDirectory not supported");
@@ -12291,15 +12470,30 @@ function codexAppServerAvailable() {
12291
12470
  return null;
12292
12471
  }
12293
12472
  }
12473
+ const _execFileAsync = promisify$1(execFile$1);
12294
12474
  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;
12475
+ let _steerProbedAt = 0;
12476
+ let _steerProbe = null;
12477
+ const _STEER_TTL_MS = 3e4;
12478
+ const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
12479
+ function _steerFresh() {
12480
+ return _steerSupport !== null && Date.now() - _steerProbedAt < _STEER_TTL_MS;
12481
+ }
12482
+ async function codexSupportsSteerInstalledAsync() {
12483
+ if (_steerFresh()) return _steerSupport;
12484
+ if (_steerProbe) return _steerProbe;
12485
+ _steerProbe = (async () => {
12486
+ try {
12487
+ const { stdout } = await _execFileAsync("codex", ["--version"], { encoding: "utf8" });
12488
+ _steerSupport = codexSupportsSteer(parseCodexVersion(stdout.trim()));
12489
+ } catch {
12490
+ _steerSupport = false;
12491
+ }
12492
+ _steerProbedAt = Date.now();
12493
+ _steerProbe = null;
12494
+ return _steerSupport;
12495
+ })();
12496
+ return _steerProbe;
12303
12497
  }
12304
12498
  class CodexAppServerClient {
12305
12499
  constructor(opts) {
@@ -12474,7 +12668,7 @@ class CodexAppServerClient {
12474
12668
  */
12475
12669
  async injectInput(prompt, _o) {
12476
12670
  if (!this._threadId || !this.pendingTurn || !this._turnId) return false;
12477
- if (!codexSupportsSteerInstalled()) return false;
12671
+ if (!await codexSupportsSteerInstalledAsync()) return false;
12478
12672
  const params = {
12479
12673
  threadId: this._threadId,
12480
12674
  expectedTurnId: this._turnId,
@@ -12529,17 +12723,16 @@ class CodexAppServerClient {
12529
12723
  // ── JSON-RPC plumbing ──────────────────────────────────────────────────────
12530
12724
  request(method, params, timeoutMs) {
12531
12725
  const id = this.nextId++;
12726
+ const effTimeout = timeoutMs ?? this.opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
12532
12727
  return new Promise((resolve, reject) => {
12533
12728
  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
- }
12729
+ const t = setTimeout(() => {
12730
+ if (this.pending.has(id)) {
12731
+ this.pending.delete(id);
12732
+ reject(new Error(`codex ${method} timed out after ${effTimeout}ms`));
12733
+ }
12734
+ }, effTimeout);
12735
+ t.unref?.();
12543
12736
  this.write({ jsonrpc: "2.0", id, method, params });
12544
12737
  });
12545
12738
  }
@@ -14839,8 +15032,8 @@ function isWorkflowEnabled(wf) {
14839
15032
  function workflowSteps(wf) {
14840
15033
  return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
14841
15034
  }
14842
- function workflowCrons(wf) {
14843
- return (wf.on?.schedule || []).map((s) => s?.cron).filter((c) => !!c);
15035
+ function workflowSchedules(wf) {
15036
+ return (wf.on?.schedule || []).filter((s) => !!s?.cron);
14844
15037
  }
14845
15038
  function workflowsDir(projectRoot) {
14846
15039
  return join$1(projectRoot, ".svamp", "workflows");
@@ -14860,7 +15053,11 @@ function normalizeOn(on) {
14860
15053
  }
14861
15054
  const out = {};
14862
15055
  if (Array.isArray(on.schedule)) {
14863
- const sched = on.schedule.map((s) => ({ cron: String(s?.cron ?? s ?? "") })).filter((s) => s.cron);
15056
+ const sched = on.schedule.map((s) => {
15057
+ const entry = { cron: String(s?.cron ?? s ?? "") };
15058
+ if (s && typeof s === "object" && s.tz != null && String(s.tz).trim()) entry.tz = String(s.tz).trim();
15059
+ return entry;
15060
+ }).filter((s) => s.cron);
14864
15061
  if (sched.length) out.schedule = sched;
14865
15062
  } else if (typeof on.schedule === "string" && on.schedule.trim()) {
14866
15063
  out.schedule = [{ cron: on.schedule.trim() }];
@@ -14908,7 +15105,7 @@ function serializeWorkflow(wf) {
14908
15105
  if (wf.session) clean.session = wf.session;
14909
15106
  if (wf.enabled === false) clean.enabled = false;
14910
15107
  const on = {};
14911
- if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => ({ cron: s.cron }));
15108
+ if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => s.tz ? { cron: s.cron, tz: s.tz } : { cron: s.cron });
14912
15109
  if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
14913
15110
  if (wf.on?.channel) on.channel = wf.on.channel;
14914
15111
  if (wf.on?.issue?.length) on.issue = wf.on.issue;
@@ -15174,7 +15371,7 @@ function escalateWorkflowFailure(projectRoot, wf, run) {
15174
15371
  const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
15175
15372
  if (open.length > 0) return void 0;
15176
15373
  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)"}`;
15374
+ 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
15375
  const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
15179
15376
  const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
15180
15377
  const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
@@ -15358,7 +15555,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
15358
15555
  } catch {
15359
15556
  return [];
15360
15557
  }
15361
- const matches = workflows.filter((wf) => wf.on?.idle === true && isWorkflowEnabled(wf));
15558
+ const matches = workflows.filter(
15559
+ (wf) => wf.on?.idle === true && isWorkflowEnabled(wf) && (!wf.session || wf.session === sessionId)
15560
+ );
15362
15561
  return matches.map((wf) => {
15363
15562
  deps.log?.(`[workflow] idle fired "${wf.name}" in ${root} for session ${sessionId}`);
15364
15563
  return runWorkflow(root, wf, {
@@ -16114,6 +16313,24 @@ async function readSessionFileBase64(resolvedPath) {
16114
16313
  const buffer = await fs$1.readFile(resolvedPath);
16115
16314
  return buffer.toString("base64");
16116
16315
  }
16316
+ async function writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast) {
16317
+ const resolvedPath = resolve$1(directory, path);
16318
+ if (sessionMetadata?.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
16319
+ throw new Error("Path outside working directory");
16320
+ }
16321
+ const safeUploadId = String(uploadId).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "upload";
16322
+ const tempPath = `${resolvedPath}.svamp-upload-${safeUploadId}.part`;
16323
+ const buffer = Buffer.from(content || "", "base64");
16324
+ if (chunkIndex === 0) {
16325
+ await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
16326
+ await fs$1.writeFile(tempPath, buffer);
16327
+ } else {
16328
+ await fs$1.appendFile(tempPath, buffer);
16329
+ }
16330
+ if (isLast) {
16331
+ await fs$1.rename(tempPath, resolvedPath);
16332
+ }
16333
+ }
16117
16334
 
16118
16335
  const __filename$1 = fileURLToPath(import.meta.url);
16119
16336
  const __dirname$1 = dirname$1(__filename$1);
@@ -17612,7 +17829,7 @@ async function startDaemon(options) {
17612
17829
  try {
17613
17830
  const dir = loadSessionIndex()[sessionId]?.directory;
17614
17831
  if (!dir) return;
17615
- const { reconcileServiceLinks } = await import('./agentCommands-DEJpgdgc.mjs');
17832
+ const { reconcileServiceLinks } = await import('./agentCommands-DdNUACV8.mjs');
17616
17833
  const configPath = getSvampConfigPath(dir, sessionId);
17617
17834
  const config = readSvampConfig(configPath);
17618
17835
  const entries = Array.from(urls.entries());
@@ -17630,7 +17847,7 @@ async function startDaemon(options) {
17630
17847
  }
17631
17848
  }
17632
17849
  async function createExposedTunnel(spec) {
17633
- const { FrpcTunnel } = await import('./frpc-DV_IIYpM.mjs');
17850
+ const { FrpcTunnel } = await import('./frpc-Cv6J2Mex.mjs');
17634
17851
  const tunnel = new FrpcTunnel({
17635
17852
  name: spec.name,
17636
17853
  ports: spec.ports,
@@ -17658,7 +17875,7 @@ async function startDaemon(options) {
17658
17875
  ensureAutoInstalledCommands(logger);
17659
17876
  (async () => {
17660
17877
  try {
17661
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BaMR97BE.mjs');
17878
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
17662
17879
  beginClaudeVersionReconcile((msg) => logger.log(msg));
17663
17880
  } catch (e) {
17664
17881
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -17941,8 +18158,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
17941
18158
  if (RATELIMIT_CFG.maxRetries <= 0) return false;
17942
18159
  if (trackedSession?.stopped) return false;
17943
18160
  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`);
18161
+ const nowTs = Date.now();
18162
+ if (rateLimitStreakStartedAt === 0) {
18163
+ rateLimitStreakStartedAt = nowTs;
18164
+ sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: nowTs };
18165
+ }
18166
+ const budget = retryStreakExhausted(rateLimitRetryCount, rateLimitStreakStartedAt, nowTs, RATELIMIT_CFG);
18167
+ if (budget.exhausted) {
18168
+ const elapsedMin = Math.round((nowTs - rateLimitStreakStartedAt) / 6e4);
18169
+ logger.log(`[Session ${sessionId}] Rate-limit retries exhausted (${budget.reason}: ${rateLimitRetryCount}/${RATELIMIT_CFG.maxRetries} attempts, ${elapsedMin}m elapsed) \u2014 surfacing error`);
18170
+ resetRateLimitStreak();
17946
18171
  return false;
17947
18172
  }
17948
18173
  const attempt = rateLimitRetryCount++;
@@ -18410,18 +18635,18 @@ ${parts.join("\n")}`);
18410
18635
  enqueueLoopMessage(push, label);
18411
18636
  if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
18412
18637
  } 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 });
18638
+ 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
18639
  logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
18415
18640
  checkSvampConfig?.();
18416
18641
  }
18417
18642
  } else {
18418
- writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18643
+ writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions });
18419
18644
  checkSvampConfig?.();
18420
18645
  }
18421
18646
  } catch (e) {
18422
18647
  logger.log(`[Session ${sessionId}] verifyGoalCompletion error \u2014 failing open (done): ${e?.message || e}`);
18423
18648
  try {
18424
- writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now() });
18649
+ writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now() });
18425
18650
  checkSvampConfig?.();
18426
18651
  } catch {
18427
18652
  }
@@ -18479,8 +18704,16 @@ ${parts.join("\n")}`);
18479
18704
  const RATELIMIT_CFG = getRateLimitRetryConfig();
18480
18705
  let currentTurnMessage;
18481
18706
  let rateLimitRetryCount = 0;
18707
+ let rateLimitStreakStartedAt = Number(sessionMetadata?.rateLimitStreakStartedAt) || 0;
18482
18708
  let rateLimitRetryTimer = null;
18483
18709
  let rateLimitRetryScheduled = false;
18710
+ const resetRateLimitStreak = () => {
18711
+ rateLimitRetryCount = 0;
18712
+ rateLimitStreakStartedAt = 0;
18713
+ if (sessionMetadata?.rateLimitStreakStartedAt) {
18714
+ sessionMetadata = { ...sessionMetadata, rateLimitStreakStartedAt: 0 };
18715
+ }
18716
+ };
18484
18717
  let checkSvampConfig;
18485
18718
  let cleanupSvampConfig;
18486
18719
  const VALID_CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
@@ -18895,7 +19128,7 @@ ${parts.join("\n")}`);
18895
19128
  "event"
18896
19129
  );
18897
19130
  }
18898
- if (!msg.is_error) rateLimitRetryCount = 0;
19131
+ if (!msg.is_error) resetRateLimitStreak();
18899
19132
  if (msg.session_id) {
18900
19133
  claudeResumeId = msg.session_id;
18901
19134
  if (sessionMetadata.claudeSessionId !== msg.session_id) {
@@ -19467,7 +19700,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19467
19700
  return;
19468
19701
  }
19469
19702
  currentTurnMessage = text;
19470
- rateLimitRetryCount = 0;
19703
+ resetRateLimitStreak();
19471
19704
  if (rateLimitRetryTimer) {
19472
19705
  clearTimeout(rateLimitRetryTimer);
19473
19706
  rateLimitRetryTimer = null;
@@ -19491,7 +19724,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19491
19724
  clearTimeout(rateLimitRetryTimer);
19492
19725
  rateLimitRetryTimer = null;
19493
19726
  }
19494
- rateLimitRetryCount = 0;
19727
+ resetRateLimitStreak();
19495
19728
  if (claudeProcess && !claudeProcess.killed) {
19496
19729
  try {
19497
19730
  const interruptMsg = JSON.stringify({ type: "control_request", request: { type: "interrupt" } });
@@ -19848,11 +20081,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19848
20081
  });
19849
20082
  },
19850
20083
  onIssue: async (params) => {
19851
- const { issueRpc } = await import('./rpc-wL-45Raf.mjs');
20084
+ const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
19852
20085
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
19853
20086
  },
19854
20087
  onWorkflow: async (params) => {
19855
- const { workflowRpc } = await import('./rpc-BjKmyKdA.mjs');
20088
+ const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
19856
20089
  return workflowRpc(params?.cwd || directory, params || {});
19857
20090
  },
19858
20091
  onRipgrep: async (args, cwd) => {
@@ -19884,6 +20117,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19884
20117
  await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
19885
20118
  await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
19886
20119
  },
20120
+ onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
20121
+ await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
20122
+ },
19887
20123
  onListDirectory: async (path) => {
19888
20124
  const resolvedDir = resolve$1(directory, path || ".");
19889
20125
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -19893,8 +20129,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19893
20129
  return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
19894
20130
  },
19895
20131
  onGetDirectoryTree: async (treePath, maxDepth) => {
20132
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
20133
+ const MAX_TREE_DEPTH = 8;
20134
+ const MAX_TREE_NODES = 2e4;
20135
+ const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
20136
+ let treeNodeCount = 0;
19896
20137
  async function buildTree(p, name, depth) {
19897
20138
  try {
20139
+ if (treeNodeCount >= MAX_TREE_NODES) return null;
20140
+ treeNodeCount++;
19898
20141
  const stats = await fs$1.stat(p);
19899
20142
  const node = {
19900
20143
  name,
@@ -19903,11 +20146,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19903
20146
  size: stats.size,
19904
20147
  modified: stats.mtime.getTime()
19905
20148
  };
19906
- if (stats.isDirectory() && depth < maxDepth) {
20149
+ if (stats.isDirectory() && depth < effectiveMaxDepth) {
19907
20150
  const entries = await fs$1.readdir(p, { withFileTypes: true });
19908
20151
  const children = [];
19909
20152
  await Promise.all(entries.map(async (entry) => {
19910
20153
  if (entry.isSymbolicLink()) return;
20154
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
20155
+ if (treeNodeCount >= MAX_TREE_NODES) return;
19911
20156
  const childPath = join(p, entry.name);
19912
20157
  const childNode = await buildTree(childPath, entry.name, depth + 1);
19913
20158
  if (childNode) children.push(childNode);
@@ -19978,7 +20223,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
19978
20223
  userMessagePending = true;
19979
20224
  turnInitiatedByUser = true;
19980
20225
  currentTurnMessage = next.text;
19981
- rateLimitRetryCount = 0;
20226
+ resetRateLimitStreak();
19982
20227
  if (rateLimitRetryTimer) {
19983
20228
  clearTimeout(rateLimitRetryTimer);
19984
20229
  rateLimitRetryTimer = null;
@@ -20257,6 +20502,30 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20257
20502
  };
20258
20503
  sessionService.updateMetadata(sessionMetadata);
20259
20504
  };
20505
+ const drainQueueHeadIfIdle = () => {
20506
+ if (acpStopped || !acpBackendReady) return;
20507
+ if (sessionMetadata.lifecycleState !== "idle") return;
20508
+ const queue = sessionMetadata.messageQueue;
20509
+ if (!queue || queue.length === 0) return;
20510
+ const next = queue[0];
20511
+ const remaining = queue.slice(1);
20512
+ sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
20513
+ sessionService.updateMetadata(sessionMetadata);
20514
+ if (typeof next.inboxHop === "number") {
20515
+ writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
20516
+ }
20517
+ sessionService.markInboxHandled(next.id);
20518
+ if (!next.alreadyStored) sessionService.pushMessage(next.displayText || next.text, "user");
20519
+ sessionService.sendKeepAlive(true);
20520
+ agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
20521
+ logger.error(`[${agentName} Session ${sessionId}] Error draining queued message after inject miss: ${err?.message ?? err}`);
20522
+ if (!acpStopped) {
20523
+ sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
20524
+ sessionService.updateMetadata(sessionMetadata);
20525
+ sessionService.sendSessionEnd();
20526
+ }
20527
+ });
20528
+ };
20260
20529
  const injectFn = agentBackend.injectInput;
20261
20530
  if (typeof injectFn === "function") {
20262
20531
  Promise.resolve(injectFn.call(agentBackend, text)).then((injected) => {
@@ -20265,10 +20534,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20265
20534
  } else {
20266
20535
  logger.log(`[${agentName} Session ${sessionId}] No active turn to inject \u2014 queuing message`);
20267
20536
  enqueueBusy();
20537
+ drainQueueHeadIfIdle();
20268
20538
  }
20269
20539
  }).catch((err) => {
20270
20540
  logger.error(`[${agentName} Session ${sessionId}] Mid-turn inject failed \u2014 queuing:`, err?.message ?? err);
20271
20541
  enqueueBusy();
20542
+ drainQueueHeadIfIdle();
20272
20543
  });
20273
20544
  return;
20274
20545
  }
@@ -20484,11 +20755,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20484
20755
  });
20485
20756
  },
20486
20757
  onIssue: async (params) => {
20487
- const { issueRpc } = await import('./rpc-wL-45Raf.mjs');
20758
+ const { issueRpc } = await import('./rpc-CkcBAtcs.mjs');
20488
20759
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
20489
20760
  },
20490
20761
  onWorkflow: async (params) => {
20491
- const { workflowRpc } = await import('./rpc-BjKmyKdA.mjs');
20762
+ const { workflowRpc } = await import('./rpc-BwxwusfB.mjs');
20492
20763
  return workflowRpc(params?.cwd || directory, params || {});
20493
20764
  },
20494
20765
  onRipgrep: async (args, cwd) => {
@@ -20520,6 +20791,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20520
20791
  await fs$1.mkdir(dirname$1(resolvedPath), { recursive: true });
20521
20792
  await fs$1.writeFile(resolvedPath, Buffer.from(content, "base64"));
20522
20793
  },
20794
+ onWriteFileChunk: async (path, content, uploadId, chunkIndex, totalChunks, isLast) => {
20795
+ await writeSessionFileChunk(directory, sessionMetadata, path, content, uploadId, chunkIndex, isLast);
20796
+ },
20523
20797
  onListDirectory: async (path) => {
20524
20798
  const resolvedDir = resolve$1(directory, path || ".");
20525
20799
  if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
@@ -20529,8 +20803,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20529
20803
  return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
20530
20804
  },
20531
20805
  onGetDirectoryTree: async (treePath, maxDepth) => {
20806
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".svamp", ".expo", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__", ".turbo", ".gradle", "target"]);
20807
+ const MAX_TREE_DEPTH = 8;
20808
+ const MAX_TREE_NODES = 2e4;
20809
+ const effectiveMaxDepth = Math.min(Math.max(0, Math.floor(Number(maxDepth)) || 0), MAX_TREE_DEPTH);
20810
+ let treeNodeCount = 0;
20532
20811
  async function buildTree(p, name, depth) {
20533
20812
  try {
20813
+ if (treeNodeCount >= MAX_TREE_NODES) return null;
20814
+ treeNodeCount++;
20534
20815
  const stats = await fs$1.stat(p);
20535
20816
  const node = {
20536
20817
  name,
@@ -20539,11 +20820,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
20539
20820
  size: stats.size,
20540
20821
  modified: stats.mtime.getTime()
20541
20822
  };
20542
- if (stats.isDirectory() && depth < maxDepth) {
20823
+ if (stats.isDirectory() && depth < effectiveMaxDepth) {
20543
20824
  const entries = await fs$1.readdir(p, { withFileTypes: true });
20544
20825
  const children = [];
20545
20826
  await Promise.all(entries.map(async (entry) => {
20546
20827
  if (entry.isSymbolicLink()) return;
20828
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) return;
20829
+ if (treeNodeCount >= MAX_TREE_NODES) return;
20547
20830
  const childPath = join(p, entry.name);
20548
20831
  const childNode = await buildTree(childPath, entry.name, depth + 1);
20549
20832
  if (childNode) children.push(childNode);
@@ -21434,7 +21717,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21434
21717
  }
21435
21718
  if (persistedSessions.length > 0) {
21436
21719
  try {
21437
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BaMR97BE.mjs');
21720
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C6MSkqS9.mjs');
21438
21721
  await awaitClaudeVersionReady();
21439
21722
  } catch {
21440
21723
  }
@@ -21639,7 +21922,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21639
21922
  const PING_TIMEOUT_MS = 15e3;
21640
21923
  const POST_RECONNECT_GRACE_MS = 2e4;
21641
21924
  const RECONNECT_JITTER_MS = 2500;
21642
- const { WorkflowScheduler } = await import('./scheduler-Dia9Akaw.mjs');
21925
+ const { WorkflowScheduler } = await import('./scheduler-CurN4UtP.mjs');
21643
21926
  const workflowProjectRoots = () => {
21644
21927
  const dirs = /* @__PURE__ */ new Set();
21645
21928
  for (const s of pidToTrackedSession.values()) {
@@ -21928,6 +22211,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
21928
22211
  logger.log(`Cleaning up (source: ${source})...`);
21929
22212
  clearInterval(heartbeatInterval);
21930
22213
  clearInterval(workflowSchedulerInterval);
22214
+ clearInterval(backendAccountRefreshInterval);
21931
22215
  if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
21932
22216
  if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
21933
22217
  if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
@@ -22272,4 +22556,4 @@ var run = /*#__PURE__*/Object.freeze({
22272
22556
  writeStopMarker: writeStopMarker
22273
22557
  });
22274
22558
 
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 };
22559
+ 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 };