svamp-cli 0.2.332 → 0.2.334

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-BNGSC5oD.mjs → adminCommands-CBuNOXdO.mjs} +1 -1
  2. package/dist/{agentCommands-DD47Krr1.mjs → agentCommands-D26EUFSD.mjs} +35 -6
  3. package/dist/{auth-CE3Cnkn6.mjs → auth-g7KNy7sB.mjs} +1 -1
  4. package/dist/{cli-CdYVO7ce.mjs → cli-CqGlhaWz.mjs} +77 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-C2X-uElg.mjs → commands-6lx2QJEs.mjs} +1 -1
  7. package/dist/{commands-09sPSIH-.mjs → commands-BiA2_M_9.mjs} +3 -3
  8. package/dist/{commands-CHKSUX7x.mjs → commands-C2clJWyQ.mjs} +3 -3
  9. package/dist/{commands-14Tf0D6G.mjs → commands-Cj8WbqGh.mjs} +3 -3
  10. package/dist/{commands-BIKekqQG.mjs → commands-Ct2EXO44.mjs} +1 -1
  11. package/dist/{commands-DXJlhBPk.mjs → commands-DLHf6gma.mjs} +7 -9
  12. package/dist/{commands-BIx8s0JL.mjs → commands-VLcMD_8H.mjs} +11 -11
  13. package/dist/{commands-D7wwybeC.mjs → commands-cYTe9lAc.mjs} +8 -3
  14. package/dist/{fleet-bAe_gJfS.mjs → fleet-CXVxTQTU.mjs} +3 -3
  15. package/dist/{headlessCli-Bwg5pf3s.mjs → headlessCli-BYJunlMc.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{notifyCommands-Cc3wLAWW.mjs → notifyCommands-CfKQtXDj.mjs} +1 -1
  18. package/dist/package-CF7vQ3Fz.mjs +64 -0
  19. package/dist/{pinnedClaudeCode-BO0Qs_kE.mjs → pinnedClaudeCode-C43KnZ-Q.mjs} +27 -6
  20. package/dist/{rpc-DObWvEis.mjs → rpc-BsgXlOhI.mjs} +13 -1
  21. package/dist/{rpc-Bv6_c-xW.mjs → rpc-C6zBQ0L5.mjs} +2 -1
  22. package/dist/{run-CyyWhgFI.mjs → run-D4TWydj5.mjs} +1 -1
  23. package/dist/{run-DGhVoN3s.mjs → run-DEn3zLAt.mjs} +434 -101
  24. package/dist/{scheduler-BvtNZ0pl.mjs → scheduler-Ciu9Qfsi.mjs} +1 -1
  25. package/dist/{serveCommands-CEM708Ls.mjs → serveCommands-BTl4Fcku.mjs} +10 -10
  26. package/dist/{sideband--9vWYqKA.mjs → sideband-BMzd_SoN.mjs} +1 -1
  27. package/package.json +4 -4
  28. package/dist/package-CPQqs8ty.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';
@@ -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--9vWYqKA.mjs');
8381
+ const { toolsForRole } = await import('./sideband-BMzd_SoN.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
  }
@@ -8464,7 +8510,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8464
8510
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8465
8511
  }
8466
8512
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8467
- const { queryCore } = await import('./commands-C2X-uElg.mjs');
8513
+ const { queryCore } = await import('./commands-6lx2QJEs.mjs');
8468
8514
  const timeout = c.reply?.timeout_sec || 120;
8469
8515
  let result;
8470
8516
  let thrownSessionId;
@@ -9278,10 +9324,15 @@ const OUTPOST_EXEC_TIMEOUT_MS = 12 * 6e4;
9278
9324
  const OUTPOST_MAX_OUTPUT_BYTES = 1 << 20;
9279
9325
  function capOutpostOutput(s) {
9280
9326
  if (!s) return "";
9281
- if (s.length <= OUTPOST_MAX_OUTPUT_BYTES) return s;
9282
- const dropped = s.length - OUTPOST_MAX_OUTPUT_BYTES;
9283
- return `[svamp-daemon] output truncated: ${dropped} earlier characters dropped]
9284
- ${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}`;
9285
9336
  }
