svamp-cli 0.2.330 → 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-CeAsjKoA.mjs → adminCommands-BNGSC5oD.mjs} +1 -1
  2. package/dist/{agentCommands-CDj6yTgX.mjs → agentCommands-DD47Krr1.mjs} +5 -5
  3. package/dist/{auth-CtiKOzhL.mjs → auth-CE3Cnkn6.mjs} +1 -1
  4. package/dist/{cli-CZ4D7AII.mjs → cli-CdYVO7ce.mjs} +93 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-BRTTViag.mjs → commands-09sPSIH-.mjs} +3 -3
  7. package/dist/{commands-BFkBvM5j.mjs → commands-14Tf0D6G.mjs} +3 -3
  8. package/dist/{commands-DkIbjVr6.mjs → commands-BIKekqQG.mjs} +1 -1
  9. package/dist/{commands-DiBOzYv7.mjs → commands-BIx8s0JL.mjs} +11 -11
  10. package/dist/{commands-BOBRaVaF.mjs → commands-C2X-uElg.mjs} +1 -1
  11. package/dist/{commands-BXjej1wP.mjs → commands-CHKSUX7x.mjs} +3 -3
  12. package/dist/{commands-71PkPL_M.mjs → commands-D7wwybeC.mjs} +8 -3
  13. package/dist/{commands-WJbeIMNx.mjs → commands-DXJlhBPk.mjs} +24 -11
  14. package/dist/{fleet-DmTm6tsY.mjs → fleet-bAe_gJfS.mjs} +2 -2
  15. package/dist/{headlessCli-DqlYDREf.mjs → headlessCli-Bwg5pf3s.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{notifyCommands-WZDIxM_h.mjs → notifyCommands-Cc3wLAWW.mjs} +1 -1
  18. package/dist/package-CPQqs8ty.mjs +64 -0
  19. package/dist/{rpc-tO-T8OWN.mjs → rpc-Bv6_c-xW.mjs} +1 -1
  20. package/dist/{rpc-CFb-c-Nu.mjs → rpc-DObWvEis.mjs} +1 -1
  21. package/dist/{run-CA3J4pGr.mjs → run-CyyWhgFI.mjs} +1 -1
  22. package/dist/{run-ON_xcuFF.mjs → run-DGhVoN3s.mjs} +189 -56
  23. package/dist/{scheduler-DtzofW-S.mjs → scheduler-BvtNZ0pl.mjs} +1 -1
  24. package/dist/{serveCommands-DBz696oe.mjs → serveCommands-CEM708Ls.mjs} +10 -10
  25. package/dist/{sideband-CDQJ24BM.mjs → sideband--9vWYqKA.mjs} +1 -1
  26. package/package.json +3 -3
  27. package/dist/package-CT65p79F.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-CDQJ24BM.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-BOBRaVaF.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);
@@ -18271,6 +18377,14 @@ function isCompactCommand(text) {
18271
18377
  const t = text.trim();
18272
18378
  return t === "/compact" || t.startsWith("/compact ") || t.startsWith("/compact\n") || t.startsWith("/compact ");
18273
18379
  }
