svamp-cli 0.2.327 → 0.2.329

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/README.md +27 -6
  2. package/dist/{adminCommands-BVXEj5XN.mjs → adminCommands-C-p7ayqB.mjs} +1 -1
  3. package/dist/{agentCommands-BiSVzb5D.mjs → agentCommands-B4sCl_5R.mjs} +5 -5
  4. package/dist/{auth-D0ji0kh3.mjs → auth-X_CjxN_J.mjs} +1 -1
  5. package/dist/{cli-CQlIsRxF.mjs → cli-BY4DQnRs.mjs} +79 -78
  6. package/dist/cli.mjs +2 -2
  7. package/dist/{commands-C4z-QQdO.mjs → commands-6uHR7YHf.mjs} +11 -11
  8. package/dist/{commands-D5R0AmXt.mjs → commands-BInj_IDQ.mjs} +3 -3
  9. package/dist/{commands-BkzYDwHO.mjs → commands-BQjZb1eL.mjs} +3 -3
  10. package/dist/{commands-Cegv66fj.mjs → commands-BR5FB3lB.mjs} +10 -2
  11. package/dist/{commands-D61SEgo0.mjs → commands-C8uGnx2z.mjs} +2 -2
  12. package/dist/{commands-CXJmXwQo.mjs → commands-CEURyD-l.mjs} +1 -1
  13. package/dist/{commands-DexO3t-N.mjs → commands-rKfH5E8g.mjs} +34 -27
  14. package/dist/{commands-CPZdcLvm.mjs → commands-rmxjAHLz.mjs} +12 -4
  15. package/dist/{fleet-CCVC-NiE.mjs → fleet-CxxkWc3z.mjs} +8 -4
  16. package/dist/{headlessCli-CFgDFTPd.mjs → headlessCli-NkXGxjrT.mjs} +2 -2
  17. package/dist/index.mjs +1 -1
  18. package/dist/{notifyCommands-BfLeyHId.mjs → notifyCommands-CEvdTCIS.mjs} +1 -1
  19. package/dist/package-BNriEfp8.mjs +64 -0
  20. package/dist/{rpc-hdw0ZlCj.mjs → rpc-fZm83jEI.mjs} +2 -1
  21. package/dist/{rpc-DXY3KqiA.mjs → rpc-q5KTMSRl.mjs} +1 -1
  22. package/dist/{run-DdTcDN3e.mjs → run-1CHjd0_g.mjs} +164 -32
  23. package/dist/{run-CewXMfIf.mjs → run-C7JwLKGe.mjs} +1 -1
  24. package/dist/{scheduler-3LUL1jFD.mjs → scheduler-DTRcVW02.mjs} +1 -1
  25. package/dist/{serveCommands-DUJ4eojk.mjs → serveCommands-CXlZ6Bdy.mjs} +19 -11
  26. package/dist/{sideband-BRHu4M46.mjs → sideband-C4QaSCEM.mjs} +1 -1
  27. package/package.json +4 -4
  28. package/dist/package-TcR2vlFS.mjs +0 -64
