svamp-cli 0.2.331 → 0.2.332

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 (27) hide show
  1. package/dist/{adminCommands-DQ6TQAXa.mjs → adminCommands-BNGSC5oD.mjs} +1 -1
  2. package/dist/{agentCommands-CovUtOYv.mjs → agentCommands-DD47Krr1.mjs} +5 -5
  3. package/dist/{auth-CMWkQmps.mjs → auth-CE3Cnkn6.mjs} +1 -1
  4. package/dist/{cli-Cd_6Xp2x.mjs → cli-CdYVO7ce.mjs} +93 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-D2he7eNq.mjs → commands-09sPSIH-.mjs} +3 -3
  7. package/dist/{commands-DCTCLAPA.mjs → commands-14Tf0D6G.mjs} +3 -3
  8. package/dist/{commands-OwmAlnGb.mjs → commands-BIKekqQG.mjs} +1 -1
  9. package/dist/{commands-Bv52WaQ0.mjs → commands-BIx8s0JL.mjs} +11 -11
  10. package/dist/{commands-CWkWkrZF.mjs → commands-C2X-uElg.mjs} +1 -1
  11. package/dist/{commands-CpXKePYP.mjs → commands-CHKSUX7x.mjs} +3 -3
  12. package/dist/{commands-B-PLeiIB.mjs → commands-D7wwybeC.mjs} +8 -3
  13. package/dist/{commands-BCWHfbuw.mjs → commands-DXJlhBPk.mjs} +24 -11
  14. package/dist/{fleet-DrLOwe6r.mjs → fleet-bAe_gJfS.mjs} +2 -2
  15. package/dist/{headlessCli-CDjGYUpj.mjs → headlessCli-Bwg5pf3s.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{notifyCommands-DSRd8YaE.mjs → notifyCommands-Cc3wLAWW.mjs} +1 -1
  18. package/dist/package-CPQqs8ty.mjs +64 -0
  19. package/dist/{rpc-OmhzsDGP.mjs → rpc-Bv6_c-xW.mjs} +1 -1
  20. package/dist/{rpc-CVBhR9Cj.mjs → rpc-DObWvEis.mjs} +1 -1
  21. package/dist/{run-DcwSfoyP.mjs → run-CyyWhgFI.mjs} +1 -1
  22. package/dist/{run-CSNL62FQ.mjs → run-DGhVoN3s.mjs} +177 -56
  23. package/dist/{scheduler-DDw3YLzH.mjs → scheduler-BvtNZ0pl.mjs} +1 -1
  24. package/dist/{serveCommands-BQ0jhFUR.mjs → serveCommands-CEM708Ls.mjs} +10 -10
  25. package/dist/{sideband-S3ycJa01.mjs → sideband--9vWYqKA.mjs} +1 -1
  26. package/package.json +3 -3
  27. package/dist/package-C23qFmVz.mjs +0 -64
@@ -3738,7 +3738,7 @@ class ServeManager {
3738
3738
  /**
3739
3739
  * Get the public URL for a mount (mount-specific subdomain).
3740
3740
  * For `access: 'link'` mounts, the subdomain itself carries the capability —
3741
- * the tunnel registers a long random suffix (~128 bits of entropy), so the
3741
+ * the tunnel registers a long random suffix (~88 bits of entropy (randomBytes(11), 22 hex chars)), so the
3742
3742
  * URL is identical in shape to other tiers, just longer. Content serves
3743
3743
  * from the root, so HTML with absolute paths (`/style.css`, `/app.js`)
3744
3744
  * works unchanged.
@@ -8332,7 +8332,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8332
8332
  }
8333
8333
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
8334
8334
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
8335
- const { toolsForRole } = await import('./sideband-S3ycJa01.mjs');
8335
+ const { toolsForRole } = await import('./sideband--9vWYqKA.mjs');
8336
8336
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
8337
8337
  return fmt(r2);
8338
8338
  }
@@ -8421,6 +8421,33 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8421
8421
  };
8422
8422
  const _caps = statelessDispatchCaps();
8423
8423
  const statelessLimiter = new StatelessDispatchLimiter(_caps.concurrent, _caps.perSender);
8424
+ const MAX_RECEIVE_POLLS_PER_CHANNEL = Math.max(
8425
+ 1,
8426
+ Number(process.env.SVAMP_MAX_CHANNEL_RECEIVE_POLLS) || 16
8427
+ );
8428
+ const _receivePolls = /* @__PURE__ */ new Map();
8429
+ const acquireReceiveSlot = (channelId) => {
8430
+ const n = _receivePolls.get(channelId) || 0;
8431
+ if (n >= MAX_RECEIVE_POLLS_PER_CHANNEL) return false;
8432
+ _receivePolls.set(channelId, n + 1);
8433
+ return true;
8434
+ };
8435
+ const releaseReceiveSlot = (channelId) => {
8436
+ const n = (_receivePolls.get(channelId) || 1) - 1;
8437
+ if (n <= 0) _receivePolls.delete(channelId);
8438
+ else _receivePolls.set(channelId, n);
8439
+ };
8440
+ const _outboxCache = /* @__PURE__ */ new Map();
8441
+ const getOutbox = (dir) => {
8442
+ let ob = _outboxCache.get(dir);
8443
+ if (!ob) {
8444
+ ob = new ChannelOutbox(dir);
8445
+ _outboxCache.set(dir, ob);
8446
+ } else {
8447
+ ob.reload();
8448
+ }
8449
+ return ob;
8450
+ };
8424
8451
  const dispatchStateless = async (c, dir, kwargs, context) => {
8425
8452
  const u = context?.user;
8426
8453
  const r = resolveSender(c, {
@@ -8437,7 +8464,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8437
8464
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8438
8465
  }
8439
8466
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8440
- const { queryCore } = await import('./commands-CWkWkrZF.mjs');
8467
+ const { queryCore } = await import('./commands-C2X-uElg.mjs');
8441
8468
  const timeout = c.reply?.timeout_sec || 120;
8442
8469
  let result;
8443
8470
  let thrownSessionId;
@@ -8539,15 +8566,22 @@ ${d?.error || "not found"}`;
8539
8566
  const waitRaw = Number(kwargs.wait ?? 25);
8540
8567
  const waitSec = Number.isFinite(waitRaw) ? waitRaw : 25;
8541
8568
  const waitMs = Math.min(Math.max(0, waitSec * 1e3), 6e4);
8542
- const outbox = new ChannelOutbox(dir);
8543
- const deadline = Date.now() + waitMs;
8544
- for (; ; ) {
8545
- const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
8546
- if (replies.length || Date.now() >= deadline) {
8547
- return { ok: true, replies, cursor: outbox.cursor(c.id) };
8569
+ if (!acquireReceiveSlot(c.id)) {
8570
+ return { error: "busy: too many concurrent receive polls on this channel \u2014 retry shortly", retryable: true };
8571
+ }
8572
+ try {
8573
+ const outbox = getOutbox(dir);
8574
+ const deadline = Date.now() + waitMs;
8575
+ for (; ; ) {
8576
+ const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
8577
+ if (replies.length || Date.now() >= deadline) {
8578
+ return { ok: true, replies, cursor: outbox.cursor(c.id) };
8579
+ }
8580
+ await new Promise((resolve) => setTimeout(resolve, 500));
8581
+ outbox.reload();
8548
8582
  }
8549
- await new Promise((resolve) => setTimeout(resolve, 500));
8550
- outbox.reload();
8583
+ } finally {
8584
+ releaseReceiveSlot(c.id);
8551
8585
  }
8552
8586
  },
8553
8587
  // Safety-restricted file upload (#0093). Deny-by-default: only channels whose
@@ -15783,8 +15817,28 @@ const SENSITIVE_ENV_VARS = [
15783
15817
  "GH_TOKEN",
15784
15818
  "NPM_TOKEN",
15785
15819
  "DOCKER_PASSWORD",
15786
- "DOCKER_CONFIG"
15820
+ "DOCKER_CONFIG",
15821
+ // #1110: svamp's own persisted credentials (written by `svamp daemon codex-auth` /
15822
+ // `kimi-auth` / `wise-agent auth`, and by the generic LLM_* aliases).
15823
+ "OPENAI_API_KEY",
15824
+ "GEMINI_API_KEY",
15825
+ "XAI_API_KEY",
15826
+ "ANTHROPIC_ADMIN_KEY",
15827
+ "LLM_API_KEY",
15828
+ "CLOUDFLARE_API_TOKEN",
15829
+ "R2_ACCESS_KEY_ID",
15830
+ "R2_SECRET_ACCESS_KEY",
15831
+ "X_API_KEY"
15787
15832
  ];
15833
+ const CREDENTIAL_NAME_RE = /(^|_)(KEY|APIKEY|TOKEN|SECRET|PASSWORD|CREDENTIALS)$/;
15834
+ const CREDENTIAL_SWEEP_ALLOWLIST = /* @__PURE__ */ new Set([
15835
+ "ANTHROPIC_API_KEY",
15836
+ "ANTHROPIC_AUTH_TOKEN"
15837
+ ]);
15838
+ function isCredentialEnvName(key) {
15839
+ if (CREDENTIAL_SWEEP_ALLOWLIST.has(key)) return false;
15840
+ return CREDENTIAL_NAME_RE.test(key);
15841
+ }
15788
15842
  async function stageCredentialsForSharing(sessionId) {
15789
15843
  const realHome = homedir$1();
15790
15844
  const realClaudeDir = join$1(realHome, ".claude");
@@ -15858,6 +15912,9 @@ function sanitizeEnvForSharing(env) {
15858
15912
  for (const key of SENSITIVE_ENV_VARS) {
15859
15913
  delete sanitized[key];
15860
15914
  }
15915
+ for (const key of Object.keys(sanitized)) {
15916
+ if (isCredentialEnvName(key)) delete sanitized[key];
15917
+ }
15861
15918
  return sanitized;
15862
15919
  }
15863
15920
  async function sweepOrphanedStagedHomes(activeSessionIds) {
@@ -15900,6 +15957,7 @@ async function copyDirRecursive(src, dest) {
15900
15957
 
15901
15958
  var credentialStaging = /*#__PURE__*/Object.freeze({
15902
15959
  __proto__: null,
15960
+ isCredentialEnvName: isCredentialEnvName,
15903
15961
  sanitizeEnvForSharing: sanitizeEnvForSharing,
15904
15962
  stageCredentialsForSharing: stageCredentialsForSharing,
15905
15963
  sweepOrphanedStagedHomes: sweepOrphanedStagedHomes
@@ -17092,7 +17150,8 @@ function planLogPrune(files, policy = DEFAULT_LOG_PRUNE_POLICY, nowMs = Date.now
17092
17150
  const overCount = keepFiles > 0 ? perStart.slice(keepFiles) : perStart;
17093
17151
  const tooOld = maxAgeMs > 0 ? perStart.filter((f) => nowMs - f.mtimeMs > maxAgeMs) : [];
17094
17152
  const deleteFiles = [...new Set([...overCount, ...tooOld].map((f) => f.name))];
17095
- const truncateFiles = files.filter((f) => APPEND_ONLY_LOGS.includes(f.name) && !excluded.has(f.name)).filter((f) => policy.maxBytes > 0 && f.size > policy.maxBytes).map((f) => ({ name: f.name, size: f.size }));
17153
+ const doomed = new Set(deleteFiles);
17154
+ const truncateFiles = files.filter((f) => (APPEND_ONLY_LOGS.includes(f.name) || isPerStartLog(f.name)) && !doomed.has(f.name)).filter((f) => policy.maxBytes > 0 && f.size > policy.maxBytes).map((f) => ({ name: f.name, size: f.size }));
17096
17155
  return { deleteFiles, truncateFiles };
17097
17156
  }
17098
17157
  function resolveLogPrunePolicy(env = process.env) {
@@ -17114,6 +17173,40 @@ function tailSlice(buf, tailBytes) {
17114
17173
  return nl >= 0 && nl + 1 < cut.length ? cut.subarray(nl + 1) : cut;
17115
17174
  }
17116
17175
 
17176
+ const CARRIER_FIELDS = ["message", "error", "detail", "reason", "description"];
17177
+ function formatLogArg(a) {
17178
+ if (typeof a === "string") return a;
17179
+ if (a instanceof Error) return a.stack || `${a.name}: ${a.message}`;
17180
+ if (a === void 0) return "undefined";
17181
+ if (a === null) return "null";
17182
+ try {
17183
+ const s = JSON.stringify(a);
17184
+ if (s === void 0 || s === "{}") return String(a);
17185
+ return s;
17186
+ } catch {
17187
+ try {
17188
+ return String(a);
17189
+ } catch {
17190
+ return "[unserializable]";
17191
+ }
17192
+ }
17193
+ }
17194
+ function formatLogArgs(args) {
17195
+ return args.map(formatLogArg).join(" ");
17196
+ }
17197
+ function describeRejectionReason(reason) {
17198
+ if (typeof reason === "string") return reason;
17199
+ if (reason instanceof Error) return `${reason.name}: ${reason.message}`;
17200
+ if (reason && typeof reason === "object") {
17201
+ for (const f of CARRIER_FIELDS) {
17202
+ const v = reason[f];
17203
+ if (typeof v === "string" && v.trim() !== "") return v;
17204
+ if (v instanceof Error) return `${v.name}: ${v.message}`;
17205
+ }
17206
+ }
17207
+ return formatLogArg(reason);
17208
+ }
17209
+
17117
17210
  function isWorkflowEnabled(wf) {
17118
17211
  return wf.enabled !== false;
17119
17212
  }
@@ -17135,6 +17228,12 @@ function validateWorkflowName(name) {
17135
17228
  function workflowPath(projectRoot, name) {
17136
17229
  return join$1(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yaml`);
17137
17230
  }
17231
+ function resolveWorkflowPath(projectRoot, name) {
17232
+ const canonical = workflowPath(projectRoot, name);
17233
+ if (existsSync(canonical)) return canonical;
17234
+ const alt = join$1(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yml`);
17235
+ return existsSync(alt) ? alt : canonical;
17236
+ }
17138
17237
  function normalizeOn(on) {
17139
17238
  if (!on || typeof on !== "object") {
17140
17239
  return void 0;
@@ -17231,7 +17330,7 @@ function listWorkflows(projectRoot) {
17231
17330
  function getWorkflow(projectRoot, name) {
17232
17331
  let p;
17233
17332
  try {
17234
- p = workflowPath(projectRoot, name);
17333
+ p = resolveWorkflowPath(projectRoot, name);
17235
17334
  } catch {
17236
17335
  return null;
17237
17336
  }
@@ -17245,7 +17344,7 @@ function getWorkflow(projectRoot, name) {
17245
17344
  function rawWorkflow(projectRoot, name) {
17246
17345
  let p;
17247
17346
  try {
17248
- p = workflowPath(projectRoot, name);
17347
+ p = resolveWorkflowPath(projectRoot, name);
17249
17348
  } catch {
17250
17349
  return null;
17251
17350
  }
@@ -17258,6 +17357,13 @@ function saveWorkflow(projectRoot, wf) {
17258
17357
  const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
17259
17358
  writeFileSync$1(tmp, serializeWorkflow(wf));
17260
17359
  renameSync(tmp, path);
17360
+ const alt = join$1(dir, `${validateWorkflowName(wf.name)}.yml`);
17361
+ if (alt !== path && existsSync(alt)) {
17362
+ try {
17363
+ unlinkSync$1(alt);
17364
+ } catch {
17365
+ }
17366
+ }
17261
17367
  }
17262
17368
  function setWorkflowEnabled(projectRoot, name, enabled) {
17263
17369
  const wf = getWorkflow(projectRoot, name);
@@ -17269,7 +17375,7 @@ function setWorkflowEnabled(projectRoot, name, enabled) {
17269
17375
  return next;
17270
17376
  }
17271
17377
  function removeWorkflow(projectRoot, name) {
17272
- const p = workflowPath(projectRoot, name);
17378
+ const p = resolveWorkflowPath(projectRoot, name);
17273
17379
  if (!existsSync(p)) return false;
17274
17380
  try {
17275
17381
  unlinkSync$1(p);
@@ -19328,17 +19434,23 @@ function isLoopActive(directory, sessionId) {
19328
19434
  const s = readLoopState(directory, sessionId);
19329
19435
  return !!s && s.active !== false && s.phase !== "dormant" && !isTerminalLoopPhase(s.phase);
19330
19436
  }
19331
- function loopOwnerSession(directory, sessionId) {
19332
- const s = readLoopState(directory, sessionId);
19333
- if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return null;
19334
- return typeof s.session_id === "string" ? s.session_id : null;
19335
- }
19336
19437
  function isLoopActiveForSession(directory, sessionId) {
19337
19438
  const s = readLoopState(directory, sessionId);
19338
19439
  if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return false;
19339
19440
  if (typeof s.session_id !== "string") return true;
19340
19441
  return s.session_id === sessionId;
19341
19442
  }
19443
+ function loopExistsForSession(directory, sessionId) {
19444
+ const s = readLoopState(directory, sessionId);
19445
+ if (!s || s.active === false || isTerminalLoopPhase(s.phase)) return false;
19446
+ if (typeof s.session_id !== "string") return true;
19447
+ return s.session_id === sessionId;
19448
+ }
19449
+ function loopOwnerSessionIncludingDormant(directory, sessionId) {
19450
+ const s = readLoopState(directory, sessionId);
19451
+ if (!s || s.active === false || isTerminalLoopPhase(s.phase)) return null;
19452
+ return typeof s.session_id === "string" ? s.session_id : null;
19453
+ }
19342
19454
  function isLoopArmedForSession(directory, sessionId) {
19343
19455
  const s = readLoopState(directory, sessionId);
19344
19456
  if (!s || s.active === false || s.phase !== "dormant") return false;
@@ -20052,7 +20164,7 @@ function ensureHomeDir() {
20052
20164
  mkdirSync(LOGS_DIR, { recursive: true });
20053
20165
  }
20054
20166
  }
20055
- function pruneDaemonLogs() {
20167
+ function pruneDaemonLogs(exclude = []) {
20056
20168
  try {
20057
20169
  const policy = resolveLogPrunePolicy(process.env);
20058
20170
  const entries = readdirSync$1(LOGS_DIR).map((name) => {
@@ -20063,7 +20175,7 @@ function pruneDaemonLogs() {
20063
20175
  return null;
20064
20176
  }
20065
20177
  }).filter((e) => e !== null);
20066
- const plan = planLogPrune(entries, policy);
20178
+ const plan = planLogPrune(entries, policy, Date.now(), exclude);
20067
20179
  for (const name of plan.deleteFiles) {
20068
20180
  try {
20069
20181
  unlinkSync(join(LOGS_DIR, name));
@@ -20085,10 +20197,13 @@ function createLogger() {
20085
20197
  ensureHomeDir();
20086
20198
  pruneDaemonLogs();
20087
20199
  const logFile = join(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
20200
+ const sweepMs = Math.max(6e4, Number(process.env.SVAMP_LOG_PRUNE_INTERVAL_MS) || 36e5);
20201
+ const sweepTimer = setInterval(() => pruneDaemonLogs([basename$1(logFile)]), sweepMs);
20202
+ if (typeof sweepTimer.unref === "function") sweepTimer.unref();
20088
20203
  return {
20089
20204
  logFilePath: logFile,
20090
20205
  log: (...args) => {
20091
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
20206
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${formatLogArgs(args)}
20092
20207
  `;
20093
20208
  fs$1.appendFile(logFile, line).catch(() => {
20094
20209
  });
@@ -20097,7 +20212,7 @@ function createLogger() {
20097
20212
  }
20098
20213
  },
20099
20214
  error: (...args) => {
20100
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
20215
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${formatLogArgs(args)}
20101
20216
  `;
20102
20217
  fs$1.appendFile(logFile, line).catch(() => {
20103
20218
  });
@@ -20254,7 +20369,7 @@ async function startDaemon(options) {
20254
20369
  const UNHANDLED_REJECTION_WINDOW_MS = 6e4;
20255
20370
  process.on("unhandledRejection", (reason) => {
20256
20371
  if (shutdownRequested) return;
20257
- const msg = String(reason);
20372
+ const msg = describeRejectionReason(reason);
20258
20373
  logger.error("Unhandled rejection:", reason);
20259
20374
  const isTransient = TRANSIENT_REJECTION_PATTERNS.some((p) => msg.toLowerCase().includes(p.toLowerCase()));
20260
20375
  const isSessionLoss = msg.toLowerCase().includes("session does not exist");
@@ -20383,7 +20498,7 @@ async function startDaemon(options) {
20383
20498
  try {
20384
20499
  const dir = loadSessionIndex()[sessionId]?.directory;
20385
20500
  if (!dir) return;
20386
- const { reconcileServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20501
+ const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20387
20502
  const configPath = getSvampConfigPath(dir, sessionId);
20388
20503
  const config = readSvampConfig(configPath);
20389
20504
  const entries = Array.from(urls.entries());
@@ -20405,7 +20520,7 @@ async function startDaemon(options) {
20405
20520
  try {
20406
20521
  const dir = loadSessionIndex()[sessionId]?.directory;
20407
20522
  if (!dir) return;
20408
- const { reconcileServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20523
+ const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20409
20524
  const configPath = getSvampConfigPath(dir, sessionId);
20410
20525
  const config = readSvampConfig(configPath);
20411
20526
  const incoming = [{
@@ -20426,7 +20541,7 @@ async function startDaemon(options) {
20426
20541
  try {
20427
20542
  const dir = loadSessionIndex()[sessionId]?.directory;
20428
20543
  if (!dir) return;
20429
- const { dropServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20544
+ const { dropServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20430
20545
  const configPath = getSvampConfigPath(dir, sessionId);
20431
20546
  const config = readSvampConfig(configPath);
20432
20547
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -22879,11 +22994,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22879
22994
  });
22880
22995
  },
22881
22996
  onIssue: async (params) => {
22882
- const { issueRpc } = await import('./rpc-CVBhR9Cj.mjs');
22997
+ const { issueRpc } = await import('./rpc-DObWvEis.mjs');
22883
22998
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22884
22999
  },
22885
23000
  onWorkflow: async (params) => {
22886
- const { workflowRpc } = await import('./rpc-OmhzsDGP.mjs');
23001
+ const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
22887
23002
  return workflowRpc(params?.cwd || directory, params || {});
22888
23003
  },
22889
23004
  onRipgrep: async (args, cwd) => {
@@ -23108,8 +23223,21 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23108
23223
  verify_errors: 0,
23109
23224
  // fresh verification budget on re-arm
23110
23225
  stall_hinted: false,
23111
- resumed_at: Date.now()
23226
+ resumed_at: Date.now(),
23112
23227
  // #0156: re-arm the stall hint
23228
+ // A re-arm starts a NEW work batch, so the previous batch's progress
23229
+ // baseline must not carry over. assessLoopProgress tracks a MONOTONIC
23230
+ // MINIMUM over the retained history, and a durable loop only parks
23231
+ // dormant once the oracle reads 0 pending — so the old tail is
23232
+ // `pending: 0` and best === 0 forever. Without this reset the first
23233
+ // new marker (pending >= 1) can never beat it, the loop reads STUCK
23234
+ // from checkpoint 3 onward while it is genuinely resolving issues,
23235
+ // `auto_resumes` never resets (updateLoopProgress keys that off the
23236
+ // same signal), and the finite #0958 cap burns down to a terminal
23237
+ // gave_up. That contradicts the invariant that a slow-but-PRODUCTIVE
23238
+ // loop is never killed by the cap.
23239
+ progress_history: [],
23240
+ auto_resumes: 0
23113
23241
  });
23114
23242
  } catch {
23115
23243
  }
@@ -23591,11 +23719,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23591
23719
  });
23592
23720
  },
23593
23721
  onIssue: async (params) => {
23594
- const { issueRpc } = await import('./rpc-CVBhR9Cj.mjs');
23722
+ const { issueRpc } = await import('./rpc-DObWvEis.mjs');
23595
23723
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23596
23724
  },
23597
23725
  onWorkflow: async (params) => {
23598
- const { workflowRpc } = await import('./rpc-OmhzsDGP.mjs');
23726
+ const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
23599
23727
  return workflowRpc(params?.cwd || directory, params || {});
23600
23728
  },
23601
23729
  onRipgrep: async (args, cwd) => {
@@ -23938,7 +24066,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23938
24066
  };
23939
24067
  try {
23940
24068
  if (!ls.acp_budget_warned && acpCumTokens === 0 && (ledger?.turns || 0) >= 1 && ls.budget && (ls.budget.max_tokens || ls.budget.max_tokens_per_hour)) {
23941
- sessionService.pushMessage({ type: "message", message: "\u26A0\uFE0F A token/rate cost cap (--max-tokens / --max-tokens-per-hour) is INERT for this agent \u2014 it exposes no token usage, so this loop is bounded only by --max (iterations) and --max-runtime-sec. Set one of those to cap cost.", level: "warning" }, "event");
24069
+ sessionService.pushMessage({ type: "message", message: "\u26A0\uFE0F The token/rate cost cap (--max-tokens-per-hour) is INERT for this agent \u2014 it exposes no token usage, so this loop is bounded only by --max (iterations) and --max-runtime-sec. Set one of those to cap cost.", level: "warning" }, "event");
23942
24070
  ls.acp_budget_warned = true;
23943
24071
  }
23944
24072
  const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
@@ -24259,7 +24387,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24259
24387
  const wasInMemory = teardownTrackedSession(sessionId);
24260
24388
  idleTriggerTracker.forget(sessionId);
24261
24389
  const markedArchived = markSessionAsArchived(sessionId);
24262
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24390
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24263
24391
  deactivateLoop(loopDir, sessionId);
24264
24392
  logger.log(`Deactivated loop for archived session ${sessionId}`);
24265
24393
  }
@@ -24317,7 +24445,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24317
24445
  teardownTrackedSession(sessionId);
24318
24446
  idleTriggerTracker.forget(sessionId);
24319
24447
  deletePersistedSession(sessionId);
24320
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24448
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24321
24449
  deactivateLoop(loopDir, sessionId);
24322
24450
  }
24323
24451
  logger.log(`Session ${sessionId} deleted`);
@@ -24658,7 +24786,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24658
24786
  try {
24659
24787
  for (const ent of readdirSync$1(join(p.directory, ".svamp"), { withFileTypes: true })) {
24660
24788
  if (!ent.isDirectory() || knownSessionIds.has(ent.name)) continue;
24661
- const owner = loopOwnerSession(p.directory, ent.name);
24789
+ const owner = loopOwnerSessionIncludingDormant(p.directory, ent.name);
24662
24790
  if (owner && !knownSessionIds.has(owner)) {
24663
24791
  deactivateLoop(p.directory, ent.name);
24664
24792
  logger.log(`[loop] Deactivated stale loop-state for ${ent.name} in ${p.directory} (owner no longer known)`);
@@ -24666,7 +24794,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24666
24794
  }
24667
24795
  } catch {
24668
24796
  }
24669
- const legacyOwner = loopOwnerSession(p.directory);
24797
+ const legacyOwner = loopOwnerSessionIncludingDormant(p.directory);
24670
24798
  if (legacyOwner && !knownSessionIds.has(legacyOwner)) {
24671
24799
  deactivateLoop(p.directory);
24672
24800
  logger.log(`[loop] Deactivated stale legacy loop-state in ${p.directory} (owner session ${legacyOwner} no longer known)`);
@@ -24727,7 +24855,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24727
24855
  try {
24728
24856
  const lp = join(getLoopDir(persisted.directory, persisted.sessionId), "loop-state.json");
24729
24857
  const cur = readLoopState(persisted.directory, persisted.sessionId);
24730
- if (cur) atomicWriteLoopState(lp, { ...cur, active: true, phase: "continue", completed_at: void 0, verify_errors: 0, resumed_at: Date.now() });
24858
+ if (cur) atomicWriteLoopState(lp, { ...cur, active: true, phase: "continue", completed_at: void 0, verify_errors: 0, resumed_at: Date.now(), progress_history: [], auto_resumes: 0 });
24731
24859
  } catch {
24732
24860
  }
24733
24861
  }
@@ -24865,13 +24993,16 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24865
24993
  }
24866
24994
  if (sessionsToLoopResume.length > 0 && !options?.noAutoContinue) {
24867
24995
  logger.log(`Resuming loop for ${sessionsToLoopResume.length} session(s)...`);
24868
- for (const { sessionId, directory: sessDir } of sessionsToLoopResume) {
24996
+ const LOOP_RESUME_STEP_MS = Number(process.env.SVAMP_AUTO_CONTINUE_STEP_MS) || 300;
24997
+ const LOOP_RESUME_MAX_SPREAD_MS = Number(process.env.SVAMP_AUTO_CONTINUE_MAX_SPREAD_MS) || 6e4;
24998
+ sessionsToLoopResume.forEach(({ sessionId, directory: sessDir }, loopResumeIndex) => {
24999
+ const loopResumeDelay = 2e3 + Math.min(loopResumeIndex * LOOP_RESUME_STEP_MS, LOOP_RESUME_MAX_SPREAD_MS) + Math.floor(Math.random() * LOOP_RESUME_STEP_MS);
24869
25000
  try {
24870
25001
  const tracked = Array.from(pidToTrackedSession.values()).find((s) => s.svampSessionId === sessionId);
24871
25002
  const rpc = tracked?.sessionRPCHandlers;
24872
25003
  if (!rpc) {
24873
25004
  logger.log(`Session ${sessionId} RPC handlers not found for loop resume`);
24874
- continue;
25005
+ return;
24875
25006
  }
24876
25007
  const loopTurnFingerprint = () => {
24877
25008
  try {
@@ -24897,7 +25028,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24897
25028
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
24898
25029
  const fpBefore = loopTurnFingerprint();
24899
25030
  await sendResume("");
24900
- const graceMs = Math.max(5e3, Number(process.env.SVAMP_LOOP_RESUME_WATCHDOG_MS) || 45e3);
25031
+ const graceMs = Math.max(5e3, Number(process.env.SVAMP_LOOP_RESUME_WATCHDOG_MS) || 45e3) + Math.min(loopResumeIndex * LOOP_RESUME_STEP_MS, LOOP_RESUME_MAX_SPREAD_MS);
24901
25032
  setTimeout(() => {
24902
25033
  try {
24903
25034
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
@@ -24914,11 +25045,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24914
25045
  } catch (err) {
24915
25046
  logger.log(`Failed to resume loop for session ${sessionId}: ${err.message}`);
24916
25047
  }
24917
- }, 2e3);
25048
+ }, loopResumeDelay);
24918
25049
  } catch (err) {
24919
25050
  logger.log(`Failed to find session service for loop resume ${sessionId}: ${err.message}`);
24920
25051
  }
24921
- }
25052
+ });
24922
25053
  }
24923
25054
  (async () => {
24924
25055
  try {
@@ -24928,27 +25059,17 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24928
25059
  logger.log(`[ARTIFACT SYNC] Background init failed: ${err.message}`);
24929
25060
  }
24930
25061
  })();
24931
- let appToken;
24932
- try {
24933
- appToken = await server.generateToken({});
24934
- logger.log(`App connection token generated`);
24935
- } catch (err) {
24936
- logger.log("Could not generate token (server may not support it):", err);
24937
- }
24938
25062
  console.log("Svamp daemon started successfully!");
24939
25063
  console.log(` Machine ID: ${machineId}`);
24940
25064
  console.log(` Hypha server: ${hyphaServerUrl}`);
24941
25065
  console.log(` Workspace: ${server.config.workspace}`);
24942
- if (appToken) {
24943
- console.log(` App token: ${appToken}`);
24944
- }
24945
25066
  console.log(` Service: svamp-machine-${machineId}`);
24946
25067
  console.log(` Log file: ${logger.logFilePath}`);
24947
25068
  const HEARTBEAT_INTERVAL_MS = 1e4;
24948
25069
  const PING_TIMEOUT_MS = 15e3;
24949
25070
  const POST_RECONNECT_GRACE_MS = 2e4;
24950
25071
  const RECONNECT_JITTER_MS = 2500;
24951
- const { WorkflowScheduler } = await import('./scheduler-DDw3YLzH.mjs');
25072
+ const { WorkflowScheduler } = await import('./scheduler-BvtNZ0pl.mjs');
24952
25073
  const workflowProjectRoots = () => {
24953
25074
  const dirs = /* @__PURE__ */ new Set();
24954
25075
  for (const s of pidToTrackedSession.values()) {
@@ -1,4 +1,4 @@
1
- import { f as resolveProjectRoot, D as listWorkflows, E as isWorkflowEnabled, F as workflowSchedules, G as inZone, w as runWorkflow, H as cronMatches } from './run-CSNL62FQ.mjs';
1
+ import { f as resolveProjectRoot, D as listWorkflows, E as isWorkflowEnabled, F as workflowSchedules, G as inZone, w as runWorkflow, H as cronMatches } from './run-DGhVoN3s.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
- import { w as wantsHelp } from './cli-Cd_6Xp2x.mjs';
3
- import './run-CSNL62FQ.mjs';
2
+ import { w as wantsHelp } from './cli-CdYVO7ce.mjs';
3
+ import './run-DGhVoN3s.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -77,7 +77,7 @@ async function handleServeCommand() {
77
77
  }
78
78
  }
79
79
  async function serveAdd(args, machineId) {
80
- const { connectAndGetMachine } = await import('./commands-CWkWkrZF.mjs');
80
+ const { connectAndGetMachine } = await import('./commands-C2X-uElg.mjs');
81
81
  const pos = positionalArgs(args);
82
82
  const name = pos[0];
83
83
  if (!name) {
@@ -110,7 +110,7 @@ async function serveAdd(args, machineId) {
110
110
  }
111
111
  if (sessionId && result?.url) {
112
112
  try {
113
- const { autoAddSessionLink } = await import('./agentCommands-CovUtOYv.mjs');
113
+ const { autoAddSessionLink } = await import('./agentCommands-DD47Krr1.mjs');
114
114
  autoAddSessionLink(String(result.url), name, void 0, { kind: "serve", name });
115
115
  } catch {
116
116
  }
@@ -124,7 +124,7 @@ async function serveAdd(args, machineId) {
124
124
  }
125
125
  }
126
126
  async function serveApply(args, machineId) {
127
- const { connectAndGetMachine } = await import('./commands-CWkWkrZF.mjs');
127
+ const { connectAndGetMachine } = await import('./commands-C2X-uElg.mjs');
128
128
  const fs = await import('fs');
129
129
  const yaml = await import('yaml');
130
130
  const file = positionalArgs(args)[0];
@@ -209,7 +209,7 @@ async function serveApply(args, machineId) {
209
209
  console.log(`URL: ${result.url}`);
210
210
  if (params.sessionId && result?.url) {
211
211
  try {
212
- const { autoAddSessionLink } = await import('./agentCommands-CovUtOYv.mjs');
212
+ const { autoAddSessionLink } = await import('./agentCommands-DD47Krr1.mjs');
213
213
  const prevSession = process.env.SVAMP_SESSION_ID;
214
214
  process.env.SVAMP_SESSION_ID = String(params.sessionId);
215
215
  try {
@@ -230,7 +230,7 @@ async function serveApply(args, machineId) {
230
230
  }
231
231
  }
232
232
  async function serveRemove(args, machineId) {
233
- const { connectAndGetMachine } = await import('./commands-CWkWkrZF.mjs');
233
+ const { connectAndGetMachine } = await import('./commands-C2X-uElg.mjs');
234
234
  const pos = positionalArgs(args);
235
235
  const name = pos[0];
236
236
  if (!name) {
@@ -240,7 +240,7 @@ async function serveRemove(args, machineId) {
240
240
  const { machine, server } = await connectAndGetMachine(machineId);
241
241
  try {
242
242
  await machine.serveRemove({ name });
243
- const { removeSessionLinkByService } = await import('./agentCommands-CovUtOYv.mjs');
243
+ const { removeSessionLinkByService } = await import('./agentCommands-DD47Krr1.mjs');
244
244
  removeSessionLinkByService("serve", name);
245
245
  console.log(`Mount '${name}' removed.`);
246
246
  } catch (err) {
@@ -252,7 +252,7 @@ async function serveRemove(args, machineId) {
252
252
  }
253
253
  }
254
254
  async function serveList(args, machineId) {
255
- const { connectAndGetMachine } = await import('./commands-CWkWkrZF.mjs');
255
+ const { connectAndGetMachine } = await import('./commands-C2X-uElg.mjs');
256
256
  const all = hasFlag(args, "--all", "-a");
257
257
  const json = hasFlag(args, "--json");
258
258
  const sessionId = getFlag(args, "--session");
@@ -286,7 +286,7 @@ async function serveList(args, machineId) {
286
286
  }
287
287
  }
288
288
  async function serveInfo(machineId) {
289
- const { connectAndGetMachine } = await import('./commands-CWkWkrZF.mjs');
289
+ const { connectAndGetMachine } = await import('./commands-C2X-uElg.mjs');
290
290
  const { machine, server } = await connectAndGetMachine(machineId);
291
291
  try {
292
292
  const info = await machine.serveInfo();
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-CSNL62FQ.mjs';
1
+ import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-DGhVoN3s.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';