18380
+ const COMPACT_CONTINUATION_SENTINEL = "This session is being continued from a previous conversation that ran out of context.";
18381
+ function isCompactionArtifactText(text) {
18382
+ if (!text) return false;
18383
+ const t = text.trimStart();
18384
+ if (t.startsWith(COMPACT_CONTINUATION_SENTINEL)) return true;
18385
+ if (t.trimEnd().startsWith("<local-command-stdout>")) return true;
18386
+ return false;
18387
+ }
18274
18388
  function describeCompaction(meta) {
18275
18389
  const trigger = meta?.trigger === "auto" ? "auto" : "manual";
18276
18390
  const pre = typeof meta?.pre_tokens === "number" ? meta.pre_tokens : void 0;
@@ -19320,17 +19434,23 @@ function isLoopActive(directory, sessionId) {
19320
19434
  const s = readLoopState(directory, sessionId);
19321
19435
  return !!s && s.active !== false && s.phase !== "dormant" && !isTerminalLoopPhase(s.phase);
19322
19436
  }
19323
- function loopOwnerSession(directory, sessionId) {
19324
- const s = readLoopState(directory, sessionId);
19325
- if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return null;
19326
- return typeof s.session_id === "string" ? s.session_id : null;
19327
- }
19328
19437
  function isLoopActiveForSession(directory, sessionId) {
19329
19438
  const s = readLoopState(directory, sessionId);
19330
19439
  if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return false;
19331
19440
  if (typeof s.session_id !== "string") return true;
19332
19441
  return s.session_id === sessionId;
19333
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
+ }
19334
19454
  function isLoopArmedForSession(directory, sessionId) {
19335
19455
  const s = readLoopState(directory, sessionId);
19336
19456
  if (!s || s.active === false || s.phase !== "dormant") return false;
@@ -20044,7 +20164,7 @@ function ensureHomeDir() {
20044
20164
  mkdirSync(LOGS_DIR, { recursive: true });
20045
20165
  }
20046
20166
  }
20047
- function pruneDaemonLogs() {
20167
+ function pruneDaemonLogs(exclude = []) {
20048
20168
  try {
20049
20169
  const policy = resolveLogPrunePolicy(process.env);
20050
20170
  const entries = readdirSync$1(LOGS_DIR).map((name) => {
@@ -20055,7 +20175,7 @@ function pruneDaemonLogs() {
20055
20175
  return null;
20056
20176
  }
20057
20177
  }).filter((e) => e !== null);
20058
- const plan = planLogPrune(entries, policy);
20178
+ const plan = planLogPrune(entries, policy, Date.now(), exclude);
20059
20179
  for (const name of plan.deleteFiles) {
20060
20180
  try {
20061
20181
  unlinkSync(join(LOGS_DIR, name));
@@ -20077,10 +20197,13 @@ function createLogger() {
20077
20197
  ensureHomeDir();
20078
20198
  pruneDaemonLogs();
20079
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();
20080
20203
  return {
20081
20204
  logFilePath: logFile,
20082
20205
  log: (...args) => {
20083
- 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)}
20084
20207
  `;
20085
20208
  fs$1.appendFile(logFile, line).catch(() => {
20086
20209
  });
@@ -20089,7 +20212,7 @@ function createLogger() {
20089
20212
  }
20090
20213
  },
20091
20214
  error: (...args) => {
20092
- 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)}
20093
20216
  `;
20094
20217
  fs$1.appendFile(logFile, line).catch(() => {
20095
20218
  });
@@ -20246,7 +20369,7 @@ async function startDaemon(options) {
20246
20369
  const UNHANDLED_REJECTION_WINDOW_MS = 6e4;
20247
20370
  process.on("unhandledRejection", (reason) => {
20248
20371
  if (shutdownRequested) return;
20249
- const msg = String(reason);
20372
+ const msg = describeRejectionReason(reason);
20250
20373
  logger.error("Unhandled rejection:", reason);
20251
20374
  const isTransient = TRANSIENT_REJECTION_PATTERNS.some((p) => msg.toLowerCase().includes(p.toLowerCase()));
20252
20375
  const isSessionLoss = msg.toLowerCase().includes("session does not exist");
@@ -20375,7 +20498,7 @@ async function startDaemon(options) {
20375
20498
  try {
20376
20499
  const dir = loadSessionIndex()[sessionId]?.directory;
20377
20500
  if (!dir) return;
20378
- const { reconcileServiceLinks } = await import('./agentCommands-CDj6yTgX.mjs');
20501
+ const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20379
20502
  const configPath = getSvampConfigPath(dir, sessionId);
20380
20503
  const config = readSvampConfig(configPath);
20381
20504
  const entries = Array.from(urls.entries());
@@ -20397,7 +20520,7 @@ async function startDaemon(options) {
20397
20520
  try {
20398
20521
  const dir = loadSessionIndex()[sessionId]?.directory;
20399
20522
  if (!dir) return;
20400
- const { reconcileServiceLinks } = await import('./agentCommands-CDj6yTgX.mjs');
20523
+ const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20401
20524
  const configPath = getSvampConfigPath(dir, sessionId);
20402
20525
  const config = readSvampConfig(configPath);
20403
20526
  const incoming = [{
@@ -20418,7 +20541,7 @@ async function startDaemon(options) {
20418
20541
  try {
20419
20542
  const dir = loadSessionIndex()[sessionId]?.directory;
20420
20543
  if (!dir) return;
20421
- const { dropServiceLinks } = await import('./agentCommands-CDj6yTgX.mjs');
20544
+ const { dropServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20422
20545
  const configPath = getSvampConfigPath(dir, sessionId);
20423
20546
  const config = readSvampConfig(configPath);
20424
20547
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -22082,6 +22205,10 @@ ${parts.join("\n")}`);
22082
22205
  consecutiveOverloadRetries = 0;
22083
22206
  }
22084
22207
  sessionService.pushMessage(msg, "agent");
22208
+ } else if (msg.type === "user" && typeof msg.message?.content === "string" && isCompactionArtifactText(msg.message.content)) {
22209
+ logger.log(
22210
+ `[Session ${sessionId}] Suppressed /compact synthetic user artifact (not stored)`
22211
+ );
22085
22212
  } else {
22086
22213
  sessionService.pushMessage(msg, "agent");
22087
22214
  }
@@ -22867,11 +22994,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22867
22994
  });
22868
22995
  },
22869
22996
  onIssue: async (params) => {
22870
- const { issueRpc } = await import('./rpc-CFb-c-Nu.mjs');
22997
+ const { issueRpc } = await import('./rpc-DObWvEis.mjs');
22871
22998
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22872
22999
  },
22873
23000
  onWorkflow: async (params) => {
22874
- const { workflowRpc } = await import('./rpc-tO-T8OWN.mjs');
23001
+ const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
22875
23002
  return workflowRpc(params?.cwd || directory, params || {});
22876
23003
  },
22877
23004
  onRipgrep: async (args, cwd) => {
@@ -23096,8 +23223,21 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23096
23223
  verify_errors: 0,
23097
23224
  // fresh verification budget on re-arm
23098
23225
  stall_hinted: false,
23099
- resumed_at: Date.now()
23226
+ resumed_at: Date.now(),
23100
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
23101
23241
  });
23102
23242
  } catch {
23103
23243
  }
@@ -23579,11 +23719,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23579
23719
  });
23580
23720
  },
23581
23721
  onIssue: async (params) => {
23582
- const { issueRpc } = await import('./rpc-CFb-c-Nu.mjs');
23722
+ const { issueRpc } = await import('./rpc-DObWvEis.mjs');
23583
23723
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23584
23724
  },
23585
23725
  onWorkflow: async (params) => {
23586
- const { workflowRpc } = await import('./rpc-tO-T8OWN.mjs');
23726
+ const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
23587
23727
  return workflowRpc(params?.cwd || directory, params || {});
23588
23728
  },
23589
23729
  onRipgrep: async (args, cwd) => {
@@ -23926,7 +24066,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23926
24066
  };
23927
24067
  try {
23928
24068
  if (!ls.acp_budget_warned && acpCumTokens === 0 && (ledger?.turns || 0) >= 1 && ls.budget && (ls.budget.max_tokens || ls.budget.max_tokens_per_hour)) {
23929
- 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");
23930
24070
  ls.acp_budget_warned = true;
23931
24071
  }
23932
24072
  const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
@@ -24247,7 +24387,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24247
24387
  const wasInMemory = teardownTrackedSession(sessionId);
24248
24388
  idleTriggerTracker.forget(sessionId);
24249
24389
  const markedArchived = markSessionAsArchived(sessionId);
24250
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24390
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24251
24391
  deactivateLoop(loopDir, sessionId);
24252
24392
  logger.log(`Deactivated loop for archived session ${sessionId}`);
24253
24393
  }
@@ -24305,7 +24445,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24305
24445
  teardownTrackedSession(sessionId);
24306
24446
  idleTriggerTracker.forget(sessionId);
24307
24447
  deletePersistedSession(sessionId);
24308
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24448
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24309
24449
  deactivateLoop(loopDir, sessionId);
24310
24450
  }
24311
24451
  logger.log(`Session ${sessionId} deleted`);
@@ -24646,7 +24786,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24646
24786
  try {
24647
24787
  for (const ent of readdirSync$1(join(p.directory, ".svamp"), { withFileTypes: true })) {
24648
24788
  if (!ent.isDirectory() || knownSessionIds.has(ent.name)) continue;
24649
- const owner = loopOwnerSession(p.directory, ent.name);
24789
+ const owner = loopOwnerSessionIncludingDormant(p.directory, ent.name);
24650
24790
  if (owner && !knownSessionIds.has(owner)) {
24651
24791
  deactivateLoop(p.directory, ent.name);
24652
24792
  logger.log(`[loop] Deactivated stale loop-state for ${ent.name} in ${p.directory} (owner no longer known)`);
@@ -24654,7 +24794,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24654
24794
  }
24655
24795
  } catch {
24656
24796
  }
24657
- const legacyOwner = loopOwnerSession(p.directory);
24797
+ const legacyOwner = loopOwnerSessionIncludingDormant(p.directory);
24658
24798
  if (legacyOwner && !knownSessionIds.has(legacyOwner)) {
24659
24799
  deactivateLoop(p.directory);
24660
24800
  logger.log(`[loop] Deactivated stale legacy loop-state in ${p.directory} (owner session ${legacyOwner} no longer known)`);
@@ -24715,7 +24855,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24715
24855
  try {
24716
24856
  const lp = join(getLoopDir(persisted.directory, persisted.sessionId), "loop-state.json");
24717
24857
  const cur = readLoopState(persisted.directory, persisted.sessionId);
24718
- 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 });
24719
24859
  } catch {
24720
24860
  }
24721
24861
  }
@@ -24853,13 +24993,16 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24853
24993
  }
24854
24994
  if (sessionsToLoopResume.length > 0 && !options?.noAutoContinue) {
24855
24995
  logger.log(`Resuming loop for ${sessionsToLoopResume.length} session(s)...`);
24856
- 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);
24857
25000
  try {
24858
25001
  const tracked = Array.from(pidToTrackedSession.values()).find((s) => s.svampSessionId === sessionId);
24859
25002
  const rpc = tracked?.sessionRPCHandlers;
24860
25003
  if (!rpc) {
24861
25004
  logger.log(`Session ${sessionId} RPC handlers not found for loop resume`);
24862
- continue;
25005
+ return;
24863
25006
  }
24864
25007
  const loopTurnFingerprint = () => {
24865
25008
  try {
@@ -24885,7 +25028,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24885
25028
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
24886
25029
  const fpBefore = loopTurnFingerprint();
24887
25030
  await sendResume("");
24888
- 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);
24889
25032
  setTimeout(() => {
24890
25033
  try {
24891
25034
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
@@ -24902,11 +25045,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24902
25045
  } catch (err) {
24903
25046
  logger.log(`Failed to resume loop for session ${sessionId}: ${err.message}`);
24904
25047
  }
24905
- }, 2e3);
25048
+ }, loopResumeDelay);
24906
25049
  } catch (err) {
24907
25050
  logger.log(`Failed to find session service for loop resume ${sessionId}: ${err.message}`);
24908
25051
  }
24909
- }
25052
+ });
24910
25053
  }
24911
25054
  (async () => {
24912
25055
  try {
@@ -24916,27 +25059,17 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24916
25059
  logger.log(`[ARTIFACT SYNC] Background init failed: ${err.message}`);
24917
25060
  }
24918
25061
  })();
24919
- let appToken;
24920
- try {
24921
- appToken = await server.generateToken({});
24922
- logger.log(`App connection token generated`);
24923
- } catch (err) {
24924
- logger.log("Could not generate token (server may not support it):", err);
24925
- }
24926
25062
  console.log("Svamp daemon started successfully!");
24927
25063
  console.log(` Machine ID: ${machineId}`);
24928
25064
  console.log(` Hypha server: ${hyphaServerUrl}`);
24929
25065
  console.log(` Workspace: ${server.config.workspace}`);
24930
- if (appToken) {
24931
- console.log(` App token: ${appToken}`);
24932
- }
24933
25066
  console.log(` Service: svamp-machine-${machineId}`);
24934
25067
  console.log(` Log file: ${logger.logFilePath}`);
24935
25068
  const HEARTBEAT_INTERVAL_MS = 1e4;
24936
25069
  const PING_TIMEOUT_MS = 15e3;
24937
25070
  const POST_RECONNECT_GRACE_MS = 2e4;
24938
25071
  const RECONNECT_JITTER_MS = 2500;
24939
- const { WorkflowScheduler } = await import('./scheduler-DtzofW-S.mjs');
25072
+ const { WorkflowScheduler } = await import('./scheduler-BvtNZ0pl.mjs');
24940
25073
  const workflowProjectRoots = () => {
24941
25074
  const dirs = /* @__PURE__ */ new Set();
24942
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-ON_xcuFF.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-CZ4D7AII.mjs';
3
- import './run-ON_xcuFF.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-BOBRaVaF.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-CDj6yTgX.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-BOBRaVaF.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-CDj6yTgX.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-BOBRaVaF.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-CDj6yTgX.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-BOBRaVaF.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-BOBRaVaF.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-ON_xcuFF.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';