9286
9337
  class OutpostCoordinator {
9287
9338
  conns = /* @__PURE__ */ new Map();
@@ -9399,9 +9450,14 @@ class OutpostCoordinator {
9399
9450
  resolveTarget(target) {
9400
9451
  const live = this.listConnections();
9401
9452
  if (target) {
9402
- const hit = live.find((c) => c.machine === target || c.label === target);
9403
- if (!hit) return { error: `no connected outpost named "${target}" (connected: ${live.map((c) => c.machine).join(", ") || "none"})` };
9404
- 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"})` };
9405
9461
  }
9406
9462
  if (live.length === 0) return { error: "no remote outpost is connected \u2014 run the install one-liner on the remote machine first" };
9407
9463
  if (live.length > 1) return { error: `multiple outposts connected (${live.map((c) => c.machine).join(", ")}) \u2014 pass a target` };
@@ -10015,7 +10071,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
10015
10071
  ensureParticipantsChannel(initialMetadata.sharing);
10016
10072
  const forkClaudeOnce = async (prompt, claudeSessionId, cwd, maxTurns, timeoutMs) => {
10017
10073
  const { spawn } = await import('child_process');
10018
- const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
10074
+ const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1", DISABLE_AUTOUPDATER: "1" };
10019
10075
  applyClaudeProxyEnv(forkEnv);
10020
10076
  return new Promise((resolve) => {
10021
10077
  const child = spawn("claude", [
@@ -11609,6 +11665,53 @@ function buildSessionIdentityEnv(identity) {
11609
11665
  return env;
11610
11666
  }
11611
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
+
11612
11715
  const HOTRELOAD_ENV = "SVAMP_DEV_HOTRELOAD";
11613
11716
  function isHotReloadEnabled(env = process.env) {
11614
11717
  const v = env[HOTRELOAD_ENV];
@@ -14219,7 +14322,7 @@ function codexCumulativeTokens(payload) {
14219
14322
  return nonCachedInput + output + Math.round(cached * 0.1);
14220
14323
  }
14221
14324
 
14222
- const PINNED_CODEX_VERSION = "0.147.0";
14325
+ const PINNED_CODEX_VERSION = "0.149.0";
14223
14326
  const MIN_CODEX_STEER_VERSION = { major: 0, minor: 129, patch: 0 };
14224
14327
  function cmpVer(a, b) {
14225
14328
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
@@ -15250,6 +15353,15 @@ function kimiProviderConfigured(configTomlText, provider) {
15250
15353
  const block = nextTable < 0 ? rest : rest.slice(0, nextTable);
15251
15354
  return /(^|\n)\s*api_key\s*=/.test(block);
15252
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
+ }
15253
15365
  function kimiModelMenu(env = process.env) {
15254
15366
  return (env.SVAMP_KIMI_MODELS || "").split(",").map((s) => s.trim()).filter(Boolean).filter((m, i, a) => a.indexOf(m) === i);
15255
15367
  }
@@ -15271,8 +15383,10 @@ var kimiProvider = /*#__PURE__*/Object.freeze({
15271
15383
  buildKimiCatalogAddArgs: buildKimiCatalogAddArgs,
15272
15384
  hasKimiProvider: hasKimiProvider,
15273
15385
  kimiClampModel: kimiClampModel,
15386
+ kimiConfigComplete: kimiConfigComplete,
15274
15387
  kimiDefaultModel: kimiDefaultModel,
15275
15388
  kimiDefaultModelId: kimiDefaultModelId,
15389
+ kimiHasGlobalDefaultModel: kimiHasGlobalDefaultModel,
15276
15390
  kimiModelMenu: kimiModelMenu,
15277
15391
  kimiProviderConfigured: kimiProviderConfigured,
15278
15392
  kimiSpawnEnv: kimiSpawnEnv,
@@ -15291,7 +15405,7 @@ async function ensureKimiProviderConfigured(env = process.env, command = "kimi",
15291
15405
  }) {
15292
15406
  const cfg = resolveKimiProvider(env);
15293
15407
  if (!hasKimiProvider(cfg)) return false;
15294
- if (kimiProviderConfigured(readKimiConfig(), cfg.provider)) return true;
15408
+ if (kimiConfigComplete(readKimiConfig(), cfg.provider)) return true;
15295
15409
  const args = buildKimiCatalogAddArgs(cfg);
15296
15410
  if (!args) return false;
15297
15411
  try {
@@ -15309,7 +15423,11 @@ async function ensureKimiProviderConfigured(env = process.env, command = "kimi",
15309
15423
  );
15310
15424
  });
15311
15425
  log(`[kimi] configured provider '${cfg.provider}' in ${KIMI_CONFIG_PATH}`);
15312
- 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;
15313
15431
  } catch (err) {
15314
15432
  log(`[kimi] provider auto-config failed (${err?.message || err}); session may need kimi-auth`);
15315
15433
  return false;
@@ -16297,14 +16415,21 @@ class ProcessSupervisor {
16297
16415
  if (entry.stopping) throw new Error(`Process '${entry.spec.name}' is being stopped, try again shortly`);
16298
16416
  throw new Error(`Process '${entry.spec.name}' is already running`);
16299
16417
  }
16300
- entry.stopping = false;
16301
- this.clearTimers(entry);
16302
- if (entry.spec.desiredStopped) {
16303
- entry.spec.desiredStopped = false;
16304
- await this.persistSpec(entry.spec);
16305
- }
16306
- this.resetFailureTracking(entry);
16307
- 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
+ });
16308
16433
  }
16309
16434
  /**
16310
16435
  * Stop a running process. Does NOT remove it from supervision.
@@ -16314,7 +16439,7 @@ class ProcessSupervisor {
16314
16439
  */
16315
16440
  /**
16316
16441
  * #0347: run `fn` in the entry's lifecycle critical section. Each call awaits the previous
16317
- * 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
16318
16443
  * serialized per entry and can never interleave across an `await killChild` (which is what let
16319
16444
  * two ops double-spawn an orphan, or resurrect a just-stopped process). The chain tail never
16320
16445
  * rejects (a failed op must not wedge the queue); the caller still gets the real result/error.
@@ -16531,7 +16656,7 @@ class ProcessSupervisor {
16531
16656
  }
16532
16657
  async persistSpec(spec) {
16533
16658
  const filePath = path__default.join(this.persistDir, `${spec.id}.json`);
16534
- const tmpPath = filePath + ".tmp";
16659
+ const tmpPath = `${filePath}.tmp-${process.pid}-${randomBytes$1(4).toString("hex")}`;
16535
16660
  await writeFile$1(tmpPath, JSON.stringify(spec, null, 2), "utf-8");
16536
16661
  await rename(tmpPath, filePath);
16537
16662
  }
@@ -17110,7 +17235,7 @@ function searchIssues(projectRoot, query) {
17110
17235
  const q = query.trim();
17111
17236
  if (!q) return [];
17112
17237
  try {
17113
- 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" });
17114
17239
  const files = out.split("\n").filter(Boolean);
17115
17240
  const issues = [];
17116
17241
  for (const f of files) {
@@ -17121,9 +17246,24 @@ function searchIssues(projectRoot, query) {
17121
17246
  }
17122
17247
  }
17123
17248
  return issues.sort((a, b) => Number(a.id) - Number(b.id));
17124
- } catch {
17125
- const ql = q.toLowerCase();
17126
- 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 };
17127
17267
  }
17128
17268
  }
17129
17269
  function summarize(issues) {
@@ -17166,11 +17306,29 @@ function resolveLogPrunePolicy(env = process.env) {
17166
17306
  maxBytes: num(env.SVAMP_LOG_MAX_BYTES, DEFAULT_LOG_PRUNE_POLICY.maxBytes)
17167
17307
  };
17168
17308
  }
17169
- function tailSlice(buf, tailBytes) {
17170
- if (buf.length <= tailBytes) return buf;
17171
- const cut = buf.subarray(buf.length - tailBytes);
17172
- const nl = cut.indexOf(10);
17173
- 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
+ }
17174
17332
  }
17175
17333
 
17176
17334
  const CARRIER_FIELDS = ["message", "error", "detail", "reason", "description"];
@@ -17889,6 +18047,15 @@ function isCostCeiling(kind) {
17889
18047
  function isHardBudgetStop(kind) {
17890
18048
  return isCostCeiling(kind) || kind === "iteration";
17891
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
+ }
17892
18059
  function checkLoopBudget(ledger, limits, now, startedAt) {
17893
18060
  const L = limits || {};
17894
18061
  const led = ledger || { turns: 0, tokens: 0, window: [] };
@@ -18368,6 +18535,47 @@ function isContextTooLongError(text) {
18368
18535
  if (t.includes("context") && t.includes("exceed") && t.includes("limit") && !t.includes("subscription")) return true;
18369
18536
  return false;
18370
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
+ }
18371
18579
 
18372
18580
  function fmtTokens(n) {
18373
18581
  return Math.max(0, Math.round(n)).toLocaleString("en-US");
@@ -18941,23 +19149,27 @@ function compareVersions(a, b) {
18941
19149
  return 0;
18942
19150
  }
18943
19151
  async function installSkillFromEndpoint(name, baseUrl) {
18944
- const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
18945
- if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
18946
- const index = await resp.json();
19152
+ const index = await fetchEndpointSkillIndex(baseUrl);
18947
19153
  const files = index.files || [];
18948
19154
  if (files.length === 0) throw new Error(`Skill index at ${baseUrl} has no files`);
18949
- const targetDir = join(CLAUDE_SKILLS_DIR, name);
18950
- mkdirSync(targetDir, { recursive: true });
18951
- for (const filePath of files) {
18952
- if (!filePath) continue;
18953
- const url = `${baseUrl}${filePath}`;
18954
- 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) });
18955
19157
  if (!fileResp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${fileResp.status}`);
18956
- const content = await fileResp.text();
18957
- const localPath = join(targetDir, filePath);
18958
- if (!localPath.startsWith(targetDir + "/")) continue;
18959
- mkdirSync(dirname$1(localPath), { recursive: true });
18960
- 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;
18961
19173
  }
18962
19174
  }
18963
19175
  async function installSkillFromMarketplace(name) {
@@ -18981,26 +19193,11 @@ async function installSkillFromMarketplace(name) {
18981
19193
  }
18982
19194
  const files = await collectFiles();
18983
19195
  if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
18984
- const targetDir = join(CLAUDE_SKILLS_DIR, name);
18985
- const stagingDir = `${targetDir}.tmp-${process.pid}-${Date.now().toString(36)}`;
18986
- try {
18987
- mkdirSync(stagingDir, { recursive: true });
18988
- for (const filePath of files) {
18989
- const url = `${BASE}/files/${filePath}`;
18990
- const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
18991
- if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
18992
- const content = await resp.text();
18993
- const localPath = join(stagingDir, filePath);
18994
- if (!localPath.startsWith(stagingDir + "/")) continue;
18995
- mkdirSync(dirname$1(localPath), { recursive: true });
18996
- writeFileSync(localPath, content, "utf-8");
18997
- }
18998
- rmSync(targetDir, { recursive: true, force: true });
18999
- renameSync$1(stagingDir, targetDir);
19000
- } catch (err) {
19001
- rmSync(stagingDir, { recursive: true, force: true });
19002
- throw err;
19003
- }
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
+ });
19004
19201
  }
19005
19202
  function getBundledSkillsDir() {
19006
19203
  try {
@@ -19219,9 +19416,16 @@ async function ensureAutoInstalledSkills(logger) {
19219
19416
  marketplaceVersion: () => fetchMarketplaceSkillVersion("svamp")
19220
19417
  },
19221
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.
19222
19426
  name: "hypha",
19223
- install: () => installSkillFromEndpoint("hypha", `${getSkillsServer()}/ws/agent-skills/`)
19224
- // 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/`)
19225
19429
  },
19226
19430
  {
19227
19431
  // `<artifact>` block rendering — see packages/svamp-app/.../Artifact.tsx.
@@ -19787,13 +19991,13 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
19787
19991
  };
19788
19992
  let announcedLoopStart = (() => {
19789
19993
  const s = readLoopState(directory, sessionId);
19790
- 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;
19791
19995
  })();
19792
19996
  const loopChecker = () => {
19793
19997
  try {
19794
19998
  const s = readLoopState(directory, sessionId);
19795
19999
  if (!s || s.phase !== "done" && s.phase !== "gave_up") return;
19796
- const key = s.started_at ?? "\u2205";
20000
+ const key = String(s.started_at ?? "\u2205");
19797
20001
  if (key === announcedLoopStart) return;
19798
20002
  announcedLoopStart = key;
19799
20003
  const iter = s.ledger && typeof s.ledger.turns === "number" && s.ledger.turns > 0 ? s.ledger.turns : typeof s.iteration === "number" ? s.iteration : 0;
@@ -20185,7 +20389,9 @@ function pruneDaemonLogs(exclude = []) {
20185
20389
  for (const { name } of plan.truncateFiles) {
20186
20390
  try {
20187
20391
  const p = join(LOGS_DIR, name);
20188
- 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;
20189
20395
  writeFileSync(p, kept);
20190
20396
  } catch {
20191
20397
  }
@@ -20498,7 +20704,7 @@ async function startDaemon(options) {
20498
20704
  try {
20499
20705
  const dir = loadSessionIndex()[sessionId]?.directory;
20500
20706
  if (!dir) return;
20501
- const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20707
+ const { reconcileServiceLinks } = await import('./agentCommands-D26EUFSD.mjs');
20502
20708
  const configPath = getSvampConfigPath(dir, sessionId);
20503
20709
  const config = readSvampConfig(configPath);
20504
20710
  const entries = Array.from(urls.entries());
@@ -20520,7 +20726,7 @@ async function startDaemon(options) {
20520
20726
  try {
20521
20727
  const dir = loadSessionIndex()[sessionId]?.directory;
20522
20728
  if (!dir) return;
20523
- const { reconcileServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20729
+ const { reconcileServiceLinks } = await import('./agentCommands-D26EUFSD.mjs');
20524
20730
  const configPath = getSvampConfigPath(dir, sessionId);
20525
20731
  const config = readSvampConfig(configPath);
20526
20732
  const incoming = [{
@@ -20541,7 +20747,7 @@ async function startDaemon(options) {
20541
20747
  try {
20542
20748
  const dir = loadSessionIndex()[sessionId]?.directory;
20543
20749
  if (!dir) return;
20544
- const { dropServiceLinks } = await import('./agentCommands-DD47Krr1.mjs');
20750
+ const { dropServiceLinks } = await import('./agentCommands-D26EUFSD.mjs');
20545
20751
  const configPath = getSvampConfigPath(dir, sessionId);
20546
20752
  const config = readSvampConfig(configPath);
20547
20753
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -20552,6 +20758,81 @@ async function startDaemon(options) {
20552
20758
  logger.log(`[serve] Link drop failed for mount ${mountName}: ${err?.message || err}`);
20553
20759
  }
20554
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-D26EUFSD.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
+ let liveSessionDirs;
20778
+ async function reconcileAllServiceLinks() {
20779
+ try {
20780
+ const { reconcileServiceLinksExact } = await import('./agentCommands-D26EUFSD.mjs');
20781
+ const desiredBySession = /* @__PURE__ */ new Map();
20782
+ const push = (sessionId, entry) => {
20783
+ if (!sessionId || !entry.url) return;
20784
+ const list = desiredBySession.get(sessionId) ?? [];
20785
+ list.push(entry);
20786
+ desiredBySession.set(sessionId, list);
20787
+ };
20788
+ for (const m of serveManager$1.listMounts()) {
20789
+ if (m?.sessionId && m?.url) push(m.sessionId, { url: String(m.url), label: m.name, service: { kind: "serve", name: m.name } });
20790
+ }
20791
+ const specBySession = /* @__PURE__ */ new Map();
20792
+ for (const spec of loadExposedTunnels()) if (spec.sessionId) specBySession.set(spec.name, spec.sessionId);
20793
+ for (const [name, tunnel] of tunnels) {
20794
+ const sessionId = specBySession.get(name);
20795
+ if (!sessionId) continue;
20796
+ const urls = Array.from(tunnel.getUrls().entries());
20797
+ for (const [port, url] of urls) {
20798
+ push(sessionId, { url: String(url), label: urls.length > 1 ? `${name}:${port}` : name, service: { kind: "tunnel", name } });
20799
+ }
20800
+ }
20801
+ const dirBySession = /* @__PURE__ */ new Map();
20802
+ for (const [id, entry] of Object.entries(loadSessionIndex())) {
20803
+ if (entry?.directory) dirBySession.set(id, entry.directory);
20804
+ }
20805
+ for (const s of liveSessionDirs?.() ?? []) {
20806
+ if (s.sessionId && s.directory) dirBySession.set(s.sessionId, s.directory);
20807
+ }
20808
+ const sessionIds = /* @__PURE__ */ new Set([...dirBySession.keys(), ...desiredBySession.keys()]);
20809
+ for (const sessionId of sessionIds) {
20810
+ try {
20811
+ const dir = dirBySession.get(sessionId);
20812
+ if (!dir) continue;
20813
+ const configPath = getSvampConfigPath(dir, sessionId);
20814
+ const config = readSvampConfig(configPath);
20815
+ const desired = desiredBySession.get(sessionId) ?? [];
20816
+ if (reconcileServiceLinksExact(config, ["serve", "tunnel"], desired)) {
20817
+ writeSvampConfig(configPath, config);
20818
+ logger.log(`[launchpad] Reconciled service links for session ${sessionId} (${desired.length} live)`);
20819
+ }
20820
+ } catch (err) {
20821
+ logger.log(`[launchpad] Reconcile failed for session ${sessionId}: ${err?.message || err}`);
20822
+ }
20823
+ }
20824
+ } catch (err) {
20825
+ logger.log(`[launchpad] Service-link sweep failed: ${err?.message || err}`);
20826
+ }
20827
+ }
20828
+ let serviceLinkReconcileTimer;
20829
+ function scheduleServiceLinkReconcile(delayMs = 1200) {
20830
+ if (serviceLinkReconcileTimer) clearTimeout(serviceLinkReconcileTimer);
20831
+ serviceLinkReconcileTimer = setTimeout(() => {
20832
+ serviceLinkReconcileTimer = void 0;
20833
+ void reconcileAllServiceLinks();
20834
+ }, delayMs);
20835
+ }
20555
20836
  async function createExposedTunnel(spec) {
20556
20837
  const { FrpcTunnel } = await Promise.resolve().then(function () { return frpc; });
20557
20838
  const tunnel = new FrpcTunnel({
@@ -20584,8 +20865,18 @@ async function startDaemon(options) {
20584
20865
  ensureAutoInstalledCommands(logger);
20585
20866
  (async () => {
20586
20867
  try {
20587
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-BO0Qs_kE.mjs');
20588
- beginClaudeVersionReconcile((msg) => logger.log(msg));
20868
+ const { beginClaudeVersionReconcile, CLAUDE_VERSION_RECHECK_MS } = await import('./pinnedClaudeCode-C43KnZ-Q.mjs');
20869
+ const onResult = (result) => {
20870
+ if (result?.kind === "install-failed" || result?.kind === "skipped" || result?.kind === "not-installed") {
20871
+ logger.log(`[claude-version] WARNING \u2014 machine is NOT on the pinned Claude Code (${result.kind}). Sessions are running an untested build.`);
20872
+ }
20873
+ };
20874
+ beginClaudeVersionReconcile((msg) => logger.log(msg), { onResult });
20875
+ const jitter = Math.floor(Math.random() * 5 * 6e4);
20876
+ const timer = setInterval(() => {
20877
+ beginClaudeVersionReconcile((msg) => logger.log(msg), { onResult });
20878
+ }, CLAUDE_VERSION_RECHECK_MS + jitter);
20879
+ timer.unref?.();
20589
20880
  } catch (e) {
20590
20881
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
20591
20882
  }
@@ -20629,6 +20920,7 @@ async function startDaemon(options) {
20629
20920
  }
20630
20921
  });
20631
20922
  const pidToTrackedSession = /* @__PURE__ */ new Map();
20923
+ liveSessionDirs = () => Array.from(pidToTrackedSession.values()).filter((s) => s.svampSessionId && !s.stopped).map((s) => ({ sessionId: s.svampSessionId, directory: s.directory }));
20632
20924
  let sessionCoreRef = {
20633
20925
  splitStreamLines,
20634
20926
  handleClaudeMessage
@@ -21078,7 +21370,7 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
21078
21370
  const cwd = sessionMetadata.path || directory;
21079
21371
  const model = process.env.SVAMP_TOPIC_MODEL;
21080
21372
  try {
21081
- const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
21373
+ const forkEnv = { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1", DISABLE_AUTOUPDATER: "1" };
21082
21374
  applyClaudeProxyEnv(forkEnv);
21083
21375
  const child = spawn("claude", [
21084
21376
  "--print",
@@ -21517,6 +21809,9 @@ ${parts.join("\n")}`);
21517
21809
  let autoCompactWasAutoInjected = false;
21518
21810
  let autoCompactDisabled = false;
21519
21811
  let autoCompactRecoveryTried = false;
21812
+ let justCompactedTurn = false;
21813
+ let compactNoBoundaryStrikes = 0;
21814
+ let compactSupported = void 0;
21520
21815
  let noIsolationWarned = false;
21521
21816
  const RATELIMIT_CFG = getRateLimitRetryConfig();
21522
21817
  let currentTurnMessage;
@@ -21962,15 +22257,30 @@ ${parts.join("\n")}`);
21962
22257
  pendingCompactTurn = false;
21963
22258
  const wasAuto = autoCompactWasAutoInjected;
21964
22259
  autoCompactWasAutoInjected = false;
21965
- if (wasAuto) {
22260
+ const d = decideCompactUnsupported({
22261
+ wasAutoInjected: wasAuto,
22262
+ turnErrored: !!msg.is_error,
22263
+ compactSupported,
22264
+ priorStrikes: compactNoBoundaryStrikes,
22265
+ maxStrikes: DEFAULT_COMPACT_NO_BOUNDARY_STRIKES
22266
+ });
22267
+ compactNoBoundaryStrikes = d.strikes;
22268
+ if (d.disable) {
21966
22269
  autoCompactDisabled = true;
21967
- logger.log(`[Session ${sessionId}] Auto /compact unsupported by this Claude Code \u2014 disabling the auto-compaction backstop for this session`);
22270
+ logger.log(`[Session ${sessionId}] Disabling the auto-compaction backstop for this session (${d.reason})`);
22271
+ }
22272
+ logger.log(`[Session ${sessionId}] /compact produced no compaction boundary (reason=${d.reason}, strikes=${d.strikes}, supported=${String(compactSupported)}, errored=${!!msg.is_error})`);
22273
+ if (d.warnUnsupported) {
22274
+ sessionService.pushMessage(
22275
+ { type: "message", message: compactUnsupportedWarning(), level: "warning" },
22276
+ "event"
22277
+ );
22278
+ } else if (wasAuto && d.reason !== "errored-turn") {
22279
+ sessionService.pushMessage(
22280
+ { type: "message", message: describeNoBoundary(d.strikes, DEFAULT_COMPACT_NO_BOUNDARY_STRIKES), level: "warning" },
22281
+ "event"
22282
+ );
21968
22283
  }
21969
- logger.log(`[Session ${sessionId}] /compact produced no compaction boundary \u2014 Claude Code likely too old to handle it in stream-json mode`);
21970
- sessionService.pushMessage(
21971
- { type: "message", message: compactUnsupportedWarning(), level: "warning" },
21972
- "event"
21973
- );
21974
22284
  }
21975
22285
  if (!msg.is_error) {
21976
22286
  resetRateLimitStreak();
@@ -22061,7 +22371,15 @@ ${parts.join("\n")}`);
22061
22371
  logger.log(`[Session ${sessionId}] ${taskInfo}`);
22062
22372
  sessionService.pushMessage({ type: "session_event", message: taskInfo }, "session");
22063
22373
  }
22064
- if (!msg.is_error && !pendingCompactTurn && !autoCompactDisabled && autoCompactEnabled) {
22374
+ const mayEvaluateLayer1 = shouldEvaluateProactiveCompaction({
22375
+ justCompacted: justCompactedTurn,
22376
+ compactPending: pendingCompactTurn,
22377
+ turnErrored: !!msg.is_error,
22378
+ disabled: autoCompactDisabled,
22379
+ enabled: autoCompactEnabled
22380
+ });
22381
+ justCompactedTurn = false;
22382
+ if (mayEvaluateLayer1) {
22065
22383
  try {
22066
22384
  const observed = computeObservedContext(msg.usage);
22067
22385
  const decision = decideProactiveCompaction({
@@ -22125,6 +22443,7 @@ ${parts.join("\n")}`);
22125
22443
  userMessagePending = false;
22126
22444
  if (msg.slash_commands && Array.isArray(msg.slash_commands)) {
22127
22445
  sessionMetadata = { ...sessionMetadata, slashCommands: msg.slash_commands };
22446
+ compactSupported = compactSupportedFromSlashCommands(msg.slash_commands);
22128
22447
  }
22129
22448
  if (typeof msg.model === "string" && msg.model.length > 0) {
22130
22449
  lastMainModel = msg.model;
@@ -22178,6 +22497,8 @@ ${parts.join("\n")}`);
22178
22497
  pendingCompactTurn = false;
22179
22498
  autoCompactWasAutoInjected = false;
22180
22499
  autoCompactRecoveryTried = false;
22500
+ justCompactedTurn = true;
22501
+ compactNoBoundaryStrikes = 0;
22181
22502
  const compactLine = describeCompaction(msg.compact_metadata);
22182
22503
  logger.log(`[Session ${sessionId}] ${compactLine}`);
22183
22504
  sessionService.pushMessage(
@@ -22994,11 +23315,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22994
23315
  });
22995
23316
  },
22996
23317
  onIssue: async (params) => {
22997
- const { issueRpc } = await import('./rpc-DObWvEis.mjs');
23318
+ const { issueRpc } = await import('./rpc-BsgXlOhI.mjs');
22998
23319
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22999
23320
  },
23000
23321
  onWorkflow: async (params) => {
23001
- const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
23322
+ const { workflowRpc } = await import('./rpc-C6zBQ0L5.mjs');
23002
23323
  return workflowRpc(params?.cwd || directory, params || {});
23003
23324
  },
23004
23325
  onRipgrep: async (args, cwd) => {
@@ -23218,6 +23539,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23218
23539
  active: true,
23219
23540
  phase: "continue",
23220
23541
  iteration: 0,
23542
+ started_at: rearmStartedAt(s.started_at, s.completed_at, Date.now()),
23543
+ // #1170
23221
23544
  completed_at: void 0,
23222
23545
  gave_up_reason: void 0,
23223
23546
  verify_errors: 0,
@@ -23719,11 +24042,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23719
24042
  });
23720
24043
  },
23721
24044
  onIssue: async (params) => {
23722
- const { issueRpc } = await import('./rpc-DObWvEis.mjs');
24045
+ const { issueRpc } = await import('./rpc-BsgXlOhI.mjs');
23723
24046
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23724
24047
  },
23725
24048
  onWorkflow: async (params) => {
23726
- const { workflowRpc } = await import('./rpc-Bv6_c-xW.mjs');
24049
+ const { workflowRpc } = await import('./rpc-C6zBQ0L5.mjs');
23727
24050
  return workflowRpc(params?.cwd || directory, params || {});
23728
24051
  },
23729
24052
  onRipgrep: async (args, cwd) => {
@@ -24588,7 +24911,11 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24588
24911
  sharingNotificationSync,
24589
24912
  persistExposedTunnel,
24590
24913
  forgetExposedTunnel,
24591
- listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, sessionId: t.sessionId, addedAt: t.addedAt }))
24914
+ listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, sessionId: t.sessionId, addedAt: t.addedAt })),
24915
+ dropTunnelSessionLink: (sessionId, tunnelName) => {
24916
+ void dropTunnelSessionLink(sessionId, tunnelName);
24917
+ },
24918
+ scheduleServiceLinkReconcile: () => scheduleServiceLinkReconcile()
24592
24919
  });
24593
24920
  const machineService = await registerMachineService(
24594
24921
  server,
@@ -24720,6 +25047,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24720
25047
  } catch (err) {
24721
25048
  logger.log(`[serve] mount link reconcile failed: ${err?.message || err}`);
24722
25049
  }
25050
+ scheduleServiceLinkReconcile(3e3);
24723
25051
  }).catch((err) => logger.error(`[serve] mount restore failed: ${err?.message || err}`));
24724
25052
  const daemonOwnerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || null;
24725
25053
  serveManager$1.setSessionResolver((sessionId) => {
@@ -24803,7 +25131,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24803
25131
  }
24804
25132
  if (persistedSessions.length > 0) {
24805
25133
  try {
24806
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-BO0Qs_kE.mjs');
25134
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-C43KnZ-Q.mjs');
24807
25135
  await awaitClaudeVersionReady();
24808
25136
  } catch {
24809
25137
  }
@@ -25069,7 +25397,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
25069
25397
  const PING_TIMEOUT_MS = 15e3;
25070
25398
  const POST_RECONNECT_GRACE_MS = 2e4;
25071
25399
  const RECONNECT_JITTER_MS = 2500;
25072
- const { WorkflowScheduler } = await import('./scheduler-BvtNZ0pl.mjs');
25400
+ const { WorkflowScheduler } = await import('./scheduler-Ciu9Qfsi.mjs');
25073
25401
  const workflowProjectRoots = () => {
25074
25402
  const dirs = /* @__PURE__ */ new Set();
25075
25403
  for (const s of pidToTrackedSession.values()) {
@@ -25108,6 +25436,9 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
25108
25436
  };
25109
25437
  runOAuthAccountRefresh();
25110
25438
  const backendAccountRefreshInterval = setInterval(runOAuthAccountRefresh, 5 * 6e4);
25439
+ const serviceLinkSweepInterval = setInterval(() => {
25440
+ void reconcileAllServiceLinks();
25441
+ }, 6e4);
25111
25442
  let heartbeatRunning = false;
25112
25443
  let heartbeatCycle = 0;
25113
25444
  const heartbeatInterval = setInterval(async () => {
@@ -25360,6 +25691,8 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
25360
25691
  clearInterval(heartbeatInterval);
25361
25692
  clearInterval(workflowSchedulerInterval);
25362
25693
  clearInterval(backendAccountRefreshInterval);
25694
+ clearInterval(serviceLinkSweepInterval);
25695
+ if (serviceLinkReconcileTimer) clearTimeout(serviceLinkReconcileTimer);
25363
25696
  if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
25364
25697
  if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
25365
25698
  if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
@@ -25704,4 +26037,4 @@ var run = /*#__PURE__*/Object.freeze({
25704
26037
  writeStopMarker: writeStopMarker
25705
26038
  });
25706
26039
 
25707
- 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 };
26040
+ 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 };