@@ -3341,7 +3341,8 @@ class ServeManager {
3341
3341
  if (replaced) {
3342
3342
  await this.removeMount(spec.name, { replacing: true });
3343
3343
  }
3344
- if (replaced?.sessionId && replaced.sessionId !== spec.sessionId) {
3344
+ const crossSessionTakeover = !!(replaced?.sessionId && replaced.sessionId !== spec.sessionId);
3345
+ if (crossSessionTakeover) {
3345
3346
  this.log(`Mount '${spec.name}': taken over by session ${spec.sessionId || "(none)"} from ${replaced.sessionId} \u2014 the previous owner's Launch Pad link is being dropped (its URL no longer resolves).`);
3346
3347
  this.fireMountUnbound(replaced.sessionId, spec.name);
3347
3348
  }
@@ -3349,6 +3350,11 @@ class ServeManager {
3349
3350
  if (access === "owner" && !spec.ownerEmail) {
3350
3351
  this.log(`\u26A0 Mount '${spec.name}': access='owner' but no owner email could be resolved \u2014 NO ONE will be able to sign in. Use an explicit email allowlist instead, e.g. access: ["you@example.com"].`);
3351
3352
  }
3353
+ const sameTarget = !!replaced && replaced.directory === resolvedDir && JSON.stringify(replaced.process ?? null) === JSON.stringify(spec.process ?? null);
3354
+ const reusableToken = replaced && access === "link" && replaced.linkToken && !spec.regenerateUrl && !crossSessionTakeover && sameTarget ? replaced.linkToken : void 0;
3355
+ if (replaced && access === "link" && replaced.linkToken && !sameTarget && !spec.regenerateUrl && !crossSessionTakeover) {
3356
+ this.log(`Mount '${spec.name}': target changed \u2014 minting a NEW capability URL. The previously shared link no longer resolves (it would otherwise have followed the mount to its new target).`);
3357
+ }
3352
3358
  const mount = {
3353
3359
  name: spec.name,
3354
3360
  directory: resolvedDir,
@@ -3356,10 +3362,10 @@ class ServeManager {
3356
3362
  sessionId: spec.sessionId,
3357
3363
  ownerEmail: spec.ownerEmail,
3358
3364
  access,
3359
- // Generate a capability token if access is 'link' and we don't
3360
- // already have one from persisted state (token is stable across
3361
- // restarts).
3362
- linkToken: access === "link" ? spec.linkToken || generateLinkToken() : void 0,
3365
+ // Capability token for 'link' access. Precedence: an explicit spec.linkToken
3366
+ // (used by restore to preserve URLs across daemon restarts) → the existing
3367
+ // mount's token on a stable re-apply (#1072) → a freshly generated token.
3368
+ linkToken: access === "link" ? spec.linkToken || reusableToken || generateLinkToken() : void 0,
3363
3369
  addedAt: Date.now()
3364
3370
  };
3365
3371
  this.mounts.set(spec.name, mount);
@@ -4716,6 +4722,7 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
4716
4722
  MANAGED_ENV_KEYS: MANAGED_ENV_KEYS,
4717
4723
  applyClaudeProxyEnv: applyClaudeProxyEnv,
4718
4724
  getClaudeAuthStatus: getClaudeAuthStatus,
4725
+ normalizeProxyUrl: normalizeProxyUrl,
4719
4726
  resolveHyphaProxyUrl: resolveHyphaProxyUrl,
4720
4727
  setClaudeAuthCustom: setClaudeAuthCustom,
4721
4728
  setClaudeAuthHyphaProxy: setClaudeAuthHyphaProxy,
@@ -4986,7 +4993,8 @@ function resolveAccountEnv(account, env = process.env, svampHome) {
4986
4993
  };
4987
4994
  }
4988
4995
  case "hypha-proxy": {
4989
- const url = account.baseUrl ? stripV1(account.baseUrl) : resolveHyphaProxyUrl();
4996
+ const envProxyUrl = (env.SVAMP_HYPHA_PROXY_URL || "").trim();
4997
+ const url = account.baseUrl ? stripV1(account.baseUrl) : envProxyUrl ? normalizeProxyUrl(envProxyUrl) : resolveHyphaProxyUrl();
4990
4998
  const token = env.HYPHA_TOKEN;
4991
4999
  if (!url) throw new Error("hypha-proxy account has no URL (set SVAMP_HYPHA_PROXY_URL or an account base URL)");
4992
5000
  if (!token) throw new Error("hypha-proxy account requires HYPHA_TOKEN on the machine");
@@ -5375,7 +5383,25 @@ function cancelFlow(flowId) {
5375
5383
  flows.delete(flowId);
5376
5384
  }
5377
5385
 
5378
- const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
5386
+ const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
5387
+ const MONTH_NAMES = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
5388
+ const DOW_NAMES = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
5389
+ function substituteNames(token, fieldIndex) {
5390
+ const names = fieldIndex === 3 ? MONTH_NAMES : fieldIndex === 4 ? DOW_NAMES : null;
5391
+ if (!names) return token;
5392
+ return token.replace(/[A-Za-z]+/g, (word) => {
5393
+ const i = names.indexOf(word.toUpperCase());
5394
+ if (i < 0) return word;
5395
+ return String(fieldIndex === 3 ? i + 1 : i);
5396
+ });
5397
+ }
5398
+ function normalizeDow(set) {
5399
+ if (!set.has(7)) return set;
5400
+ const out = new Set(set);
5401
+ out.delete(7);
5402
+ out.add(0);
5403
+ return out;
5404
+ }
5379
5405
  function parseField(token, [min, max]) {
5380
5406
  const set = /* @__PURE__ */ new Set();
5381
5407
  for (const part of token.split(",")) {
@@ -5404,8 +5430,11 @@ function parseField(token, [min, max]) {
5404
5430
  function parseCron(expr) {
5405
5431
  const fields = String(expr).trim().split(/\s+/);
5406
5432
  if (fields.length !== 5) throw new Error(`cron must have 5 fields, got ${fields.length}: "${expr}"`);
5407
- const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(f, FIELD_RANGES[i]));
5408
- return { minute, hour, dom, month, dow, domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
5433
+ const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(substituteNames(f, i), FIELD_RANGES[i]));
5434
+ return { minute, hour, dom, month, dow: normalizeDow(dow), domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
5435
+ }
5436
+ function validateCronExpr(expr) {
5437
+ parseCron(expr);
5409
5438
  }
5410
5439
  function cronMatches(expr, date) {
5411
5440
  const c = typeof expr === "string" ? parseCron(expr) : expr;
@@ -7940,7 +7969,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7940
7969
  process: params.process,
7941
7970
  sessionId: params.sessionId,
7942
7971
  access,
7943
- ownerEmail: ownerEmail2
7972
+ ownerEmail: ownerEmail2,
7973
+ regenerateUrl: params.regenerateUrl
7944
7974
  });
7945
7975
  },
7946
7976
  /** Remove a mount from the shared static file server. */
@@ -8302,7 +8332,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8302
8332
  }
8303
8333
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
8304
8334
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
8305
- const { toolsForRole } = await import('./sideband-BRHu4M46.mjs');
8335
+ const { toolsForRole } = await import('./sideband-C4QaSCEM.mjs');
8306
8336
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
8307
8337
  return fmt(r2);
8308
8338
  }
@@ -8407,18 +8437,21 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8407
8437
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8408
8438
  }
8409
8439
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8410
- const { queryCore } = await import('./commands-DexO3t-N.mjs');
8440
+ const { queryCore } = await import('./commands-rKfH5E8g.mjs');
8411
8441
  const timeout = c.reply?.timeout_sec || 120;
8412
8442
  let result;
8443
+ let thrownSessionId;
8413
8444
  try {
8414
8445
  result = await queryCore(selfMachine, dir, rendered, { permissionMode: "bypassPermissions", tag: "svamp-channel", timeout });
8415
8446
  } catch (e) {
8447
+ thrownSessionId = e?.sessionId || void 0;
8416
8448
  return { ok: false, call_id: callId, status: "error", error: e?.message || String(e) };
8417
8449
  } finally {
8418
8450
  statelessLimiter.release(senderKey);
8419
- if (result?.sessionId) {
8451
+ const ephemeralId = result?.sessionId || thrownSessionId;
8452
+ if (ephemeralId) {
8420
8453
  try {
8421
- handlers.deleteSession?.(result.sessionId);
8454
+ handlers.deleteSession?.(ephemeralId);
8422
8455
  } catch {
8423
8456
  }
8424
8457
  }
@@ -8971,6 +9004,11 @@ function nextBackoffMs(prevMs) {
8971
9004
  if (!prevMs || prevMs <= 0) return RL_BACKOFF_MIN_MS;
8972
9005
  return Math.min(prevMs * 2, RL_BACKOFF_MAX_MS);
8973
9006
  }
9007
+ const LISTENER_ERROR_STRIKE_LIMIT = 3;
9008
+ function decideListenerErrorAction(prevStrikes, limit = LISTENER_ERROR_STRIKE_LIMIT) {
9009
+ const strikes = (prevStrikes > 0 ? prevStrikes : 0) + 1;
9010
+ return { strikes, drop: strikes >= limit };
9011
+ }
8974
9012
  function retryDecision(listenerPresent, now, backoffUntil) {
8975
9013
  if (!listenerPresent) return "drop";
8976
9014
  if (backoffUntil !== void 0 && now < backoffUntil) return "reschedule";
@@ -9712,11 +9750,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9712
9750
  const listeners = [];
9713
9751
  const rateLimitBackoff = /* @__PURE__ */ new Map();
9714
9752
  const rateLimitRetry = /* @__PURE__ */ new Map();
9753
+ const listenerErrorStrikes = /* @__PURE__ */ new Map();
9715
9754
  const removeListener = (listener, reason) => {
9716
9755
  const idx = listeners.indexOf(listener);
9717
9756
  if (idx >= 0) {
9718
9757
  listeners.splice(idx, 1);
9719
9758
  rateLimitBackoff.delete(listener);
9759
+ listenerErrorStrikes.delete(listener);
9720
9760
  const t = rateLimitRetry.get(listener);
9721
9761
  if (t) {
9722
9762
  clearTimeout(t);
@@ -9741,8 +9781,15 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9741
9781
  } else if (kind === "stale") {
9742
9782
  removeListener(listener, "stale connection");
9743
9783
  } else {
9744
- console.error(`[HYPHA SESSION ${sessionId}] Async listener error:`, err);
9745
- removeListener(listener, "async error");
9784
+ const { strikes, drop } = decideListenerErrorAction(listenerErrorStrikes.get(listener) || 0);
9785
+ if (drop) {
9786
+ listenerErrorStrikes.delete(listener);
9787
+ console.error(`[HYPHA SESSION ${sessionId}] Listener error (strike ${strikes}/${LISTENER_ERROR_STRIKE_LIMIT}, dropping):`, err);
9788
+ removeListener(listener, "async error");
9789
+ } else {
9790
+ listenerErrorStrikes.set(listener, strikes);
9791
+ console.warn(`[HYPHA SESSION ${sessionId}] Listener error (strike ${strikes}/${LISTENER_ERROR_STRIKE_LIMIT}, keeping listener):`, String(err?.message || err || ""));
9792
+ }
9746
9793
  }
9747
9794
  };
9748
9795
  const scheduleBackoffRetry = (listener) => {
@@ -9790,11 +9837,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9790
9837
  if (result && typeof result.catch === "function") {
9791
9838
  result.then(() => {
9792
9839
  rateLimitBackoff.delete(listener);
9840
+ listenerErrorStrikes.delete(listener);
9793
9841
  }).catch((err) => handleListenerRejection(listener, err));
9842
+ } else {
9843
+ listenerErrorStrikes.delete(listener);
9794
9844
  }
9795
9845
  } catch (err) {
9796
- console.error(`[HYPHA SESSION ${sessionId}] Listener error:`, err);
9797
- removeListener(listener, "sync error");
9846
+ handleListenerRejection(listener, err);
9798
9847
  }
9799
9848
  }
9800
9849
  };
@@ -15871,6 +15920,30 @@ function classifyRestoreContinuation(opts) {
15871
15920
  if (opts.wasProcessing && opts.hasResumeId) return "auto-continue";
15872
15921
  return "none";
15873
15922
  }
15923
+ const PERMANENT_RESTORE_PATTERNS = [
15924
+ /failed to create directory/i,
15925
+ /no such file or directory/i,
15926
+ /\benoent\b/i,
15927
+ // spawn ENOENT = launcher/path missing
15928
+ /no agent launchers?/i,
15929
+ /cli (?:is )?(?:not )?installed|command not found/i,
15930
+ /not authorized|permission denied|\beacces\b/i,
15931
+ /\binvalid\b|malformed|unsupported/i
15932
+ ];
15933
+ function classifyRestoreFailure(errorMessage) {
15934
+ const m = errorMessage || "";
15935
+ for (const re of PERMANENT_RESTORE_PATTERNS) if (re.test(m)) return "permanent";
15936
+ return "transient";
15937
+ }
15938
+ const RESTORE_MAX_RETRIES = 3;
15939
+ const RESTORE_BACKOFF_MS = [500, 1500, 4e3];
15940
+ function restoreBackoffMs(attempt) {
15941
+ const i = attempt < 0 ? 0 : Math.min(attempt, RESTORE_BACKOFF_MS.length - 1);
15942
+ return RESTORE_BACKOFF_MS[i];
15943
+ }
15944
+ function shouldRetryRestore(kind, attempt, maxRetries = RESTORE_MAX_RETRIES) {
15945
+ return kind === "transient" && attempt < maxRetries;
15946
+ }
15874
15947
 
15875
15948
  function getShareBaseUrl() {
15876
15949
  return getSvampWebBaseUrl();
@@ -16898,12 +16971,13 @@ function addIssue(projectRoot, fields) {
16898
16971
  function issueLockPath(projectRoot, id) {
16899
16972
  return join$1(issuesDir(projectRoot), `${normalizeIssueRef(id)}.lock`);
16900
16973
  }
16974
+ const ISSUE_LOCK_DEADLINE_MS = 3e3;
16901
16975
  function withIssueLock(projectRoot, id, fn) {
16902
16976
  try {
16903
16977
  mkdirSync$1(issuesDir(projectRoot), { recursive: true });
16904
16978
  } catch {
16905
16979
  }
16906
- return withFileLock(issueLockPath(projectRoot, id), fn);
16980
+ return withFileLock(issueLockPath(projectRoot, id), fn, { deadlineMs: ISSUE_LOCK_DEADLINE_MS });
16907
16981
  }
16908
16982
  function _updateIssueUnlocked(projectRoot, id, patch) {
16909
16983
  const cur = getIssue(projectRoot, id);
@@ -18177,6 +18251,18 @@ function nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures) {
18177
18251
  function shouldRunServiceProbe(args) {
18178
18252
  return !args.inGrace && !args.zombieProbeRan;
18179
18253
  }
18254
+ const MAX_DISCONNECT_RESPAWNS = 3;
18255
+ const DISCONNECT_RESPAWN_WINDOW_MS = 30 * 6e4;
18256
+ function pruneRespawnTimestamps(timestamps, now, windowMs = DISCONNECT_RESPAWN_WINDOW_MS) {
18257
+ return (timestamps || []).filter((t) => Number.isFinite(t) && now - t < windowMs);
18258
+ }
18259
+ function decideDisconnectRecovery(args) {
18260
+ if (args.shutdownRequested) return "stay";
18261
+ if (!args.supervised) return "stay";
18262
+ const cap = args.maxRespawns ?? MAX_DISCONNECT_RESPAWNS;
18263
+ if ((args.respawnsInWindow ?? 0) >= cap) return "stay";
18264
+ return "exit-for-respawn";
18265
+ }
18180
18266
  function shouldForceReconnect(consecutiveHeartbeatFailures) {
18181
18267
  if (consecutiveHeartbeatFailures < 2) return false;
18182
18268
  return consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0;
@@ -19971,6 +20057,25 @@ function writeDaemonStateFile(state) {
19971
20057
  writeFileSync(tmpPath, JSON.stringify(state, null, 2), "utf-8");
19972
20058
  renameSync$1(tmpPath, DAEMON_STATE_FILE);
19973
20059
  }
20060
+ const DISCONNECT_RESPAWN_FILE = join(SVAMP_HOME, "disconnect-respawns.json");
20061
+ function readDisconnectRespawns() {
20062
+ try {
20063
+ if (!existsSync$1(DISCONNECT_RESPAWN_FILE)) return [];
20064
+ const parsed = JSON.parse(readFileSync$1(DISCONNECT_RESPAWN_FILE, "utf-8"));
20065
+ return Array.isArray(parsed) ? parsed.filter((n) => typeof n === "number") : [];
20066
+ } catch {
20067
+ return [];
20068
+ }
20069
+ }
20070
+ function writeDisconnectRespawns(timestamps) {
20071
+ try {
20072
+ ensureHomeDir();
20073
+ const tmpPath = DISCONNECT_RESPAWN_FILE + ".tmp";
20074
+ writeFileSync(tmpPath, JSON.stringify(timestamps), "utf-8");
20075
+ renameSync$1(tmpPath, DISCONNECT_RESPAWN_FILE);
20076
+ } catch {
20077
+ }
20078
+ }
19974
20079
  function readDaemonStateFile() {
19975
20080
  try {
19976
20081
  if (!existsSync$1(DAEMON_STATE_FILE)) return null;
@@ -20224,7 +20329,7 @@ async function startDaemon(options) {
20224
20329
  try {
20225
20330
  const dir = loadSessionIndex()[sessionId]?.directory;
20226
20331
  if (!dir) return;
20227
- const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20332
+ const { reconcileServiceLinks } = await import('./agentCommands-B4sCl_5R.mjs');
20228
20333
  const configPath = getSvampConfigPath(dir, sessionId);
20229
20334
  const config = readSvampConfig(configPath);
20230
20335
  const entries = Array.from(urls.entries());
@@ -20246,7 +20351,7 @@ async function startDaemon(options) {
20246
20351
  try {
20247
20352
  const dir = loadSessionIndex()[sessionId]?.directory;
20248
20353
  if (!dir) return;
20249
- const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20354
+ const { reconcileServiceLinks } = await import('./agentCommands-B4sCl_5R.mjs');
20250
20355
  const configPath = getSvampConfigPath(dir, sessionId);
20251
20356
  const config = readSvampConfig(configPath);
20252
20357
  const incoming = [{
@@ -20267,7 +20372,7 @@ async function startDaemon(options) {
20267
20372
  try {
20268
20373
  const dir = loadSessionIndex()[sessionId]?.directory;
20269
20374
  if (!dir) return;
20270
- const { dropServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20375
+ const { dropServiceLinks } = await import('./agentCommands-B4sCl_5R.mjs');
20271
20376
  const configPath = getSvampConfigPath(dir, sessionId);
20272
20377
  const config = readSvampConfig(configPath);
20273
20378
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -20341,7 +20446,18 @@ async function startDaemon(options) {
20341
20446
  supervised: process.env.SVAMP_SUPERVISED === "1"
20342
20447
  });
20343
20448
  server.on("disconnected", (reason) => {
20344
- logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
20449
+ const supervised2 = process.env.SVAMP_SUPERVISED === "1";
20450
+ const nowMs = Date.now();
20451
+ const respawnHistory = pruneRespawnTimestamps(readDisconnectRespawns(), nowMs);
20452
+ const recovery = decideDisconnectRecovery({ supervised: supervised2, shutdownRequested, respawnsInWindow: respawnHistory.length });
20453
+ if (recovery === "exit-for-respawn") {
20454
+ writeDisconnectRespawns([...respawnHistory, nowMs]);
20455
+ logger.log(`Hypha connection permanently lost: ${reason}. Exiting for a clean supervised respawn (sessions restore via --resume).`);
20456
+ setTimeout(() => process.exit(1), 250);
20457
+ } else {
20458
+ const capped = supervised2 && !shutdownRequested && respawnHistory.length >= MAX_DISCONNECT_RESPAWNS;
20459
+ logger.log(capped ? `Hypha connection permanently lost: ${reason}. Already respawned ${respawnHistory.length}x in the last 30 min \u2014 the far end looks down, so STAYING UP (local sessions keep running). Will retry after the window clears.` : `Hypha connection permanently lost: ${reason}. Daemon continues running (unsupervised) \u2014 restart manually to reconnect.`);
20460
+ }
20345
20461
  });
20346
20462
  const pidToTrackedSession = /* @__PURE__ */ new Map();
20347
20463
  let sessionCoreRef = {
@@ -22643,11 +22759,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22643
22759
  });
22644
22760
  },
22645
22761
  onIssue: async (params) => {
22646
- const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
22762
+ const { issueRpc } = await import('./rpc-q5KTMSRl.mjs');
22647
22763
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22648
22764
  },
22649
22765
  onWorkflow: async (params) => {
22650
- const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
22766
+ const { workflowRpc } = await import('./rpc-fZm83jEI.mjs');
22651
22767
  return workflowRpc(params?.cwd || directory, params || {});
22652
22768
  },
22653
22769
  onRipgrep: async (args, cwd) => {
@@ -23353,11 +23469,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23353
23469
  });
23354
23470
  },
23355
23471
  onIssue: async (params) => {
23356
- const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
23472
+ const { issueRpc } = await import('./rpc-q5KTMSRl.mjs');
23357
23473
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23358
23474
  },
23359
23475
  onWorkflow: async (params) => {
23360
- const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
23476
+ const { workflowRpc } = await import('./rpc-fZm83jEI.mjs');
23361
23477
  return workflowRpc(params?.cwd || directory, params || {});
23362
23478
  },
23363
23479
  onRipgrep: async (args, cwd) => {
@@ -24443,7 +24559,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24443
24559
  }
24444
24560
  logger.log(`Restoring ${persistedSessions.length} persisted session(s)...`);
24445
24561
  const restoreConcurrency = Math.max(1, parseInt(process.env.SVAMP_RESTORE_CONCURRENCY || "8", 10) || 8);
24446
- const restoreOne = async (persisted) => {
24562
+ const restoreOne = async (persisted, attempt = 0) => {
24447
24563
  try {
24448
24564
  const isOrphaned = persisted.machineId && persisted.machineId !== machineId;
24449
24565
  if (isOrphaned) {
@@ -24500,10 +24616,26 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24500
24616
  sessionsToLoopResume.push({ sessionId: persisted.sessionId, directory: persisted.directory });
24501
24617
  }
24502
24618
  } else {
24503
- logger.log(`Failed to restore session ${persisted.sessionId}: ${result.type}`);
24619
+ const reason = result.errorMessage || result.type;
24620
+ const kind = classifyRestoreFailure(reason);
24621
+ if (shouldRetryRestore(kind, attempt)) {
24622
+ const wait = restoreBackoffMs(attempt);
24623
+ logger.log(`Restore of ${persisted.sessionId} failed (${reason}) \u2014 transient, retrying in ${wait}ms (attempt ${attempt + 1}/${RESTORE_MAX_RETRIES})`);
24624
+ await new Promise((r) => setTimeout(r, wait));
24625
+ return restoreOne(persisted, attempt + 1);
24626
+ }
24627
+ logger.log(`Failed to restore session ${persisted.sessionId}: ${reason} (${kind}; preserved on disk for a later restart)`);
24504
24628
  }
24505
24629
  } catch (err) {
24506
- logger.error(`Error restoring session ${persisted.sessionId}:`, err.message);
24630
+ const reason = err?.message || String(err);
24631
+ const kind = classifyRestoreFailure(reason);
24632
+ if (shouldRetryRestore(kind, attempt)) {
24633
+ const wait = restoreBackoffMs(attempt);
24634
+ logger.log(`Error restoring ${persisted.sessionId}: ${reason} \u2014 transient, retrying in ${wait}ms (attempt ${attempt + 1}/${RESTORE_MAX_RETRIES})`);
24635
+ await new Promise((r) => setTimeout(r, wait));
24636
+ return restoreOne(persisted, attempt + 1);
24637
+ }
24638
+ logger.error(`Error restoring session ${persisted.sessionId} (${kind}, giving up this lifetime):`, reason);
24507
24639
  }
24508
24640
  };
24509
24641
  let restoreCursor = 0;
@@ -24694,7 +24826,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24694
24826
  const PING_TIMEOUT_MS = 15e3;
24695
24827
  const POST_RECONNECT_GRACE_MS = 2e4;
24696
24828
  const RECONNECT_JITTER_MS = 2500;
24697
- const { WorkflowScheduler } = await import('./scheduler-3LUL1jFD.mjs');
24829
+ const { WorkflowScheduler } = await import('./scheduler-DTRcVW02.mjs');
24698
24830
  const workflowProjectRoots = () => {
24699
24831
  const dirs = /* @__PURE__ */ new Set();
24700
24832
  for (const s of pidToTrackedSession.values()) {
@@ -25329,4 +25461,4 @@ var run = /*#__PURE__*/Object.freeze({
25329
25461
  writeStopMarker: writeStopMarker
25330
25462
  });
25331
25463
 
25332
- export { listSkillFiles as $, saveWorkflow as A, rawWorkflow as B, listWorkflows as C, isWorkflowEnabled as D, workflowSchedules as E, inZone as F, cronMatches as G, summarize as H, workflowSteps as I, parseJwtEmail as J, computeCollectionConfigUpdate as K, SYSTEM_COLLECTION_CONFIG as L, loadMachineContext as M, buildMachineInstructions as N, machineToolsForRole as O, buildMachineTools as P, parseFrontmatter as Q, READ_ONLY_TOOLS as R, SharingNotificationSync as S, getSkillsServer as T, getSkillsWorkspaceName as U, getSkillsCollectionName as V, fetchWithTimeout as W, searchSkills as X, SKILLS_DIR as Y, getSkillInfo as Z, downloadSkillFile as _, createSessionStore as a, resolveModel as a0, formatHandle as a1, normalizeAllowedUser as a2, loadSecurityContextConfig as a3, resolveSecurityContext as a4, buildSecurityContextFromFlags as a5, mergeSecurityContexts as a6, buildSessionShareUrl as a7, computeOutboundHop as a8, registerAwaitingReply as a9, kimiSetup as aA, api as aB, supervisorLock as aC, run as aD, buildMachineShareUrl as aa, parseHandle as ab, handleMatchesMetadata as ac, PINNED_CODEX_VERSION as ad, clearStopMarker as ae, stopMarkerExists as af, withFileLock as ag, describeMisconfiguration as ah, buildMachineDeps as ai, applyClaudeProxyEnv as aj, composeSessionId as ak, generateFriendlyName as al, generateHookSettings as am, instanceConfig as an, frpc as ao, staticFileServer as ap, claudeAuth as aq, codexProvider as ar, projectInfo as as, DefaultTransport$1 as at, acpBackend as au, acpAgentConfig as av, codexAppServerBackend as aw, GeminiTransport$1 as ax, KimiTransport$1 as ay, kimiProvider 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, validateWorkflowName as z };
25464
+ 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 };
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { aj as applyClaudeProxyEnv, ak as composeSessionId, al as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, am as generateHookSettings } from './run-DdTcDN3e.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { ak as applyClaudeProxyEnv, al as composeSessionId, am as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, an as generateHookSettings } from './run-1CHjd0_g.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
@@ -1,4 +1,4 @@
1
- import { f as resolveProjectRoot, C as listWorkflows, D as isWorkflowEnabled, E as workflowSchedules, F as inZone, w as runWorkflow, G as cronMatches } from './run-DdTcDN3e.mjs';
1
+ import { f as resolveProjectRoot, D as listWorkflows, E as isWorkflowEnabled, F as workflowSchedules, G as inZone, w as runWorkflow, H as cronMatches } from './run-1CHjd0_g.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
- import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
3
- import './run-DdTcDN3e.mjs';
2
+ import { w as wantsHelp } from './cli-BY4DQnRs.mjs';
3
+ import './run-1CHjd0_g.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -77,7 +77,7 @@ async function handleServeCommand() {
77
77
  }
78
78
  }
79
79
  async function serveAdd(args, machineId) {
80
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
80
+ const { connectAndGetMachine } = await import('./commands-rKfH5E8g.mjs');
81
81
  const pos = positionalArgs(args);
82
82
  const name = pos[0];
83
83
  if (!name) {
@@ -110,7 +110,7 @@ async function serveAdd(args, machineId) {
110
110
  }
111
111
  if (sessionId && result?.url) {
112
112
  try {
113
- const { autoAddSessionLink } = await import('./agentCommands-BiSVzb5D.mjs');
113
+ const { autoAddSessionLink } = await import('./agentCommands-B4sCl_5R.mjs');
114
114
  autoAddSessionLink(String(result.url), name, void 0, { kind: "serve", name });
115
115
  } catch {
116
116
  }
@@ -124,7 +124,7 @@ async function serveAdd(args, machineId) {
124
124
  }
125
125
  }
126
126
  async function serveApply(args, machineId) {
127
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
127
+ const { connectAndGetMachine } = await import('./commands-rKfH5E8g.mjs');
128
128
  const fs = await import('fs');
129
129
  const yaml = await import('yaml');
130
130
  const file = positionalArgs(args)[0];
@@ -193,7 +193,10 @@ async function serveApply(args, machineId) {
193
193
  sessionId: parsed.sessionId ?? parsed.session_id ?? process.env.SVAMP_SESSION_ID,
194
194
  access: parsed.access ?? "link",
195
195
  // default flipped from 'owner' to 'link' (capability URL)
196
- ownerEmail: parsed.ownerEmail ?? parsed.owner_email
196
+ ownerEmail: parsed.ownerEmail ?? parsed.owner_email,
197
+ // #1072: re-applying keeps the SAME URL by default (stable capability token). Pass
198
+ // --regenerate-url (or regenerate_url: true in the YAML) to mint a fresh URL on purpose.
199
+ regenerateUrl: hasFlag(args, "--regenerate-url", "--regenerate") || parsed.regenerateUrl === true || parsed.regenerate_url === true
197
200
  };
198
201
  const { machine, server } = await connectAndGetMachine(machineId);
199
202
  try {
@@ -206,7 +209,7 @@ async function serveApply(args, machineId) {
206
209
  console.log(`URL: ${result.url}`);
207
210
  if (params.sessionId && result?.url) {
208
211
  try {
209
- const { autoAddSessionLink } = await import('./agentCommands-BiSVzb5D.mjs');
212
+ const { autoAddSessionLink } = await import('./agentCommands-B4sCl_5R.mjs');
210
213
  const prevSession = process.env.SVAMP_SESSION_ID;
211
214
  process.env.SVAMP_SESSION_ID = String(params.sessionId);
212
215
  try {
@@ -227,7 +230,7 @@ async function serveApply(args, machineId) {
227
230
  }
228
231
  }
229
232
  async function serveRemove(args, machineId) {
230
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
233
+ const { connectAndGetMachine } = await import('./commands-rKfH5E8g.mjs');
231
234
  const pos = positionalArgs(args);
232
235
  const name = pos[0];
233
236
  if (!name) {
@@ -237,7 +240,7 @@ async function serveRemove(args, machineId) {
237
240
  const { machine, server } = await connectAndGetMachine(machineId);
238
241
  try {
239
242
  await machine.serveRemove({ name });
240
- const { removeSessionLinkByService } = await import('./agentCommands-BiSVzb5D.mjs');
243
+ const { removeSessionLinkByService } = await import('./agentCommands-B4sCl_5R.mjs');
241
244
  removeSessionLinkByService("serve", name);
242
245
  console.log(`Mount '${name}' removed.`);
243
246
  } catch (err) {
@@ -249,7 +252,7 @@ async function serveRemove(args, machineId) {
249
252
  }
250
253
  }
251
254
  async function serveList(args, machineId) {
252
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
255
+ const { connectAndGetMachine } = await import('./commands-rKfH5E8g.mjs');
253
256
  const all = hasFlag(args, "--all", "-a");
254
257
  const json = hasFlag(args, "--json");
255
258
  const sessionId = getFlag(args, "--session");
@@ -283,7 +286,7 @@ async function serveList(args, machineId) {
283
286
  }
284
287
  }
285
288
  async function serveInfo(machineId) {
286
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
289
+ const { connectAndGetMachine } = await import('./commands-rKfH5E8g.mjs');
287
290
  const { machine, server } = await connectAndGetMachine(machineId);
288
291
  try {
289
292
  const info = await machine.serveInfo();
@@ -332,6 +335,11 @@ Access tiers (default: link):
332
335
  --owner Hypha login required, owner-only.
333
336
  --access email1,email2 Hypha login required, specific allowed emails.
334
337
 
338
+ Stable URLs (re-apply):
339
+ Re-applying a mount (same name) keeps the SAME URL \u2014 the capability token is
340
+ reused, so repeated 'serve apply' / 'serve <name>' no longer leaks stale URLs.
341
+ --regenerate-url Mint a fresh URL on this apply (the old one stops resolving).
342
+
335
343
  Options:
336
344
  -m, --machine <id> Target a specific machine
337
345
  --session <id> Filter by session ID
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, M as loadMachineContext, N as buildMachineInstructions, O as machineToolsForRole, P as buildMachineTools } from './run-DdTcDN3e.mjs';
1
+ import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-1CHjd0_g.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.327",
4
- "description": "Svamp CLI AI workspace daemon on Hypha Cloud",
3
+ "version": "0.2.329",
4
+ "description": "Svamp CLI \u2014 AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "type": "module",
@@ -20,13 +20,13 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "node test/with-timeout.mjs 420 npx tsx test/test-context-window.mjs && node test/with-timeout.mjs 420 npx tsx test/test-ratelimit-retry.mjs && node test/with-timeout.mjs 420 npx tsx test/test-completed-requests.mjs && node test/with-timeout.mjs 420 npx tsx test/test-reconnect-health.mjs &&node test/with-timeout.mjs 420 npx tsx test/test-instance-config.mjs && node test/with-timeout.mjs 420 npx tsx test/test-authorize.mjs && node test/with-timeout.mjs 420 npx tsx test/test-normalize-allowed-user.mjs && node test/with-timeout.mjs 420 npx tsx test/test-share-url.mjs && node test/with-timeout.mjs 420 npx tsx test/test-update-sharing-normalization.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sharing-notify-sync.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sharing-notify-resolve.mjs && node test/with-timeout.mjs 420 npx tsx test/test-staged-homes-sweep.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-cli-routing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-security-context.mjs && node test/with-timeout.mjs 420 npx tsx test/test-isolation-decision.mjs && node test/with-timeout.mjs 420 npx tsx test/test-message-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-stdin-delivery.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-config.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wrap-command.mjs && node test/with-timeout.mjs 420 npx tsx test/test-credential-staging.mjs && node test/with-timeout.mjs 420 npx tsx test/test-claude-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-backend-accounts.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-force-model.mjs && node test/with-timeout.mjs 420 npx tsx test/test-backend-oauth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-btw-proxy-env.mjs && node test/with-timeout.mjs 420 npx tsx test/test-output-formatters.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-user-events.mjs && node test/with-timeout.mjs 420 npx tsx test/test-migrate-children.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-install.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-setup.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-pairing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-coordinator.mjs && node test/with-timeout.mjs 420 npx tsx test/e2e-outpost-transport.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-reconcile.mjs && node test/with-timeout.mjs 420 npx tsx test/test-auto-topic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-project-info.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-types.mjs && node test/with-timeout.mjs 420 npx tsx test/test-transport.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-update-handlers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-scanner.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hypha-client.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hook-settings.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-service-logic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-daemon-persistence.mjs && node test/with-timeout.mjs 420 npx tsx test/test-detect-isolation.mjs && node test/with-timeout.mjs 420 npx tsx test/test-isolation-disable.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-service-logic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-session-visibility.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-access-fallback.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-rpc-role-map.mjs && node test/with-timeout.mjs 420 npx tsx test/test-interactive-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-pinned-codex.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-request-timeout.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-app-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-kimi-provider.mjs && node test/with-timeout.mjs 420 npx tsx test/test-acp-backend.mjs && node test/with-timeout.mjs 420 npx tsx test/test-acp-bridge.mjs && node test/with-timeout.mjs 420 npx tsx test/test-listener-backoff.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-identity-env.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hook-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-commands.mjs && node test/with-timeout.mjs 420 npx tsx test/test-interactive-console.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-messages.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-send-query.mjs && node test/with-timeout.mjs 420 npx tsx test/test-skills.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-grouping.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-list-directory.mjs && node test/with-timeout.mjs 420 npx tsx test/test-service-commands.mjs && node test/with-timeout.mjs 420 npx tsx test/test-supervisor.mjs && node test/with-timeout.mjs 420 npx tsx test/test-supervisor-lock.mjs && node test/with-timeout.mjs 420 node test/test-supervisor-restart.mjs && node test/with-timeout.mjs 420 npx tsx test/test-clear-detection.mjs && node test/with-timeout.mjs 420 npx tsx test/test-compact-detect.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-consolidation.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-cross-machine.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-store-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-close-gate.mjs && node test/with-timeout.mjs 420 npx tsx test/test-loop-verify.mjs && node test/with-timeout.mjs 420 npx tsx test/test-restore-decision.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-pause.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-scheduler.mjs && node test/with-timeout.mjs 420 npx tsx test/test-cron.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-runs.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-runs-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-idle.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-reconcile.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-link-subdomain.mjs && node test/with-timeout.mjs 420 npx tsx test/test-short-id.mjs && node test/with-timeout.mjs 420 npx tsx test/test-transcript-edit.mjs && node test/with-timeout.mjs 420 npx tsx test/test-edit-history.mjs && node test/with-timeout.mjs 420 npx tsx test/test-friendly-name.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-rpc-dispatch.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sandbox-cli.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-manager.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-files-gateway.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-ws-upgrade.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-static-file-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-stability.mjs && node test/with-timeout.mjs 420 npx tsx test/test-frpc-e2e.mjs --unit-only && node test/with-timeout.mjs 420 npx tsx test/test-frpc-status.mjs && node test/with-timeout.mjs 420 npx tsx test/test-frpc-orphan-reap.mjs && node test/with-timeout.mjs 420 node test/pinnedClaudeCode.test.mjs && node test/with-timeout.mjs 420 node test/fleet.test.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-file.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-agent.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-agent.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channels-service.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hotreload-seam.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hot-reload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hot-reload-live.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-core.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-async-reply.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outbox-reload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-binding.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-store-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-identity.mjs && node test/with-timeout.mjs 420 npx tsx test/test-stateless-dispatch-limiter.mjs && node test/with-timeout.mjs 420 npx tsx test/test-shared-session-identity.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-agent-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-http.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-upload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-file-access.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-voice.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-headless.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-machine.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-merge.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-verdict-routing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-standalone.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-links.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-loop-pause-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-artifact-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-artifact-sync-pagination.mjs && node test/with-timeout.mjs 420 npx tsx test/test-graceful-restart.mjs && node test/with-timeout.mjs 420 npx tsx test/test-flush-exit.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-skills.mjs && node test/with-timeout.mjs 420 npx tsx test/test-nightly-0807-fixes.mjs && node test/with-timeout.mjs 420 npx tsx test/test-nightly-0814-fixes.mjs && node test/with-timeout.mjs 420 npx tsx test/test-log-prune.mjs && node test/with-timeout.mjs 420 node test/test-message-count-cache.mjs && node test/with-timeout.mjs 420 node test/test-serve-link-gate.mjs && node test/with-timeout.mjs 420 node test/test-security-context-unenforceable.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-id-resolution.mjs && node test/with-timeout.mjs 420 npx tsx test/test-help-flag-never-acts.mjs && node test/with-timeout.mjs 420 npx tsx test/test-with-timeout.mjs && node test/with-timeout.mjs 420 npx tsx test/test-message-queue-cap.mjs",
23
+ "test": "node test/with-timeout.mjs 420 npx tsx test/test-context-window.mjs && node test/with-timeout.mjs 420 npx tsx test/test-ratelimit-retry.mjs && node test/with-timeout.mjs 420 npx tsx test/test-completed-requests.mjs && node test/with-timeout.mjs 420 npx tsx test/test-reconnect-health.mjs &&node test/with-timeout.mjs 420 npx tsx test/test-fleet-version-parse.mjs &&node test/with-timeout.mjs 420 npx tsx test/test-instance-config.mjs && node test/with-timeout.mjs 420 npx tsx test/test-authorize.mjs && node test/with-timeout.mjs 420 npx tsx test/test-normalize-allowed-user.mjs && node test/with-timeout.mjs 420 npx tsx test/test-share-url.mjs && node test/with-timeout.mjs 420 npx tsx test/test-update-sharing-normalization.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sharing-notify-sync.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sharing-notify-resolve.mjs && node test/with-timeout.mjs 420 npx tsx test/test-staged-homes-sweep.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-cli-routing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-security-context.mjs && node test/with-timeout.mjs 420 npx tsx test/test-isolation-decision.mjs && node test/with-timeout.mjs 420 npx tsx test/test-message-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-stdin-delivery.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-config.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wrap-command.mjs && node test/with-timeout.mjs 420 npx tsx test/test-credential-staging.mjs && node test/with-timeout.mjs 420 npx tsx test/test-claude-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-backend-accounts.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-force-model.mjs && node test/with-timeout.mjs 420 npx tsx test/test-backend-oauth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-btw-proxy-env.mjs && node test/with-timeout.mjs 420 npx tsx test/test-output-formatters.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-user-events.mjs && node test/with-timeout.mjs 420 npx tsx test/test-migrate-children.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-install.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-setup.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-pairing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outpost-coordinator.mjs && node test/with-timeout.mjs 420 npx tsx test/e2e-outpost-transport.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-reconcile.mjs && node test/with-timeout.mjs 420 npx tsx test/test-auto-topic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-project-info.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-types.mjs && node test/with-timeout.mjs 420 npx tsx test/test-transport.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-update-handlers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-scanner.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hypha-client.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hook-settings.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-service-logic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-daemon-persistence.mjs && node test/with-timeout.mjs 420 npx tsx test/test-detect-isolation.mjs && node test/with-timeout.mjs 420 npx tsx test/test-isolation-disable.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-service-logic.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-session-visibility.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-access-fallback.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-rpc-role-map.mjs && node test/with-timeout.mjs 420 npx tsx test/test-interactive-helpers.mjs && node test/with-timeout.mjs 420 npx tsx test/test-pinned-codex.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-request-timeout.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-app-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-kimi-provider.mjs && node test/with-timeout.mjs 420 npx tsx test/test-acp-backend.mjs && node test/with-timeout.mjs 420 npx tsx test/test-acp-bridge.mjs && node test/with-timeout.mjs 420 npx tsx test/test-listener-backoff.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-identity-env.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hook-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-commands.mjs && node test/with-timeout.mjs 420 npx tsx test/test-interactive-console.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-messages.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-send-query.mjs && node test/with-timeout.mjs 420 npx tsx test/test-skills.mjs && node test/with-timeout.mjs 420 npx tsx test/test-agent-grouping.mjs && node test/with-timeout.mjs 420 npx tsx test/test-machine-list-directory.mjs && node test/with-timeout.mjs 420 npx tsx test/test-service-commands.mjs && node test/with-timeout.mjs 420 npx tsx test/test-supervisor.mjs && node test/with-timeout.mjs 420 npx tsx test/test-supervisor-lock.mjs && node test/with-timeout.mjs 420 node test/test-supervisor-restart.mjs && node test/with-timeout.mjs 420 npx tsx test/test-clear-detection.mjs && node test/with-timeout.mjs 420 npx tsx test/test-compact-detect.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-consolidation.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox.mjs && node test/with-timeout.mjs 420 npx tsx test/test-inbox-cross-machine.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-store-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-close-gate.mjs && node test/with-timeout.mjs 420 npx tsx test/test-loop-verify.mjs && node test/with-timeout.mjs 420 npx tsx test/test-restore-decision.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-pause.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-store.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-scheduler.mjs && node test/with-timeout.mjs 420 npx tsx test/test-cron.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-runs.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-runs-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-idle.mjs && node test/with-timeout.mjs 420 npx tsx test/test-workflow-reconcile.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-link-subdomain.mjs && node test/with-timeout.mjs 420 npx tsx test/test-short-id.mjs && node test/with-timeout.mjs 420 npx tsx test/test-transcript-edit.mjs && node test/with-timeout.mjs 420 npx tsx test/test-edit-history.mjs && node test/with-timeout.mjs 420 npx tsx test/test-friendly-name.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-rpc-dispatch.mjs && node test/with-timeout.mjs 420 npx tsx test/test-sandbox-cli.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-manager.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-files-gateway.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-ws-upgrade.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-static-file-server.mjs && node test/with-timeout.mjs 420 npx tsx test/test-serve-stability.mjs && node test/with-timeout.mjs 420 npx tsx test/test-frpc-e2e.mjs --unit-only && node test/with-timeout.mjs 420 npx tsx test/test-frpc-status.mjs && node test/with-timeout.mjs 420 npx tsx test/test-frpc-orphan-reap.mjs && node test/with-timeout.mjs 420 node test/pinnedClaudeCode.test.mjs && node test/with-timeout.mjs 420 node test/fleet.test.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-file.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-rpc.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-agent.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-agent.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channels-service.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hotreload-seam.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hot-reload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-hot-reload-live.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-core.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-async-reply.mjs && node test/with-timeout.mjs 420 npx tsx test/test-outbox-reload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-binding.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-store-lock.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-identity.mjs && node test/with-timeout.mjs 420 npx tsx test/test-stateless-dispatch-limiter.mjs && node test/with-timeout.mjs 420 npx tsx test/test-shared-session-identity.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-agent-auth.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-http.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-upload.mjs && node test/with-timeout.mjs 420 npx tsx test/test-channel-file-access.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-voice.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-headless.mjs && node test/with-timeout.mjs 420 npx tsx test/test-wise-machine.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-merge.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-verdict-routing.mjs && node test/with-timeout.mjs 420 npx tsx test/test-crew-standalone.mjs && node test/with-timeout.mjs 420 npx tsx test/test-session-links.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-loop-pause-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-artifact-guard.mjs && node test/with-timeout.mjs 420 npx tsx test/test-artifact-sync-pagination.mjs && node test/with-timeout.mjs 420 npx tsx test/test-graceful-restart.mjs && node test/with-timeout.mjs 420 npx tsx test/test-flush-exit.mjs && node test/with-timeout.mjs 420 npx tsx test/test-codex-skills.mjs && node test/with-timeout.mjs 420 npx tsx test/test-nightly-0807-fixes.mjs && node test/with-timeout.mjs 420 npx tsx test/test-nightly-0814-fixes.mjs && node test/with-timeout.mjs 420 npx tsx test/test-log-prune.mjs && node test/with-timeout.mjs 420 node test/test-message-count-cache.mjs && node test/with-timeout.mjs 420 node test/test-serve-link-gate.mjs && node test/with-timeout.mjs 420 node test/test-security-context-unenforceable.mjs && node test/with-timeout.mjs 420 npx tsx test/test-issue-id-resolution.mjs && node test/with-timeout.mjs 420 npx tsx test/test-help-flag-never-acts.mjs && node test/with-timeout.mjs 420 npx tsx test/test-with-timeout.mjs && node test/with-timeout.mjs 420 npx tsx test/test-message-queue-cap.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
27
27
  "test:e2e": "node --no-warnings test/e2e-session-tests.mjs && npx tsx test/e2e-codex-midturn-steer.mjs && npx tsx test/e2e-serve-iframe-login.mjs && npx tsx test/test-archive-preserves-claude-file.mjs && npx tsx test/test-daemon-e2e.mjs && npx tsx test/test-hypha-service.mjs && npx tsx test/test-permission-e2e.mjs && npx tsx test/test-serve-e2e.mjs && npx tsx test/test-session-archive-resume.mjs && npx tsx test/test-session-rpc-e2e.mjs && npx tsx test/test-skills-integration.mjs && npx tsx test/test-wm-refresh.mjs && npx tsx test/e2e-session-files-gateway.mjs && npx tsx test/e2e-dns-production-hosts.mjs",
28
28
  "test:frpc": "npx tsx test/test-frpc-e2e.mjs",
29
- "prepublishOnly": "node ../../scripts/bump-cli-version.mjs --check && yarn build"
29
+ "prepublishOnly": "node ../../scripts/bump-cli-version.mjs --check && yarn build && yarn test"
30
30
  },
31
31
  "dependencies": {
32
32
  "@agentclientprotocol/sdk": "^0.14.1",