svamp-cli 0.2.331 → 0.2.333

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-DQ6TQAXa.mjs → adminCommands-BMzwjOct.mjs} +1 -1
  2. package/dist/{agentCommands-CovUtOYv.mjs → agentCommands-BVz1k2R2.mjs} +35 -6
  3. package/dist/{auth-CMWkQmps.mjs → auth-D0v5RwMJ.mjs} +1 -1
  4. package/dist/{cli-Cd_6Xp2x.mjs → cli-DmjtuskT.mjs} +93 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-B-PLeiIB.mjs → commands-BOGd7CmP.mjs} +14 -4
  7. package/dist/{commands-CpXKePYP.mjs → commands-C3r_GhOb.mjs} +3 -3
  8. package/dist/{commands-D2he7eNq.mjs → commands-CPTWYXJD.mjs} +3 -3
  9. package/dist/{commands-DCTCLAPA.mjs → commands-CYgkxHsf.mjs} +3 -3
  10. package/dist/{commands-OwmAlnGb.mjs → commands-CwoMu1mu.mjs} +1 -1
  11. package/dist/{commands-CWkWkrZF.mjs → commands-DKouDroK.mjs} +1 -1
  12. package/dist/{commands-BCWHfbuw.mjs → commands-DcQMh7oz.mjs} +29 -18
  13. package/dist/{commands-Bv52WaQ0.mjs → commands-DmPwUOqX.mjs} +11 -11
  14. package/dist/{fleet-DrLOwe6r.mjs → fleet-5foIrEnM.mjs} +3 -3
  15. package/dist/{headlessCli-CDjGYUpj.mjs → headlessCli-BLZSVVlt.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{notifyCommands-DSRd8YaE.mjs → notifyCommands-DADOx-j_.mjs} +1 -1
  18. package/dist/package-By6uMuic.mjs +64 -0
  19. package/dist/{pinnedClaudeCode-BO0Qs_kE.mjs → pinnedClaudeCode-C43KnZ-Q.mjs} +27 -6
  20. package/dist/{rpc-OmhzsDGP.mjs → rpc-CoPRGZkJ.mjs} +2 -1
  21. package/dist/{rpc-CVBhR9Cj.mjs → rpc-UomHF39a.mjs} +13 -1
  22. package/dist/{run-DcwSfoyP.mjs → run-CkEjIO1m.mjs} +1 -1
  23. package/dist/{run-CSNL62FQ.mjs → run-PXdghhjy.mjs} +592 -147
  24. package/dist/{scheduler-DDw3YLzH.mjs → scheduler-gMWmsAgp.mjs} +1 -1
  25. package/dist/{serveCommands-BQ0jhFUR.mjs → serveCommands-Cklx2iuZ.mjs} +10 -10
  26. package/dist/{sideband-S3ycJa01.mjs → sideband-DXyAkIaB.mjs} +1 -1
  27. package/package.json +4 -4
  28. package/dist/package-C23qFmVz.mjs +0 -64
@@ -7,8 +7,8 @@ import path__default, { join, delimiter, resolve as resolve$1, dirname as dirnam
7
7
  import { fileURLToPath } from 'url';
8
8
  import { execFile, execSync, spawn, exec as exec$1 } from 'child_process';
9
9
  import * as crypto from 'crypto';
10
- import { randomUUID, createHash } from 'crypto';
11
- import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync as chmodSync$1, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, openSync, readSync, closeSync, unlinkSync as unlinkSync$1, writeSync } from 'node:fs';
10
+ import { randomUUID, createHash, randomBytes as randomBytes$1 } from 'crypto';
11
+ import { existsSync, readFileSync, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, chmodSync as chmodSync$1, rmSync as rmSync$1, cpSync, statSync, realpathSync, readdirSync, renameSync, appendFileSync, openSync, readSync, closeSync, unlinkSync as unlinkSync$1, fstatSync, writeSync } from 'node:fs';
12
12
  import { join as join$1, extname, resolve, sep, basename, dirname } from 'node:path';
13
13
  import { randomUUID as randomUUID$1, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
14
14
  import { exec, spawn as spawn$1, execSync as execSync$1, execFile as execFile$1, execFileSync } from 'node:child_process';
@@ -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.
@@ -5446,10 +5446,11 @@ function cronMatches(expr, date) {
5446
5446
  if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
5447
5447
  return domOk && dowOk;
5448
5448
  }
5449
- function inZone(date, tz) {
5450
- if (!tz) return date;
5451
- try {
5452
- const p = new Intl.DateTimeFormat("en-US", {
5449
+ const _zoneFormatters = /* @__PURE__ */ new Map();
5450
+ function zoneFormatter(tz) {
5451
+ let f = _zoneFormatters.get(tz);
5452
+ if (!f) {
5453
+ f = new Intl.DateTimeFormat("en-US", {
5453
5454
  timeZone: tz,
5454
5455
  hour12: false,
5455
5456
  year: "numeric",
@@ -5457,7 +5458,15 @@ function inZone(date, tz) {
5457
5458
  day: "2-digit",
5458
5459
  hour: "2-digit",
5459
5460
  minute: "2-digit"
5460
- }).formatToParts(date).reduce((o, x) => {
5461
+ });
5462
+ _zoneFormatters.set(tz, f);
5463
+ }
5464
+ return f;
5465
+ }
5466
+ function inZone(date, tz) {
5467
+ if (!tz) return date;
5468
+ try {
5469
+ const p = zoneFormatter(tz).formatToParts(date).reduce((o, x) => {
5461
5470
  o[x.type] = x.value;
5462
5471
  return o;
5463
5472
  }, {});
@@ -5467,6 +5476,34 @@ function inZone(date, tz) {
5467
5476
  return date;
5468
5477
  }
5469
5478
  }
5479
+ function nextFire(expr, from, tz) {
5480
+ const c = parseCron(expr);
5481
+ if (tz && scanForMatch(c, from, void 0) === null) return null;
5482
+ return scanForMatch(c, from, tz);
5483
+ }
5484
+ function scanForMatch(c, from, tz) {
5485
+ const d = new Date(from.getTime());
5486
+ d.setSeconds(0, 0);
5487
+ d.setMinutes(d.getMinutes() + 1);
5488
+ for (let i = 0; i < 366 * 24 * 60; i++) {
5489
+ if (cronMatches(c, tz ? inZone(d, tz) : d)) return new Date(d.getTime());
5490
+ d.setMinutes(d.getMinutes() + 1);
5491
+ }
5492
+ return null;
5493
+ }
5494
+ function describeNextFire(expr, tz, from = /* @__PURE__ */ new Date()) {
5495
+ let next;
5496
+ try {
5497
+ next = nextFire(expr, from, tz);
5498
+ } catch {
5499
+ return null;
5500
+ }
5501
+ if (!next) return "never fires";
5502
+ const shown = tz ? inZone(next, tz) : next;
5503
+ const pad = (n) => String(n).padStart(2, "0");
5504
+ const stamp = `${shown.getFullYear()}-${pad(shown.getMonth() + 1)}-${pad(shown.getDate())} ${pad(shown.getHours())}:${pad(shown.getMinutes())}`;
5505
+ return `next ${stamp} ${tz || "local"}`;
5506
+ }
5470
5507
  function resolvePath(ctx, path) {
5471
5508
  return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
5472
5509
  }
@@ -7823,9 +7860,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7823
7860
  if (!tunnels) throw new Error("Tunnel management not available");
7824
7861
  const client = tunnels.get(params.name);
7825
7862
  if (!client) throw new Error(`Tunnel '${params.name}' not found`);
7863
+ const owner = handlers.listExposedTunnels?.().find((t) => t.name === params.name)?.sessionId;
7826
7864
  client.destroy();
7827
7865
  tunnels.delete(params.name);
7828
7866
  handlers.forgetExposedTunnel?.(params.name);
7867
+ if (owner) handlers.dropTunnelSessionLink?.(owner, params.name);
7868
+ handlers.scheduleServiceLinkReconcile?.();
7829
7869
  return { name: params.name, stopped: true };
7830
7870
  },
7831
7871
  /**
@@ -7868,6 +7908,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7868
7908
  sessionId: params.sessionId,
7869
7909
  addedAt: Date.now()
7870
7910
  });
7911
+ if (params.sessionId) handlers.scheduleServiceLinkReconcile?.();
7871
7912
  const urls = {};
7872
7913
  for (const [port, url] of tunnel.getUrls()) urls[port] = url;
7873
7914
  return { ...tunnel.status, urls };
@@ -7879,9 +7920,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7879
7920
  if (!tunnels) throw new Error("Tunnel management not available");
7880
7921
  const client = tunnels.get(params.name);
7881
7922
  if (!client) throw new Error(`Tunnel '${params.name}' not found`);
7923
+ const owner = handlers.listExposedTunnels?.().find((t) => t.name === params.name)?.sessionId;
7882
7924
  client.destroy();
7883
7925
  tunnels.delete(params.name);
7884
7926
  handlers.forgetExposedTunnel?.(params.name);
7927
+ if (owner) handlers.dropTunnelSessionLink?.(owner, params.name);
7928
+ handlers.scheduleServiceLinkReconcile?.();
7885
7929
  return { name: params.name, stopped: true };
7886
7930
  },
7887
7931
  /**
@@ -7942,7 +7986,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7942
7986
  assertMayReplaceMount(sm, params.name, context);
7943
7987
  const ownerEmail2 = resolveMountOwnerEmailFor(params.ownerEmail, context);
7944
7988
  const access = params.access || "owner";
7945
- return sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail2);
7989
+ const result = sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail2);
7990
+ if (params.sessionId) handlers.scheduleServiceLinkReconcile?.();
7991
+ return result;
7946
7992
  },
7947
7993
  /**
7948
7994
  * Apply a mount declaratively. Replaces existing mount with same name.
@@ -8332,7 +8378,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8332
8378
  }
8333
8379
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
8334
8380
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
8335
- const { toolsForRole } = await import('./sideband-S3ycJa01.mjs');
8381
+ const { toolsForRole } = await import('./sideband-DXyAkIaB.mjs');
8336
8382
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
8337
8383
  return fmt(r2);
8338
8384
  }
@@ -8421,6 +8467,33 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8421
8467
  };
8422
8468
  const _caps = statelessDispatchCaps();
8423
8469
  const statelessLimiter = new StatelessDispatchLimiter(_caps.concurrent, _caps.perSender);
8470
+ const MAX_RECEIVE_POLLS_PER_CHANNEL = Math.max(
8471
+ 1,
8472
+ Number(process.env.SVAMP_MAX_CHANNEL_RECEIVE_POLLS) || 16
8473
+ );
8474
+ const _receivePolls = /* @__PURE__ */ new Map();
8475
+ const acquireReceiveSlot = (channelId) => {
8476
+ const n = _receivePolls.get(channelId) || 0;
8477
+ if (n >= MAX_RECEIVE_POLLS_PER_CHANNEL) return false;
8478
+ _receivePolls.set(channelId, n + 1);
8479
+ return true;
8480
+ };
8481
+ const releaseReceiveSlot = (channelId) => {
8482
+ const n = (_receivePolls.get(channelId) || 1) - 1;
8483
+ if (n <= 0) _receivePolls.delete(channelId);
8484
+ else _receivePolls.set(channelId, n);
8485
+ };
8486
+ const _outboxCache = /* @__PURE__ */ new Map();
8487
+ const getOutbox = (dir) => {
8488
+ let ob = _outboxCache.get(dir);
8489
+ if (!ob) {
8490
+ ob = new ChannelOutbox(dir);
8491
+ _outboxCache.set(dir, ob);
8492
+ } else {
8493
+ ob.reload();
8494
+ }
8495
+ return ob;
8496
+ };
8424
8497
  const dispatchStateless = async (c, dir, kwargs, context) => {
8425
8498
  const u = context?.user;
8426
8499
  const r = resolveSender(c, {
@@ -8437,7 +8510,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8437
8510
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8438
8511
  }
8439
8512
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8440
- const { queryCore } = await import('./commands-CWkWkrZF.mjs');
8513
+ const { queryCore } = await import('./commands-DKouDroK.mjs');
8441
8514
  const timeout = c.reply?.timeout_sec || 120;
8442
8515
  let result;
8443
8516
  let thrownSessionId;
@@ -8539,15 +8612,22 @@ ${d?.error || "not found"}`;
8539
8612
  const waitRaw = Number(kwargs.wait ?? 25);
8540
8613
  const waitSec = Number.isFinite(waitRaw) ? waitRaw : 25;
8541
8614
  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) };
8615
+ if (!acquireReceiveSlot(c.id)) {
8616
+ return { error: "busy: too many concurrent receive polls on this channel \u2014 retry shortly", retryable: true };
8617
+ }
8618
+ try {
8619
+ const outbox = getOutbox(dir);
8620
+ const deadline = Date.now() + waitMs;
8621
+ for (; ; ) {
8622
+ const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
8623
+ if (replies.length || Date.now() >= deadline) {
8624
+ return { ok: true, replies, cursor: outbox.cursor(c.id) };
8625
+ }
8626
+ await new Promise((resolve) => setTimeout(resolve, 500));
8627
+ outbox.reload();
8548
8628
  }
8549
- await new Promise((resolve) => setTimeout(resolve, 500));
8550
- outbox.reload();
8629
+ } finally {
8630
+ releaseReceiveSlot(c.id);
8551
8631
  }
8552
8632
  },
8553
8633
  // Safety-restricted file upload (#0093). Deny-by-default: only channels whose
@@ -9244,10 +9324,15 @@ const OUTPOST_EXEC_TIMEOUT_MS = 12 * 6e4;
9244
9324
  const OUTPOST_MAX_OUTPUT_BYTES = 1 << 20;
9245
9325
  function capOutpostOutput(s) {
9246
9326
  if (!s) return "";
9247
- if (s.length <= OUTPOST_MAX_OUTPUT_BYTES) return s;
9248
- const dropped = s.length - OUTPOST_MAX_OUTPUT_BYTES;
9249
- return `[svamp-daemon] output truncated: ${dropped} earlier characters dropped]
9250
- ${s.slice(-OUTPOST_MAX_OUTPUT_BYTES)}`;
9327
+ const bytes = Buffer.byteLength(s, "utf-8");
9328
+ if (bytes <= OUTPOST_MAX_OUTPUT_BYTES) return s;
9329
+ const buf = Buffer.from(s, "utf-8");
9330
+ let start = buf.length - OUTPOST_MAX_OUTPUT_BYTES;
9331
+ while (start < buf.length && (buf[start] & 192) === 128) start++;
9332
+ const tail = buf.subarray(start).toString("utf-8");
9333
+ const dropped = bytes - Buffer.byteLength(tail, "utf-8");
9334
+ return `[svamp-daemon] output truncated: ${dropped} earlier bytes dropped
9335
+ ${tail}`;
9251
9336
  }
9252
9337
  class OutpostCoordinator {
9253
9338
  conns = /* @__PURE__ */ new Map();
@@ -9365,9 +9450,14 @@ class OutpostCoordinator {
9365
9450
  resolveTarget(target) {
9366
9451
  const live = this.listConnections();
9367
9452
  if (target) {
9368
- const hit = live.find((c) => c.machine === target || c.label === target);
9369
- if (!hit) return { error: `no connected outpost named "${target}" (connected: ${live.map((c) => c.machine).join(", ") || "none"})` };
9370
- return { machine: hit.machine };
9453
+ const byId = live.find((c) => c.machine === target);
9454
+ if (byId) return { machine: byId.machine };
9455
+ const byLabel = live.filter((c) => c.label === target);
9456
+ if (byLabel.length === 1) return { machine: byLabel[0].machine };
9457
+ if (byLabel.length > 1) {
9458
+ return { error: `outpost label "${target}" is ambiguous \u2014 ${byLabel.length} connected outposts share it (${byLabel.map((c) => c.machine).join(", ")}); pass the machine id instead` };
9459
+ }
9460
+ return { error: `no connected outpost named "${target}" (connected: ${live.map((c) => c.machine).join(", ") || "none"})` };
9371
9461
  }
9372
9462
  if (live.length === 0) return { error: "no remote outpost is connected \u2014 run the install one-liner on the remote machine first" };
9373
9463
  if (live.length > 1) return { error: `multiple outposts connected (${live.map((c) => c.machine).join(", ")}) \u2014 pass a target` };
@@ -9981,7 +10071,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9981
10071
  ensureParticipantsChannel(initialMetadata.sharing);
9982
10072
  const forkClaudeOnce = async (prompt, claudeSessionId, cwd, maxTurns, timeoutMs) => {
9983
10073
  const { spawn } = await import('child_process');
9984
- const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
10074
+ const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1", DISABLE_AUTOUPDATER: "1" };
9985
10075
  applyClaudeProxyEnv(forkEnv);
9986
10076
  return new Promise((resolve) => {
9987
10077
  const child = spawn("claude", [
@@ -11575,6 +11665,53 @@ function buildSessionIdentityEnv(identity) {
11575
11665
  return env;
11576
11666
  }
11577
11667
 
11668
+ async function downloadSkillAtomically(targetDir, files, fetchFile) {
11669
+ const stagingDir = `${targetDir}.tmp-${process.pid}-${Date.now().toString(36)}`;
11670
+ try {
11671
+ mkdirSync$1(stagingDir, { recursive: true });
11672
+ for (const filePath of files) {
11673
+ if (!filePath) continue;
11674
+ const content = await fetchFile(filePath);
11675
+ const localPath = join$1(stagingDir, filePath);
11676
+ if (!localPath.startsWith(stagingDir + "/")) continue;
11677
+ mkdirSync$1(dirname(localPath), { recursive: true });
11678
+ writeFileSync$1(localPath, content, "utf-8");
11679
+ }
11680
+ } catch (err) {
11681
+ rmSync$1(stagingDir, { recursive: true, force: true });
11682
+ throw err;
11683
+ }
11684
+ const backupDir = `${targetDir}.old-${process.pid}-${Date.now().toString(36)}`;
11685
+ let backedUp = false;
11686
+ try {
11687
+ if (existsSync(targetDir)) {
11688
+ renameSync(targetDir, backupDir);
11689
+ backedUp = true;
11690
+ }
11691
+ renameSync(stagingDir, targetDir);
11692
+ } catch (err) {
11693
+ if (backedUp && !existsSync(targetDir)) {
11694
+ try {
11695
+ renameSync(backupDir, targetDir);
11696
+ backedUp = false;
11697
+ } catch {
11698
+ }
11699
+ }
11700
+ try {
11701
+ rmSync$1(stagingDir, { recursive: true, force: true });
11702
+ } catch {
11703
+ }
11704
+ if (backedUp) {
11705
+ try {
11706
+ rmSync$1(backupDir, { recursive: true, force: true });
11707
+ } catch {
11708
+ }
11709
+ }
11710
+ throw err;
11711
+ }
11712
+ if (backedUp) rmSync$1(backupDir, { recursive: true, force: true });
11713
+ }
11714
+
11578
11715
  const HOTRELOAD_ENV = "SVAMP_DEV_HOTRELOAD";
11579
11716
  function isHotReloadEnabled(env = process.env) {
11580
11717
  const v = env[HOTRELOAD_ENV];
@@ -14185,7 +14322,7 @@ function codexCumulativeTokens(payload) {
14185
14322
  return nonCachedInput + output + Math.round(cached * 0.1);
14186
14323
  }
14187
14324
 
14188
- const PINNED_CODEX_VERSION = "0.147.0";
14325
+ const PINNED_CODEX_VERSION = "0.149.0";
14189
14326
  const MIN_CODEX_STEER_VERSION = { major: 0, minor: 129, patch: 0 };
14190
14327
  function cmpVer(a, b) {
14191
14328
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
@@ -15216,6 +15353,15 @@ function kimiProviderConfigured(configTomlText, provider) {
15216
15353
  const block = nextTable < 0 ? rest : rest.slice(0, nextTable);
15217
15354
  return /(^|\n)\s*api_key\s*=/.test(block);
15218
15355
  }
15356
+ function kimiHasGlobalDefaultModel(configTomlText) {
15357
+ if (!configTomlText) return false;
15358
+ const firstTable = configTomlText.search(/(^|\n)\s*\[/);
15359
+ const preamble = firstTable < 0 ? configTomlText : configTomlText.slice(0, firstTable);
15360
+ return /(^|\n)\s*default_model\s*=/.test(preamble);
15361
+ }
15362
+ function kimiConfigComplete(configTomlText, provider) {
15363
+ return kimiProviderConfigured(configTomlText, provider) && kimiHasGlobalDefaultModel(configTomlText);
15364
+ }
15219
15365
  function kimiModelMenu(env = process.env) {
15220
15366
  return (env.SVAMP_KIMI_MODELS || "").split(",").map((s) => s.trim()).filter(Boolean).filter((m, i, a) => a.indexOf(m) === i);
15221
15367
  }
@@ -15237,8 +15383,10 @@ var kimiProvider = /*#__PURE__*/Object.freeze({
15237
15383
  buildKimiCatalogAddArgs: buildKimiCatalogAddArgs,
15238
15384
  hasKimiProvider: hasKimiProvider,
15239
15385
  kimiClampModel: kimiClampModel,
15386
+ kimiConfigComplete: kimiConfigComplete,
15240
15387
  kimiDefaultModel: kimiDefaultModel,
15241
15388
  kimiDefaultModelId: kimiDefaultModelId,
15389
+ kimiHasGlobalDefaultModel: kimiHasGlobalDefaultModel,
15242
15390
  kimiModelMenu: kimiModelMenu,
15243
15391
  kimiProviderConfigured: kimiProviderConfigured,
15244
15392
  kimiSpawnEnv: kimiSpawnEnv,
@@ -15257,7 +15405,7 @@ async function ensureKimiProviderConfigured(env = process.env, command = "kimi",
15257
15405
  }) {
15258
15406
  const cfg = resolveKimiProvider(env);
15259
15407
  if (!hasKimiProvider(cfg)) return false;
15260
- if (kimiProviderConfigured(readKimiConfig(), cfg.provider)) return true;
15408
+ if (kimiConfigComplete(readKimiConfig(), cfg.provider)) return true;
15261
15409
  const args = buildKimiCatalogAddArgs(cfg);
15262
15410
  if (!args) return false;
15263
15411
  try {
@@ -15275,7 +15423,11 @@ async function ensureKimiProviderConfigured(env = process.env, command = "kimi",
15275
15423
  );
15276
15424
  });
15277
15425
  log(`[kimi] configured provider '${cfg.provider}' in ${KIMI_CONFIG_PATH}`);
15278
- return kimiProviderConfigured(readKimiConfig(), cfg.provider);
15426
+ const complete = kimiConfigComplete(readKimiConfig(), cfg.provider);
15427
+ if (!complete) {
15428
+ log(`[kimi] catalog add left ${KIMI_CONFIG_PATH} without a global default_model \u2014 \`kimi acp\` will refuse session/new ("no default model configured")`);
15429
+ }
15430
+ return complete;
15279
15431
  } catch (err) {
15280
15432
  log(`[kimi] provider auto-config failed (${err?.message || err}); session may need kimi-auth`);
15281
15433
  return false;
@@ -15783,8 +15935,28 @@ const SENSITIVE_ENV_VARS = [
15783
15935
  "GH_TOKEN",
15784
15936
  "NPM_TOKEN",
15785
15937
  "DOCKER_PASSWORD",
15786
- "DOCKER_CONFIG"
15938
+ "DOCKER_CONFIG",
15939
+ // #1110: svamp's own persisted credentials (written by `svamp daemon codex-auth` /
15940
+ // `kimi-auth` / `wise-agent auth`, and by the generic LLM_* aliases).
15941
+ "OPENAI_API_KEY",
15942
+ "GEMINI_API_KEY",
15943
+ "XAI_API_KEY",
15944
+ "ANTHROPIC_ADMIN_KEY",
15945
+ "LLM_API_KEY",
15946
+ "CLOUDFLARE_API_TOKEN",
15947
+ "R2_ACCESS_KEY_ID",
15948
+ "R2_SECRET_ACCESS_KEY",
15949
+ "X_API_KEY"
15787
15950
  ];
15951
+ const CREDENTIAL_NAME_RE = /(^|_)(KEY|APIKEY|TOKEN|SECRET|PASSWORD|CREDENTIALS)$/;
15952
+ const CREDENTIAL_SWEEP_ALLOWLIST = /* @__PURE__ */ new Set([
15953
+ "ANTHROPIC_API_KEY",
15954
+ "ANTHROPIC_AUTH_TOKEN"
15955
+ ]);
15956
+ function isCredentialEnvName(key) {
15957
+ if (CREDENTIAL_SWEEP_ALLOWLIST.has(key)) return false;
15958
+ return CREDENTIAL_NAME_RE.test(key);
15959
+ }
15788
15960
  async function stageCredentialsForSharing(sessionId) {
15789
15961
  const realHome = homedir$1();
15790
15962
  const realClaudeDir = join$1(realHome, ".claude");
@@ -15858,6 +16030,9 @@ function sanitizeEnvForSharing(env) {
15858
16030
  for (const key of SENSITIVE_ENV_VARS) {
15859
16031
  delete sanitized[key];
15860
16032
  }
16033
+ for (const key of Object.keys(sanitized)) {
16034
+ if (isCredentialEnvName(key)) delete sanitized[key];
16035
+ }
15861
16036
  return sanitized;
15862
16037
  }
15863
16038
  async function sweepOrphanedStagedHomes(activeSessionIds) {
@@ -15900,6 +16075,7 @@ async function copyDirRecursive(src, dest) {
15900
16075
 
15901
16076
  var credentialStaging = /*#__PURE__*/Object.freeze({
15902
16077
  __proto__: null,
16078
+ isCredentialEnvName: isCredentialEnvName,
15903
16079
  sanitizeEnvForSharing: sanitizeEnvForSharing,
15904
16080
  stageCredentialsForSharing: stageCredentialsForSharing,
15905
16081
  sweepOrphanedStagedHomes: sweepOrphanedStagedHomes
@@ -16239,14 +16415,21 @@ class ProcessSupervisor {
16239
16415
  if (entry.stopping) throw new Error(`Process '${entry.spec.name}' is being stopped, try again shortly`);
16240
16416
  throw new Error(`Process '${entry.spec.name}' is already running`);
16241
16417
  }
16242
- entry.stopping = false;
16243
- this.clearTimers(entry);
16244
- if (entry.spec.desiredStopped) {
16245
- entry.spec.desiredStopped = false;
16246
- await this.persistSpec(entry.spec);
16247
- }
16248
- this.resetFailureTracking(entry);
16249
- await this.startEntry(entry, false);
16418
+ return this.runExclusive(entry, async () => {
16419
+ if (entry.deleted) throw new Error(`Process '${idOrName}' has been removed`);
16420
+ if (entry.child) {
16421
+ if (entry.stopping) throw new Error(`Process '${entry.spec.name}' is being stopped, try again shortly`);
16422
+ throw new Error(`Process '${entry.spec.name}' is already running`);
16423
+ }
16424
+ entry.stopping = false;
16425
+ this.clearTimers(entry);
16426
+ if (entry.spec.desiredStopped) {
16427
+ entry.spec.desiredStopped = false;
16428
+ await this.persistSpec(entry.spec);
16429
+ }
16430
+ this.resetFailureTracking(entry);
16431
+ await this.startEntry(entry, false);
16432
+ });
16250
16433
  }
16251
16434
  /**
16252
16435
  * Stop a running process. Does NOT remove it from supervision.
@@ -16256,7 +16439,7 @@ class ProcessSupervisor {
16256
16439
  */
16257
16440
  /**
16258
16441
  * #0347: run `fn` in the entry's lifecycle critical section. Each call awaits the previous
16259
- * lifecycle op's SETTLEMENT before running, so stop/restart/apply/update/remove are strictly
16442
+ * lifecycle op's SETTLEMENT before running, so start/stop/restart/apply/update/remove are strictly
16260
16443
  * serialized per entry and can never interleave across an `await killChild` (which is what let
16261
16444
  * two ops double-spawn an orphan, or resurrect a just-stopped process). The chain tail never
16262
16445
  * rejects (a failed op must not wedge the queue); the caller still gets the real result/error.
@@ -16473,7 +16656,7 @@ class ProcessSupervisor {
16473
16656
  }
16474
16657
  async persistSpec(spec) {
16475
16658
  const filePath = path__default.join(this.persistDir, `${spec.id}.json`);
16476
- const tmpPath = filePath + ".tmp";
16659
+ const tmpPath = `${filePath}.tmp-${process.pid}-${randomBytes$1(4).toString("hex")}`;
16477
16660
  await writeFile$1(tmpPath, JSON.stringify(spec, null, 2), "utf-8");
16478
16661
  await rename(tmpPath, filePath);
16479
16662
  }
@@ -17052,7 +17235,7 @@ function searchIssues(projectRoot, query) {
17052
17235
  const q = query.trim();
17053
17236
  if (!q) return [];
17054
17237
  try {
17055
- const out = execFileSync("rg", ["-l", "-i", "--no-messages", q, issuesDir(projectRoot)], { encoding: "utf-8" });
17238
+ const out = execFileSync("rg", ["-l", "-i", "-F", "--no-messages", "-e", q, "--", issuesDir(projectRoot)], { encoding: "utf-8" });
17056
17239
  const files = out.split("\n").filter(Boolean);
17057
17240
  const issues = [];
17058
17241
  for (const f of files) {
@@ -17063,9 +17246,24 @@ function searchIssues(projectRoot, query) {
17063
17246
  }
17064
17247
  }
17065
17248
  return issues.sort((a, b) => Number(a.id) - Number(b.id));
17066
- } catch {
17067
- const ql = q.toLowerCase();
17068
- return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
17249
+ } catch (e) {
17250
+ if (e?.status === 1) return [];
17251
+ return searchIssuesInMemory(projectRoot, q);
17252
+ }
17253
+ }
17254
+ function searchIssuesInMemory(projectRoot, q) {
17255
+ const ql = q.toLowerCase();
17256
+ return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
17257
+ }
17258
+ function checkVerifyCommand(projectRoot, issue) {
17259
+ const cmd = issue?.verify?.type === "command" ? (issue.verify.text || "").trim() : "";
17260
+ if (!cmd) return { ok: true };
17261
+ try {
17262
+ execSync$1(cmd, { cwd: projectRoot, stdio: "pipe", timeout: 6e5, maxBuffer: 64 * 1024 * 1024 });
17263
+ return { ok: true, cmd };
17264
+ } catch (e) {
17265
+ const output = (String(e?.stdout || "") + String(e?.stderr || "")).split("\n").slice(-15).join("\n").trim();
17266
+ return { ok: false, cmd, output };
17069
17267
  }
17070
17268
  }
17071
17269
  function summarize(issues) {
@@ -17092,7 +17290,8 @@ function planLogPrune(files, policy = DEFAULT_LOG_PRUNE_POLICY, nowMs = Date.now
17092
17290
  const overCount = keepFiles > 0 ? perStart.slice(keepFiles) : perStart;
17093
17291
  const tooOld = maxAgeMs > 0 ? perStart.filter((f) => nowMs - f.mtimeMs > maxAgeMs) : [];
17094
17292
  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 }));
17293
+ const doomed = new Set(deleteFiles);
17294
+ 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
17295
  return { deleteFiles, truncateFiles };
17097
17296
  }
17098
17297
  function resolveLogPrunePolicy(env = process.env) {
@@ -17107,11 +17306,63 @@ function resolveLogPrunePolicy(env = process.env) {
17107
17306
  maxBytes: num(env.SVAMP_LOG_MAX_BYTES, DEFAULT_LOG_PRUNE_POLICY.maxBytes)
17108
17307
  };
17109
17308
  }
17110
- function tailSlice(buf, tailBytes) {
17111
- if (buf.length <= tailBytes) return buf;
17112
- const cut = buf.subarray(buf.length - tailBytes);
17113
- const nl = cut.indexOf(10);
17114
- return nl >= 0 && nl + 1 < cut.length ? cut.subarray(nl + 1) : cut;
17309
+ function trimToLineStart(buf) {
17310
+ const nl = buf.indexOf(10);
17311
+ return nl >= 0 && nl + 1 < buf.length ? buf.subarray(nl + 1) : buf;
17312
+ }
17313
+
17314
+ function readLogTail(path, tailBytes) {
17315
+ const fd = openSync(path, "r");
17316
+ try {
17317
+ const size = fstatSync(fd).size;
17318
+ const want = Math.min(Math.max(0, Math.floor(tailBytes)), size);
17319
+ if (want === 0) return { data: Buffer.alloc(0), truncated: size > 0 };
17320
+ const buf = Buffer.allocUnsafe(want);
17321
+ const start = size - want;
17322
+ let read = 0;
17323
+ while (read < want) {
17324
+ const n = readSync(fd, buf, read, want - read, start + read);
17325
+ if (n <= 0) break;
17326
+ read += n;
17327
+ }
17328
+ return { data: read === want ? buf : buf.subarray(0, read), truncated: size > want };
17329
+ } finally {
17330
+ closeSync(fd);
17331
+ }
17332
+ }
17333
+
17334
+ const CARRIER_FIELDS = ["message", "error", "detail", "reason", "description"];
17335
+ function formatLogArg(a) {
17336
+ if (typeof a === "string") return a;
17337
+ if (a instanceof Error) return a.stack || `${a.name}: ${a.message}`;
17338
+ if (a === void 0) return "undefined";
17339
+ if (a === null) return "null";
17340
+ try {
17341
+ const s = JSON.stringify(a);
17342
+ if (s === void 0 || s === "{}") return String(a);
17343
+ return s;
17344
+ } catch {
17345
+ try {
17346
+ return String(a);
17347
+ } catch {
17348
+ return "[unserializable]";
17349
+ }
17350
+ }
17351
+ }
17352
+ function formatLogArgs(args) {
17353
+ return args.map(formatLogArg).join(" ");
17354
+ }
17355
+ function describeRejectionReason(reason) {
17356
+ if (typeof reason === "string") return reason;
17357
+ if (reason instanceof Error) return `${reason.name}: ${reason.message}`;
17358
+ if (reason && typeof reason === "object") {
17359
+ for (const f of CARRIER_FIELDS) {
17360
+ const v = reason[f];
17361
+ if (typeof v === "string" && v.trim() !== "") return v;
17362
+ if (v instanceof Error) return `${v.name}: ${v.message}`;
17363
+ }
17364
+ }
17365
+ return formatLogArg(reason);
17115
17366
  }
17116
17367
 
17117
17368
  function isWorkflowEnabled(wf) {
@@ -17135,6 +17386,12 @@ function validateWorkflowName(name) {
17135
17386
  function workflowPath(projectRoot, name) {
17136
17387
  return join$1(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yaml`);
17137
17388
  }
17389
+ function resolveWorkflowPath(projectRoot, name) {
17390
+ const canonical = workflowPath(projectRoot, name);
17391
+ if (existsSync(canonical)) return canonical;
17392
+ const alt = join$1(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yml`);
17393
+ return existsSync(alt) ? alt : canonical;
17394
+ }
17138
17395
  function normalizeOn(on) {
17139
17396
  if (!on || typeof on !== "object") {
17140
17397
  return void 0;
@@ -17231,7 +17488,7 @@ function listWorkflows(projectRoot) {
17231
17488
  function getWorkflow(projectRoot, name) {
17232
17489
  let p;
17233
17490
  try {
17234
- p = workflowPath(projectRoot, name);
17491
+ p = resolveWorkflowPath(projectRoot, name);
17235
17492
  } catch {
17236
17493
  return null;
17237
17494
  }
@@ -17245,7 +17502,7 @@ function getWorkflow(projectRoot, name) {
17245
17502
  function rawWorkflow(projectRoot, name) {
17246
17503
  let p;
17247
17504
  try {
17248
- p = workflowPath(projectRoot, name);
17505
+ p = resolveWorkflowPath(projectRoot, name);
17249
17506
  } catch {
17250
17507
  return null;
17251
17508
  }
@@ -17258,6 +17515,13 @@ function saveWorkflow(projectRoot, wf) {
17258
17515
  const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
17259
17516
  writeFileSync$1(tmp, serializeWorkflow(wf));
17260
17517
  renameSync(tmp, path);
17518
+ const alt = join$1(dir, `${validateWorkflowName(wf.name)}.yml`);
17519
+ if (alt !== path && existsSync(alt)) {
17520
+ try {
17521
+ unlinkSync$1(alt);
17522
+ } catch {
17523
+ }
17524
+ }
17261
17525
  }
17262
17526
  function setWorkflowEnabled(projectRoot, name, enabled) {
17263
17527
  const wf = getWorkflow(projectRoot, name);
@@ -17269,7 +17533,7 @@ function setWorkflowEnabled(projectRoot, name, enabled) {
17269
17533
  return next;
17270
17534
  }
17271
17535
  function removeWorkflow(projectRoot, name) {
17272
- const p = workflowPath(projectRoot, name);
17536
+ const p = resolveWorkflowPath(projectRoot, name);
17273
17537
  if (!existsSync(p)) return false;
17274
17538
  try {
17275
17539
  unlinkSync$1(p);
@@ -17783,6 +18047,15 @@ function isCostCeiling(kind) {
17783
18047
  function isHardBudgetStop(kind) {
17784
18048
  return isCostCeiling(kind) || kind === "iteration";
17785
18049
  }
18050
+ function rearmStartedAt(startedAt, completedAt, now) {
18051
+ const fin = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
18052
+ if (!fin(startedAt)) return startedAt;
18053
+ if (!fin(completedAt)) return startedAt;
18054
+ if (completedAt < startedAt) return startedAt;
18055
+ const dormantMs = now - completedAt;
18056
+ if (dormantMs <= 0) return startedAt;
18057
+ return startedAt + dormantMs;
18058
+ }
17786
18059
  function checkLoopBudget(ledger, limits, now, startedAt) {
17787
18060
  const L = limits || {};
17788
18061
  const led = ledger || { turns: 0, tokens: 0, window: [] };
@@ -18262,6 +18535,47 @@ function isContextTooLongError(text) {
18262
18535
  if (t.includes("context") && t.includes("exceed") && t.includes("limit") && !t.includes("subscription")) return true;
18263
18536
  return false;
18264
18537
  }
18538
+ function shouldEvaluateProactiveCompaction(state) {
18539
+ if (!state.enabled) return false;
18540
+ if (state.disabled) return false;
18541
+ if (state.turnErrored) return false;
18542
+ if (state.compactPending) return false;
18543
+ if (state.justCompacted) return false;
18544
+ return true;
18545
+ }
18546
+ function compactSupportedFromSlashCommands(slashCommands) {
18547
+ if (!Array.isArray(slashCommands) || slashCommands.length === 0) return void 0;
18548
+ return slashCommands.some(
18549
+ (c) => typeof c === "string" && c.trim().replace(/^\//, "").toLowerCase() === "compact"
18550
+ );
18551
+ }
18552
+ const DEFAULT_COMPACT_NO_BOUNDARY_STRIKES = 3;
18553
+ function decideCompactUnsupported(input) {
18554
+ const prior = Math.max(0, input.priorStrikes || 0);
18555
+ const limit = Math.max(1, input.maxStrikes);
18556
+ if (input.turnErrored) {
18557
+ return { disable: false, warnUnsupported: false, strikes: prior, reason: "errored-turn" };
18558
+ }
18559
+ if (!input.wasAutoInjected) {
18560
+ return {
18561
+ disable: false,
18562
+ warnUnsupported: input.compactSupported === false,
18563
+ strikes: prior,
18564
+ reason: "user-typed"
18565
+ };
18566
+ }
18567
+ if (input.compactSupported === false) {
18568
+ return { disable: true, warnUnsupported: true, strikes: prior + 1, reason: "unsupported" };
18569
+ }
18570
+ const strikes = prior + 1;
18571
+ if (strikes >= limit) {
18572
+ return { disable: true, warnUnsupported: false, strikes, reason: "strikes-exhausted" };
18573
+ }
18574
+ return { disable: false, warnUnsupported: false, strikes, reason: "strike" };
18575
+ }
18576
+ function describeNoBoundary(strikes, limit) {
18577
+ return `Automatic compaction ran but reported no compaction boundary (attempt ${strikes} of ${limit}) \u2014 usually because there was nothing left to compact. Keeping automatic compaction enabled.`;
18578
+ }
18265
18579
 
18266
18580
  function fmtTokens(n) {
18267
18581
  return Math.max(0, Math.round(n)).toLocaleString("en-US");
@@ -18835,23 +19149,27 @@ function compareVersions(a, b) {
18835
19149
  return 0;
18836
19150
  }
18837
19151
  async function installSkillFromEndpoint(name, baseUrl) {
18838
- const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
18839
- if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
18840
- const index = await resp.json();
19152
+ const index = await fetchEndpointSkillIndex(baseUrl);
18841
19153
  const files = index.files || [];
18842
19154
  if (files.length === 0) throw new Error(`Skill index at ${baseUrl} has no files`);
18843
- const targetDir = join(CLAUDE_SKILLS_DIR, name);
18844
- mkdirSync(targetDir, { recursive: true });
18845
- for (const filePath of files) {
18846
- if (!filePath) continue;
18847
- const url = `${baseUrl}${filePath}`;
18848
- const fileResp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
19155
+ await downloadSkillAtomically(join(CLAUDE_SKILLS_DIR, name), files, async (filePath) => {
19156
+ const fileResp = await fetch(`${baseUrl}${filePath}`, { signal: AbortSignal.timeout(3e4) });
18849
19157
  if (!fileResp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${fileResp.status}`);
18850
- const content = await fileResp.text();
18851
- const localPath = join(targetDir, filePath);
18852
- if (!localPath.startsWith(targetDir + "/")) continue;
18853
- mkdirSync(dirname$1(localPath), { recursive: true });
18854
- writeFileSync(localPath, content, "utf-8");
19158
+ return fileResp.text();
19159
+ });
19160
+ }
19161
+ async function fetchEndpointSkillIndex(baseUrl) {
19162
+ const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
19163
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
19164
+ return await resp.json();
19165
+ }
19166
+ async function fetchEndpointSkillVersion(baseUrl) {
19167
+ try {
19168
+ const index = await fetchEndpointSkillIndex(baseUrl);
19169
+ const v = index?.version;
19170
+ return typeof v === "string" && v.trim() ? v.trim() : null;
19171
+ } catch {
19172
+ return null;
18855
19173
  }
18856
19174
  }
18857
19175
  async function installSkillFromMarketplace(name) {
@@ -18875,26 +19193,11 @@ async function installSkillFromMarketplace(name) {
18875
19193
  }
18876
19194
  const files = await collectFiles();
18877
19195
  if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
18878
- const targetDir = join(CLAUDE_SKILLS_DIR, name);
18879
- const stagingDir = `${targetDir}.tmp-${process.pid}-${Date.now().toString(36)}`;
18880
- try {
18881
- mkdirSync(stagingDir, { recursive: true });
18882
- for (const filePath of files) {
18883
- const url = `${BASE}/files/${filePath}`;
18884
- const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
18885
- if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
18886
- const content = await resp.text();
18887
- const localPath = join(stagingDir, filePath);
18888
- if (!localPath.startsWith(stagingDir + "/")) continue;
18889
- mkdirSync(dirname$1(localPath), { recursive: true });
18890
- writeFileSync(localPath, content, "utf-8");
18891
- }
18892
- rmSync(targetDir, { recursive: true, force: true });
18893
- renameSync$1(stagingDir, targetDir);
18894
- } catch (err) {
18895
- rmSync(stagingDir, { recursive: true, force: true });
18896
- throw err;
18897
- }
19196
+ await downloadSkillAtomically(join(CLAUDE_SKILLS_DIR, name), files, async (filePath) => {
19197
+ const resp = await fetch(`${BASE}/files/${filePath}`, { signal: AbortSignal.timeout(3e4) });
19198
+ if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
19199
+ return resp.text();
19200
+ });
18898
19201
  }
18899
19202
  function getBundledSkillsDir() {
18900
19203
  try {
@@ -19113,9 +19416,16 @@ async function ensureAutoInstalledSkills(logger) {
19113
19416
  marketplaceVersion: () => fetchMarketplaceSkillVersion("svamp")
19114
19417
  },
19115
19418
  {
19419
+ // #1192: the agent-skills endpoint's index already carries a `version` (verified live:
19420
+ // `{"name":"hypha","version":"1.0.0","files":[...]}`), and installSkillFromEndpoint
19421
+ // fetches that exact document anyway — it just used to discard the field. Supplying the
19422
+ // probe moves `hypha` off the "install once, never refresh" branch, so a machine is no
19423
+ // longer pinned forever to whatever version it first installed. Safe now that #1191
19424
+ // gave this downloader atomic staging: a refresh that fails mid-download leaves the
19425
+ // previous skill intact instead of half-overwriting it.
19116
19426
  name: "hypha",
19117
- install: () => installSkillFromEndpoint("hypha", `${getSkillsServer()}/ws/agent-skills/`)
19118
- // hypha skill ships from a different endpoint without a version probe yet.
19427
+ install: () => installSkillFromEndpoint("hypha", `${getSkillsServer()}/ws/agent-skills/`),
19428
+ marketplaceVersion: () => fetchEndpointSkillVersion(`${getSkillsServer()}/ws/agent-skills/`)
19119
19429
  },
19120
19430
  {
19121
19431
  // `<artifact>` block rendering — see packages/svamp-app/.../Artifact.tsx.
@@ -19328,17 +19638,23 @@ function isLoopActive(directory, sessionId) {
19328
19638
  const s = readLoopState(directory, sessionId);
19329
19639
  return !!s && s.active !== false && s.phase !== "dormant" && !isTerminalLoopPhase(s.phase);
19330
19640
  }
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
19641
  function isLoopActiveForSession(directory, sessionId) {
19337
19642
  const s = readLoopState(directory, sessionId);
19338
19643
  if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return false;
19339
19644
  if (typeof s.session_id !== "string") return true;
19340
19645
  return s.session_id === sessionId;
19341
19646
  }
19647
+ function loopExistsForSession(directory, sessionId) {
19648
+ const s = readLoopState(directory, sessionId);
19649
+ if (!s || s.active === false || isTerminalLoopPhase(s.phase)) return false;
19650
+ if (typeof s.session_id !== "string") return true;
19651
+ return s.session_id === sessionId;
19652
+ }
19653
+ function loopOwnerSessionIncludingDormant(directory, sessionId) {
19654
+ const s = readLoopState(directory, sessionId);
19655
+ if (!s || s.active === false || isTerminalLoopPhase(s.phase)) return null;
19656
+ return typeof s.session_id === "string" ? s.session_id : null;
19657
+ }
19342
19658
  function isLoopArmedForSession(directory, sessionId) {
19343
19659
  const s = readLoopState(directory, sessionId);
19344
19660
  if (!s || s.active === false || s.phase !== "dormant") return false;
@@ -19675,13 +19991,13 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
19675
19991
  };
19676
19992
  let announcedLoopStart = (() => {
19677
19993
  const s = readLoopState(directory, sessionId);
19678
- return s && (s.phase === "done" || s.phase === "gave_up") ? s.started_at ?? "\u2205" : null;
19994
+ return s && (s.phase === "done" || s.phase === "gave_up") ? String(s.started_at ?? "\u2205") : null;
19679
19995
  })();
19680
19996
  const loopChecker = () => {
19681
19997
  try {
19682
19998
  const s = readLoopState(directory, sessionId);
19683
19999
  if (!s || s.phase !== "done" && s.phase !== "gave_up") return;
19684
- const key = s.started_at ?? "\u2205";
20000
+ const key = String(s.started_at ?? "\u2205");
19685
20001
  if (key === announcedLoopStart) return;
19686
20002
  announcedLoopStart = key;
19687
20003
  const iter = s.ledger && typeof s.ledger.turns === "number" && s.ledger.turns > 0 ? s.ledger.turns : typeof s.iteration === "number" ? s.iteration : 0;
@@ -20052,7 +20368,7 @@ function ensureHomeDir() {
20052
20368
  mkdirSync(LOGS_DIR, { recursive: true });
20053
20369
  }
20054
20370
  }
20055
- function pruneDaemonLogs() {
20371
+ function pruneDaemonLogs(exclude = []) {
20056
20372
  try {
20057
20373
  const policy = resolveLogPrunePolicy(process.env);
20058
20374
  const entries = readdirSync$1(LOGS_DIR).map((name) => {
@@ -20063,7 +20379,7 @@ function pruneDaemonLogs() {
20063
20379
  return null;
20064
20380
  }
20065
20381
  }).filter((e) => e !== null);
20066
- const plan = planLogPrune(entries, policy);
20382
+ const plan = planLogPrune(entries, policy, Date.now(), exclude);
20067
20383
  for (const name of plan.deleteFiles) {
20068
20384
  try {
20069
20385
  unlinkSync(join(LOGS_DIR, name));
@@ -20073,7 +20389,9 @@ function pruneDaemonLogs() {
20073
20389
  for (const { name } of plan.truncateFiles) {
20074
20390
  try {
20075
20391
  const p = join(LOGS_DIR, name);
20076
- const kept = tailSlice(readFileSync$1(p), Math.floor(policy.maxBytes / 8));
20392
+ const keepBytes = Math.floor(policy.maxBytes / 8);
20393
+ const tail = readLogTail(p, keepBytes);
20394
+ const kept = tail.truncated ? trimToLineStart(tail.data) : tail.data;
20077
20395
  writeFileSync(p, kept);
20078
20396
  } catch {
20079
20397
  }
@@ -20085,10 +20403,13 @@ function createLogger() {
20085
20403
  ensureHomeDir();
20086
20404
  pruneDaemonLogs();
20087
20405
  const logFile = join(LOGS_DIR, `daemon-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.log`);
20406
+ const sweepMs = Math.max(6e4, Number(process.env.SVAMP_LOG_PRUNE_INTERVAL_MS) || 36e5);
20407
+ const sweepTimer = setInterval(() => pruneDaemonLogs([basename$1(logFile)]), sweepMs);
20408
+ if (typeof sweepTimer.unref === "function") sweepTimer.unref();
20088
20409
  return {
20089
20410
  logFilePath: logFile,
20090
20411
  log: (...args) => {
20091
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
20412
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${formatLogArgs(args)}
20092
20413
  `;
20093
20414
  fs$1.appendFile(logFile, line).catch(() => {
20094
20415
  });
@@ -20097,7 +20418,7 @@ function createLogger() {
20097
20418
  }
20098
20419
  },
20099
20420
  error: (...args) => {
20100
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
20421
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${formatLogArgs(args)}
20101
20422
  `;
20102
20423
  fs$1.appendFile(logFile, line).catch(() => {
20103
20424
  });
@@ -20254,7 +20575,7 @@ async function startDaemon(options) {
20254
20575
  const UNHANDLED_REJECTION_WINDOW_MS = 6e4;
20255
20576
  process.on("unhandledRejection", (reason) => {
20256
20577
  if (shutdownRequested) return;
20257
- const msg = String(reason);
20578
+ const msg = describeRejectionReason(reason);
20258
20579
  logger.error("Unhandled rejection:", reason);
20259
20580
  const isTransient = TRANSIENT_REJECTION_PATTERNS.some((p) => msg.toLowerCase().includes(p.toLowerCase()));
20260
20581
  const isSessionLoss = msg.toLowerCase().includes("session does not exist");
@@ -20383,7 +20704,7 @@ async function startDaemon(options) {
20383
20704
  try {
20384
20705
  const dir = loadSessionIndex()[sessionId]?.directory;
20385
20706
  if (!dir) return;
20386
- const { reconcileServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20707
+ const { reconcileServiceLinks } = await import('./agentCommands-BVz1k2R2.mjs');
20387
20708
  const configPath = getSvampConfigPath(dir, sessionId);
20388
20709
  const config = readSvampConfig(configPath);
20389
20710
  const entries = Array.from(urls.entries());
@@ -20405,7 +20726,7 @@ async function startDaemon(options) {
20405
20726
  try {
20406
20727
  const dir = loadSessionIndex()[sessionId]?.directory;
20407
20728
  if (!dir) return;
20408
- const { reconcileServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20729
+ const { reconcileServiceLinks } = await import('./agentCommands-BVz1k2R2.mjs');
20409
20730
  const configPath = getSvampConfigPath(dir, sessionId);
20410
20731
  const config = readSvampConfig(configPath);
20411
20732
  const incoming = [{
@@ -20426,7 +20747,7 @@ async function startDaemon(options) {
20426
20747
  try {
20427
20748
  const dir = loadSessionIndex()[sessionId]?.directory;
20428
20749
  if (!dir) return;
20429
- const { dropServiceLinks } = await import('./agentCommands-CovUtOYv.mjs');
20750
+ const { dropServiceLinks } = await import('./agentCommands-BVz1k2R2.mjs');
20430
20751
  const configPath = getSvampConfigPath(dir, sessionId);
20431
20752
  const config = readSvampConfig(configPath);
20432
20753
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -20437,6 +20758,73 @@ async function startDaemon(options) {
20437
20758
  logger.log(`[serve] Link drop failed for mount ${mountName}: ${err?.message || err}`);
20438
20759
  }
20439
20760
  }
20761
+ async function dropTunnelSessionLink(sessionId, tunnelName) {
20762
+ if (!sessionId) return;
20763
+ try {
20764
+ const dir = loadSessionIndex()[sessionId]?.directory;
20765
+ if (!dir) return;
20766
+ const { dropServiceLinks } = await import('./agentCommands-BVz1k2R2.mjs');
20767
+ const configPath = getSvampConfigPath(dir, sessionId);
20768
+ const config = readSvampConfig(configPath);
20769
+ if (dropServiceLinks(config, "tunnel", tunnelName)) {
20770
+ writeSvampConfig(configPath, config);
20771
+ logger.log(`[exposed-tunnels] Dropped Launch Pad link for tunnel ${tunnelName} \u2190 session ${sessionId}`);
20772
+ }
20773
+ } catch (err) {
20774
+ logger.log(`[exposed-tunnels] Link drop failed for tunnel ${tunnelName}: ${err?.message || err}`);
20775
+ }
20776
+ }
20777
+ async function reconcileAllServiceLinks() {
20778
+ try {
20779
+ const { reconcileServiceLinksExact } = await import('./agentCommands-BVz1k2R2.mjs');
20780
+ const desiredBySession = /* @__PURE__ */ new Map();
20781
+ const push = (sessionId, entry) => {
20782
+ if (!sessionId || !entry.url) return;
20783
+ const list = desiredBySession.get(sessionId) ?? [];
20784
+ list.push(entry);
20785
+ desiredBySession.set(sessionId, list);
20786
+ };
20787
+ for (const m of serveManager$1.listMounts()) {
20788
+ if (m?.sessionId && m?.url) push(m.sessionId, { url: String(m.url), label: m.name, service: { kind: "serve", name: m.name } });
20789
+ }
20790
+ const specBySession = /* @__PURE__ */ new Map();
20791
+ for (const spec of loadExposedTunnels()) if (spec.sessionId) specBySession.set(spec.name, spec.sessionId);
20792
+ for (const [name, tunnel] of tunnels) {
20793
+ const sessionId = specBySession.get(name);
20794
+ if (!sessionId) continue;
20795
+ const urls = Array.from(tunnel.getUrls().entries());
20796
+ for (const [port, url] of urls) {
20797
+ push(sessionId, { url: String(url), label: urls.length > 1 ? `${name}:${port}` : name, service: { kind: "tunnel", name } });
20798
+ }
20799
+ }
20800
+ const sessionIds = /* @__PURE__ */ new Set([...Object.keys(loadSessionIndex()), ...desiredBySession.keys()]);
20801
+ for (const sessionId of sessionIds) {
20802
+ try {
20803
+ const dir = loadSessionIndex()[sessionId]?.directory;
20804
+ if (!dir) continue;
20805
+ const configPath = getSvampConfigPath(dir, sessionId);
20806
+ const config = readSvampConfig(configPath);
20807
+ const desired = desiredBySession.get(sessionId) ?? [];
20808
+ if (reconcileServiceLinksExact(config, ["serve", "tunnel"], desired)) {
20809
+ writeSvampConfig(configPath, config);
20810
+ logger.log(`[launchpad] Reconciled service links for session ${sessionId} (${desired.length} live)`);
20811
+ }
20812
+ } catch (err) {
20813
+ logger.log(`[launchpad] Reconcile failed for session ${sessionId}: ${err?.message || err}`);
20814
+ }
20815
+ }
20816
+ } catch (err) {
20817
+ logger.log(`[launchpad] Service-link sweep failed: ${err?.message || err}`);
20818
+ }
20819
+ }
20820
+ let serviceLinkReconcileTimer;
20821
+ function scheduleServiceLinkReconcile(delayMs = 1200) {
20822
+ if (serviceLinkReconcileTimer) clearTimeout(serviceLinkReconcileTimer);
20823
+ serviceLinkReconcileTimer = setTimeout(() => {
20824
+ serviceLinkReconcileTimer = void 0;
20825
+ void reconcileAllServiceLinks();
20826
+ }, delayMs);
20827
+ }
20440
20828
  async function createExposedTunnel(spec) {
20441
20829
  const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
20442
20830
  const tunnel = new FrpcTunnel({
@@ -20469,8 +20857,18 @@ async function startDaemon(options) {
20469
20857
  ensureAutoInstalledCommands(logger);
20470
20858
  (async () => {
20471
20859
  try {
20472
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BO0Qs_kE.mjs');
20473
- beginClaudeVersionReconcile((msg) => logger.log(msg));
20860
+ const { beginClaudeVersionReconcile, CLAUDE_VERSION_RECHECK_MS } = await import('./pinnedClaudeCode-C43KnZ-Q.mjs');
20861
+ const onResult = (result) => {
20862
+ if (result?.kind === "install-failed" || result?.kind === "skipped" || result?.kind === "not-installed") {
20863
+ logger.log(`[claude-version] WARNING \u2014 machine is NOT on the pinned Claude Code (${result.kind}). Sessions are running an untested build.`);
20864
+ }
20865
+ };
20866
+ beginClaudeVersionReconcile((msg) => logger.log(msg), { onResult });
20867
+ const jitter = Math.floor(Math.random() * 5 * 6e4);
20868
+ const timer = setInterval(() => {
20869
+ beginClaudeVersionReconcile((msg) => logger.log(msg), { onResult });
20870
+ }, CLAUDE_VERSION_RECHECK_MS + jitter);
20871
+ timer.unref?.();
20474
20872
  } catch (e) {
20475
20873
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
20476
20874
  }
@@ -20963,7 +21361,7 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
20963
21361
  const cwd = sessionMetadata.path || directory;
20964
21362
  const model = process.env.SVAMP_TOPIC_MODEL;
20965
21363
  try {
20966
- const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
21364
+ const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1", DISABLE_AUTOUPDATER: "1" };
20967
21365
  applyClaudeProxyEnv(forkEnv);
20968
21366
  const child = spawn("claude", [
20969
21367
  "--print",
@@ -21402,6 +21800,9 @@ ${parts.join("\n")}`);
21402
21800
  let autoCompactWasAutoInjected = false;
21403
21801
  let autoCompactDisabled = false;
21404
21802
  let autoCompactRecoveryTried = false;
21803
+ let justCompactedTurn = false;
21804
+ let compactNoBoundaryStrikes = 0;
21805
+ let compactSupported = void 0;
21405
21806
  let noIsolationWarned = false;
21406
21807
  const RATELIMIT_CFG = getRateLimitRetryConfig();
21407
21808
  let currentTurnMessage;
@@ -21847,15 +22248,30 @@ ${parts.join("\n")}`);
21847
22248
  pendingCompactTurn = false;
21848
22249
  const wasAuto = autoCompactWasAutoInjected;
21849
22250
  autoCompactWasAutoInjected = false;
21850
- if (wasAuto) {
22251
+ const d = decideCompactUnsupported({
22252
+ wasAutoInjected: wasAuto,
22253
+ turnErrored: !!msg.is_error,
22254
+ compactSupported,
22255
+ priorStrikes: compactNoBoundaryStrikes,
22256
+ maxStrikes: DEFAULT_COMPACT_NO_BOUNDARY_STRIKES
22257
+ });
22258
+ compactNoBoundaryStrikes = d.strikes;
22259
+ if (d.disable) {
21851
22260
  autoCompactDisabled = true;
21852
- logger.log(`[Session ${sessionId}] Auto /compact unsupported by this Claude Code \u2014 disabling the auto-compaction backstop for this session`);
22261
+ logger.log(`[Session ${sessionId}] Disabling the auto-compaction backstop for this session (${d.reason})`);
22262
+ }
22263
+ logger.log(`[Session ${sessionId}] /compact produced no compaction boundary (reason=${d.reason}, strikes=${d.strikes}, supported=${String(compactSupported)}, errored=${!!msg.is_error})`);
22264
+ if (d.warnUnsupported) {
22265
+ sessionService.pushMessage(
22266
+ { type: "message", message: compactUnsupportedWarning(), level: "warning" },
22267
+ "event"
22268
+ );
22269
+ } else if (wasAuto && d.reason !== "errored-turn") {
22270
+ sessionService.pushMessage(
22271
+ { type: "message", message: describeNoBoundary(d.strikes, DEFAULT_COMPACT_NO_BOUNDARY_STRIKES), level: "warning" },
22272
+ "event"
22273
+ );
21853
22274
  }
21854
- logger.log(`[Session ${sessionId}] /compact produced no compaction boundary \u2014 Claude Code likely too old to handle it in stream-json mode`);
21855
- sessionService.pushMessage(
21856
- { type: "message", message: compactUnsupportedWarning(), level: "warning" },
21857
- "event"
21858
- );
21859
22275
  }
21860
22276
  if (!msg.is_error) {
21861
22277
  resetRateLimitStreak();
@@ -21946,7 +22362,15 @@ ${parts.join("\n")}`);
21946
22362
  logger.log(`[Session ${sessionId}] ${taskInfo}`);
21947
22363
  sessionService.pushMessage({ type: "session_event", message: taskInfo }, "session");
21948
22364
  }
21949
- if (!msg.is_error && !pendingCompactTurn && !autoCompactDisabled && autoCompactEnabled) {
22365
+ const mayEvaluateLayer1 = shouldEvaluateProactiveCompaction({
22366
+ justCompacted: justCompactedTurn,
22367
+ compactPending: pendingCompactTurn,
22368
+ turnErrored: !!msg.is_error,
22369
+ disabled: autoCompactDisabled,
22370
+ enabled: autoCompactEnabled
22371
+ });
22372
+ justCompactedTurn = false;
22373
+ if (mayEvaluateLayer1) {
21950
22374
  try {
21951
22375
  const observed = computeObservedContext(msg.usage);
21952
22376
  const decision = decideProactiveCompaction({
@@ -22010,6 +22434,7 @@ ${parts.join("\n")}`);
22010
22434
  userMessagePending = false;
22011
22435
  if (msg.slash_commands && Array.isArray(msg.slash_commands)) {
22012
22436
  sessionMetadata = { ...sessionMetadata, slashCommands: msg.slash_commands };
22437
+ compactSupported = compactSupportedFromSlashCommands(msg.slash_commands);
22013
22438
  }
22014
22439
  if (typeof msg.model === "string" && msg.model.length > 0) {
22015
22440
  lastMainModel = msg.model;
@@ -22063,6 +22488,8 @@ ${parts.join("\n")}`);
22063
22488
  pendingCompactTurn = false;
22064
22489
  autoCompactWasAutoInjected = false;
22065
22490
  autoCompactRecoveryTried = false;
22491
+ justCompactedTurn = true;
22492
+ compactNoBoundaryStrikes = 0;
22066
22493
  const compactLine = describeCompaction(msg.compact_metadata);
22067
22494
  logger.log(`[Session ${sessionId}] ${compactLine}`);
22068
22495
  sessionService.pushMessage(
@@ -22879,11 +23306,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22879
23306
  });
22880
23307
  },
22881
23308
  onIssue: async (params) => {
22882
- const { issueRpc } = await import('./rpc-CVBhR9Cj.mjs');
23309
+ const { issueRpc } = await import('./rpc-UomHF39a.mjs');
22883
23310
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22884
23311
  },
22885
23312
  onWorkflow: async (params) => {
22886
- const { workflowRpc } = await import('./rpc-OmhzsDGP.mjs');
23313
+ const { workflowRpc } = await import('./rpc-CoPRGZkJ.mjs');
22887
23314
  return workflowRpc(params?.cwd || directory, params || {});
22888
23315
  },
22889
23316
  onRipgrep: async (args, cwd) => {
@@ -23103,13 +23530,28 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23103
23530
  active: true,
23104
23531
  phase: "continue",
23105
23532
  iteration: 0,
23533
+ started_at: rearmStartedAt(s.started_at, s.completed_at, Date.now()),
23534
+ // #1170
23106
23535
  completed_at: void 0,
23107
23536
  gave_up_reason: void 0,
23108
23537
  verify_errors: 0,
23109
23538
  // fresh verification budget on re-arm
23110
23539
  stall_hinted: false,
23111
- resumed_at: Date.now()
23540
+ resumed_at: Date.now(),
23112
23541
  // #0156: re-arm the stall hint
23542
+ // A re-arm starts a NEW work batch, so the previous batch's progress
23543
+ // baseline must not carry over. assessLoopProgress tracks a MONOTONIC
23544
+ // MINIMUM over the retained history, and a durable loop only parks
23545
+ // dormant once the oracle reads 0 pending — so the old tail is
23546
+ // `pending: 0` and best === 0 forever. Without this reset the first
23547
+ // new marker (pending >= 1) can never beat it, the loop reads STUCK
23548
+ // from checkpoint 3 onward while it is genuinely resolving issues,
23549
+ // `auto_resumes` never resets (updateLoopProgress keys that off the
23550
+ // same signal), and the finite #0958 cap burns down to a terminal
23551
+ // gave_up. That contradicts the invariant that a slow-but-PRODUCTIVE
23552
+ // loop is never killed by the cap.
23553
+ progress_history: [],
23554
+ auto_resumes: 0
23113
23555
  });
23114
23556
  } catch {
23115
23557
  }
@@ -23591,11 +24033,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23591
24033
  });
23592
24034
  },
23593
24035
  onIssue: async (params) => {
23594
- const { issueRpc } = await import('./rpc-CVBhR9Cj.mjs');
24036
+ const { issueRpc } = await import('./rpc-UomHF39a.mjs');
23595
24037
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23596
24038
  },
23597
24039
  onWorkflow: async (params) => {
23598
- const { workflowRpc } = await import('./rpc-OmhzsDGP.mjs');
24040
+ const { workflowRpc } = await import('./rpc-CoPRGZkJ.mjs');
23599
24041
  return workflowRpc(params?.cwd || directory, params || {});
23600
24042
  },
23601
24043
  onRipgrep: async (args, cwd) => {
@@ -23938,7 +24380,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23938
24380
  };
23939
24381
  try {
23940
24382
  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");
24383
+ 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
24384
  ls.acp_budget_warned = true;
23943
24385
  }
23944
24386
  const acpMaxExt = resolveMaxExtensions(process.env.SVAMP_LOOP_MAX_EXTENSIONS);
@@ -24259,7 +24701,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24259
24701
  const wasInMemory = teardownTrackedSession(sessionId);
24260
24702
  idleTriggerTracker.forget(sessionId);
24261
24703
  const markedArchived = markSessionAsArchived(sessionId);
24262
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24704
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24263
24705
  deactivateLoop(loopDir, sessionId);
24264
24706
  logger.log(`Deactivated loop for archived session ${sessionId}`);
24265
24707
  }
@@ -24317,7 +24759,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24317
24759
  teardownTrackedSession(sessionId);
24318
24760
  idleTriggerTracker.forget(sessionId);
24319
24761
  deletePersistedSession(sessionId);
24320
- if (loopDir && isLoopActiveForSession(loopDir, sessionId)) {
24762
+ if (loopDir && loopExistsForSession(loopDir, sessionId)) {
24321
24763
  deactivateLoop(loopDir, sessionId);
24322
24764
  }
24323
24765
  logger.log(`Session ${sessionId} deleted`);
@@ -24460,7 +24902,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24460
24902
  sharingNotificationSync,
24461
24903
  persistExposedTunnel,
24462
24904
  forgetExposedTunnel,
24463
- listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, sessionId: t.sessionId, addedAt: t.addedAt }))
24905
+ listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, sessionId: t.sessionId, addedAt: t.addedAt })),
24906
+ dropTunnelSessionLink: (sessionId, tunnelName) => {
24907
+ void dropTunnelSessionLink(sessionId, tunnelName);
24908
+ },
24909
+ scheduleServiceLinkReconcile: () => scheduleServiceLinkReconcile()
24464
24910
  });
24465
24911
  const machineService = await registerMachineService(
24466
24912
  server,
@@ -24592,6 +25038,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24592
25038
  } catch (err) {
24593
25039
  logger.log(`[serve] mount link reconcile failed: ${err?.message || err}`);
24594
25040
  }
25041
+ scheduleServiceLinkReconcile(3e3);
24595
25042
  }).catch((err) => logger.error(`[serve] mount restore failed: ${err?.message || err}`));
24596
25043
  const daemonOwnerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || null;
24597
25044
  serveManager$1.setSessionResolver((sessionId) => {
@@ -24658,7 +25105,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24658
25105
  try {
24659
25106
  for (const ent of readdirSync$1(join(p.directory, ".svamp"), { withFileTypes: true })) {
24660
25107
  if (!ent.isDirectory() || knownSessionIds.has(ent.name)) continue;
24661
- const owner = loopOwnerSession(p.directory, ent.name);
25108
+ const owner = loopOwnerSessionIncludingDormant(p.directory, ent.name);
24662
25109
  if (owner && !knownSessionIds.has(owner)) {
24663
25110
  deactivateLoop(p.directory, ent.name);
24664
25111
  logger.log(`[loop] Deactivated stale loop-state for ${ent.name} in ${p.directory} (owner no longer known)`);
@@ -24666,7 +25113,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24666
25113
  }
24667
25114
  } catch {
24668
25115
  }
24669
- const legacyOwner = loopOwnerSession(p.directory);
25116
+ const legacyOwner = loopOwnerSessionIncludingDormant(p.directory);
24670
25117
  if (legacyOwner && !knownSessionIds.has(legacyOwner)) {
24671
25118
  deactivateLoop(p.directory);
24672
25119
  logger.log(`[loop] Deactivated stale legacy loop-state in ${p.directory} (owner session ${legacyOwner} no longer known)`);
@@ -24675,7 +25122,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24675
25122
  }
24676
25123
  if (persistedSessions.length > 0) {
24677
25124
  try {
24678
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BO0Qs_kE.mjs');
25125
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C43KnZ-Q.mjs');
24679
25126
  await awaitClaudeVersionReady();
24680
25127
  } catch {
24681
25128
  }
@@ -24727,7 +25174,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24727
25174
  try {
24728
25175
  const lp = join(getLoopDir(persisted.directory, persisted.sessionId), "loop-state.json");
24729
25176
  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() });
25177
+ 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
25178
  } catch {
24732
25179
  }
24733
25180
  }
@@ -24865,13 +25312,16 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24865
25312
  }
24866
25313
  if (sessionsToLoopResume.length > 0 && !options?.noAutoContinue) {
24867
25314
  logger.log(`Resuming loop for ${sessionsToLoopResume.length} session(s)...`);
24868
- for (const { sessionId, directory: sessDir } of sessionsToLoopResume) {
25315
+ const LOOP_RESUME_STEP_MS = Number(process.env.SVAMP_AUTO_CONTINUE_STEP_MS) || 300;
25316
+ const LOOP_RESUME_MAX_SPREAD_MS = Number(process.env.SVAMP_AUTO_CONTINUE_MAX_SPREAD_MS) || 6e4;
25317
+ sessionsToLoopResume.forEach(({ sessionId, directory: sessDir }, loopResumeIndex) => {
25318
+ const loopResumeDelay = 2e3 + Math.min(loopResumeIndex * LOOP_RESUME_STEP_MS, LOOP_RESUME_MAX_SPREAD_MS) + Math.floor(Math.random() * LOOP_RESUME_STEP_MS);
24869
25319
  try {
24870
25320
  const tracked = Array.from(pidToTrackedSession.values()).find((s) => s.svampSessionId === sessionId);
24871
25321
  const rpc = tracked?.sessionRPCHandlers;
24872
25322
  if (!rpc) {
24873
25323
  logger.log(`Session ${sessionId} RPC handlers not found for loop resume`);
24874
- continue;
25324
+ return;
24875
25325
  }
24876
25326
  const loopTurnFingerprint = () => {
24877
25327
  try {
@@ -24897,7 +25347,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24897
25347
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
24898
25348
  const fpBefore = loopTurnFingerprint();
24899
25349
  await sendResume("");
24900
- const graceMs = Math.max(5e3, Number(process.env.SVAMP_LOOP_RESUME_WATCHDOG_MS) || 45e3);
25350
+ 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
25351
  setTimeout(() => {
24902
25352
  try {
24903
25353
  if (!isLoopActiveForSession(sessDir, sessionId)) return;
@@ -24914,11 +25364,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24914
25364
  } catch (err) {
24915
25365
  logger.log(`Failed to resume loop for session ${sessionId}: ${err.message}`);
24916
25366
  }
24917
- }, 2e3);
25367
+ }, loopResumeDelay);
24918
25368
  } catch (err) {
24919
25369
  logger.log(`Failed to find session service for loop resume ${sessionId}: ${err.message}`);
24920
25370
  }
24921
- }
25371
+ });
24922
25372
  }
24923
25373
  (async () => {
24924
25374
  try {
@@ -24928,27 +25378,17 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24928
25378
  logger.log(`[ARTIFACT SYNC] Background init failed: ${err.message}`);
24929
25379
  }
24930
25380
  })();
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
25381
  console.log("Svamp daemon started successfully!");
24939
25382
  console.log(` Machine ID: ${machineId}`);
24940
25383
  console.log(` Hypha server: ${hyphaServerUrl}`);
24941
25384
  console.log(` Workspace: ${server.config.workspace}`);
24942
- if (appToken) {
24943
- console.log(` App token: ${appToken}`);
24944
- }
24945
25385
  console.log(` Service: svamp-machine-${machineId}`);
24946
25386
  console.log(` Log file: ${logger.logFilePath}`);
24947
25387
  const HEARTBEAT_INTERVAL_MS = 1e4;
24948
25388
  const PING_TIMEOUT_MS = 15e3;
24949
25389
  const POST_RECONNECT_GRACE_MS = 2e4;
24950
25390
  const RECONNECT_JITTER_MS = 2500;
24951
- const { WorkflowScheduler } = await import('./scheduler-DDw3YLzH.mjs');
25391
+ const { WorkflowScheduler } = await import('./scheduler-gMWmsAgp.mjs');
24952
25392
  const workflowProjectRoots = () => {
24953
25393
  const dirs = /* @__PURE__ */ new Set();
24954
25394
  for (const s of pidToTrackedSession.values()) {
@@ -24987,6 +25427,9 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24987
25427
  };
24988
25428
  runOAuthAccountRefresh();
24989
25429
  const backendAccountRefreshInterval = setInterval(runOAuthAccountRefresh, 5 * 6e4);
25430
+ const serviceLinkSweepInterval = setInterval(() => {
25431
+ void reconcileAllServiceLinks();
25432
+ }, 6e4);
24990
25433
  let heartbeatRunning = false;
24991
25434
  let heartbeatCycle = 0;
24992
25435
  const heartbeatInterval = setInterval(async () => {
@@ -25239,6 +25682,8 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
25239
25682
  clearInterval(heartbeatInterval);
25240
25683
  clearInterval(workflowSchedulerInterval);
25241
25684
  clearInterval(backendAccountRefreshInterval);
25685
+ clearInterval(serviceLinkSweepInterval);
25686
+ if (serviceLinkReconcileTimer) clearTimeout(serviceLinkReconcileTimer);
25242
25687
  if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
25243
25688
  if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
25244
25689
  if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
@@ -25583,4 +26028,4 @@ var run = /*#__PURE__*/Object.freeze({
25583
26028
  writeStopMarker: writeStopMarker
25584
26029
  });
25585
26030
 
25586
- export { downloadSkillFile as $, validateWorkflowName as A, saveWorkflow as B, rawWorkflow as C, listWorkflows as D, isWorkflowEnabled as E, workflowSchedules as F, inZone 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, parseFrontmatter as T, getSkillsServer as U, getSkillsWorkspaceName as V, getSkillsCollectionName as W, fetchWithTimeout as X, searchSkills as Y, SKILLS_DIR as Z, getSkillInfo as _, createSessionStore as a, listSkillFiles as a0, resolveModel as a1, formatHandle as a2, normalizeAllowedUser as a3, loadSecurityContextConfig as a4, resolveSecurityContext as a5, buildSecurityContextFromFlags as a6, mergeSecurityContexts as a7, buildSessionShareUrl as a8, computeOutboundHop as a9, kimiProvider as aA, kimiSetup as aB, api as aC, supervisorLock as aD, run as aE, registerAwaitingReply as aa, buildMachineShareUrl as ab, parseHandle as ac, handleMatchesMetadata as ad, PINNED_CODEX_VERSION as ae, clearStopMarker as af, stopMarkerExists as ag, withFileLock as ah, describeMisconfiguration as ai, buildMachineDeps as aj, applyClaudeProxyEnv as ak, composeSessionId as al, generateFriendlyName as am, generateHookSettings as an, instanceConfig as ao, frpc as ap, staticFileServer 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, KimiTransport$1 as az, stopDaemon as b, connectToHypha as c, daemonStatus as d, shortId as e, resolveProjectRoot as f, getHyphaServerUrl$1 as g, getIssue as h, resumeIssue as i, addComment as j, addIssue as k, listIssues as l, isPendingIssue as m, searchIssues as n, isVisibleTo as o, pauseIssue as p, getRun as q, registerMachineService as r, startDaemon as s, listRuns as t, updateIssue as u, getWorkflow as v, runWorkflow as w, setWorkflowEnabled as x, removeWorkflow as y, validateCronExpr as z };
26031
+ export { SKILLS_DIR as $, validateCronExpr 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, describeNextFire as L, parseJwtEmail as M, computeCollectionConfigUpdate as N, SYSTEM_COLLECTION_CONFIG as O, loadMachineContext as P, buildMachineInstructions as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, machineToolsForRole as T, buildMachineTools 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, formatHandle as a4, normalizeAllowedUser as a5, loadSecurityContextConfig as a6, resolveSecurityContext as a7, buildSecurityContextFromFlags as a8, mergeSecurityContexts as a9, GeminiTransport$1 as aA, KimiTransport$1 as aB, kimiProvider as aC, kimiSetup as aD, api as aE, supervisorLock as aF, run as aG, buildSessionShareUrl as aa, computeOutboundHop as ab, registerAwaitingReply as ac, buildMachineShareUrl as ad, parseHandle as ae, handleMatchesMetadata as af, PINNED_CODEX_VERSION as ag, clearStopMarker as ah, stopMarkerExists as ai, withFileLock as aj, describeMisconfiguration as ak, buildMachineDeps as al, applyClaudeProxyEnv as am, composeSessionId as an, generateFriendlyName as ao, generateHookSettings as ap, instanceConfig as aq, frpc as ar, staticFileServer as as, claudeAuth as at, codexProvider as au, projectInfo as av, DefaultTransport$1 as aw, acpBackend as ax, acpAgentConfig as ay, codexAppServerBackend as az, stopDaemon as b, connectToHypha as c, daemonStatus as d, shortId as e, resolveProjectRoot as f, getHyphaServerUrl$1 as g, getIssue as h, checkVerifyCommand as i, resumeIssue as j, addComment as k, addIssue as l, listIssues as m, isPendingIssue as n, searchIssues as o, pauseIssue as p, isVisibleTo as q, registerMachineService as r, startDaemon as s, getRun as t, updateIssue as u, listRuns as v, getWorkflow as w, runWorkflow as x, setWorkflowEnabled as y, removeWorkflow as z };