svamp-cli 0.2.327 → 0.2.328

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 (26) hide show
  1. package/dist/{adminCommands-BVXEj5XN.mjs → adminCommands-DotiO7jx.mjs} +1 -1
  2. package/dist/{agentCommands-BiSVzb5D.mjs → agentCommands-DefL1pIo.mjs} +5 -5
  3. package/dist/{auth-D0ji0kh3.mjs → auth-DLK3tc_f.mjs} +1 -1
  4. package/dist/{cli-CQlIsRxF.mjs → cli-BCN1uAze.mjs} +77 -77
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-C4z-QQdO.mjs → commands-BD6Qfv3u.mjs} +11 -11
  7. package/dist/{commands-D61SEgo0.mjs → commands-BEnXz1U_.mjs} +2 -2
  8. package/dist/{commands-CXJmXwQo.mjs → commands-BFlfbiey.mjs} +1 -1
  9. package/dist/{commands-DexO3t-N.mjs → commands-DEZLlXJ3.mjs} +1 -1
  10. package/dist/{commands-D5R0AmXt.mjs → commands-DH2WCdPH.mjs} +3 -3
  11. package/dist/{commands-BkzYDwHO.mjs → commands-gO42Ijz3.mjs} +3 -3
  12. package/dist/{commands-CPZdcLvm.mjs → commands-uN_gulw0.mjs} +3 -3
  13. package/dist/{commands-Cegv66fj.mjs → commands-xhA5FWM8.mjs} +2 -2
  14. package/dist/{fleet-CCVC-NiE.mjs → fleet-CbKnESdR.mjs} +1 -1
  15. package/dist/{headlessCli-CFgDFTPd.mjs → headlessCli-2wTVI-Kj.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{notifyCommands-BfLeyHId.mjs → notifyCommands--tpDRdrL.mjs} +1 -1
  18. package/dist/{package-TcR2vlFS.mjs → package-BORyBEey.mjs} +1 -1
  19. package/dist/{rpc-hdw0ZlCj.mjs → rpc-BCLZpI5H.mjs} +1 -1
  20. package/dist/{rpc-DXY3KqiA.mjs → rpc-CZqCc9Pr.mjs} +1 -1
  21. package/dist/{run-CewXMfIf.mjs → run-BAYXQ4-N.mjs} +1 -1
  22. package/dist/{run-DdTcDN3e.mjs → run-CSV6OgQ8.mjs} +94 -24
  23. package/dist/{scheduler-3LUL1jFD.mjs → scheduler-s_j089nA.mjs} +1 -1
  24. package/dist/{serveCommands-DUJ4eojk.mjs → serveCommands-C4779W5n.mjs} +19 -11
  25. package/dist/{sideband-BRHu4M46.mjs → sideband-B7sDP61Z.mjs} +1 -1
  26. package/package.json +1 -1
@@ -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-BCN1uAze.mjs';
3
+ import './run-CSV6OgQ8.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -83,7 +83,7 @@ async function serviceExpose(args) {
83
83
  process.exit(1);
84
84
  }
85
85
  if (foreground) {
86
- const { runFrpcTunnel } = await import('./run-DdTcDN3e.mjs').then(function (n) { return n.ao; });
86
+ const { runFrpcTunnel } = await import('./run-CSV6OgQ8.mjs').then(function (n) { return n.ao; });
87
87
  await runFrpcTunnel(name, ports, void 0, {
88
88
  group,
89
89
  groupKey,
@@ -93,7 +93,7 @@ async function serviceExpose(args) {
93
93
  });
94
94
  return;
95
95
  }
96
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-DEZLlXJ3.mjs');
97
97
  const { server, machine } = await connectAndGetMachine();
98
98
  try {
99
99
  const status = await machine.tunnelStart({
@@ -115,7 +115,7 @@ async function serviceExpose(args) {
115
115
  console.log(` port ${port}: ${url}`);
116
116
  }
117
117
  if (process.env.SVAMP_SESSION_ID) {
118
- const { autoAddSessionLink } = await import('./agentCommands-BiSVzb5D.mjs');
118
+ const { autoAddSessionLink } = await import('./agentCommands-DefL1pIo.mjs');
119
119
  let added = 0;
120
120
  for (const [port, url] of urlEntries) {
121
121
  const label = urlEntries.length > 1 ? `${name}:${port}` : name;
@@ -151,8 +151,8 @@ async function serviceServe(args) {
151
151
  const resolvedDir = path.resolve(directory);
152
152
  console.log(`Serving ${resolvedDir}`);
153
153
  const http = await import('http');
154
- const { serveStaticMount } = await import('./run-DdTcDN3e.mjs').then(function (n) { return n.ap; });
155
- const { getFreePort } = await import('./run-DdTcDN3e.mjs').then(function (n) { return n.ao; });
154
+ const { serveStaticMount } = await import('./run-CSV6OgQ8.mjs').then(function (n) { return n.ap; });
155
+ const { getFreePort } = await import('./run-CSV6OgQ8.mjs').then(function (n) { return n.ao; });
156
156
  const server = http.createServer((req, res) => {
157
157
  const u = new URL(req.url || "/", "http://127.0.0.1");
158
158
  let rel = u.pathname;
@@ -175,7 +175,7 @@ async function serviceServe(args) {
175
175
  resolve(18080);
176
176
  });
177
177
  });
178
- const { runFrpcTunnel } = await import('./run-DdTcDN3e.mjs').then(function (n) { return n.ao; });
178
+ const { runFrpcTunnel } = await import('./run-CSV6OgQ8.mjs').then(function (n) { return n.ao; });
179
179
  void server;
180
180
  await runFrpcTunnel(name, [servePort]);
181
181
  } catch (err) {
@@ -185,7 +185,7 @@ async function serviceServe(args) {
185
185
  }
186
186
  async function serviceList(_args) {
187
187
  try {
188
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
188
+ const { connectAndGetMachine } = await import('./commands-DEZLlXJ3.mjs');
189
189
  const { server, machine } = await connectAndGetMachine();
190
190
  try {
191
191
  const tunnels = await machine.tunnelList({});
@@ -225,11 +225,11 @@ async function serviceDelete(args) {
225
225
  process.exit(1);
226
226
  }
227
227
  try {
228
- const { connectAndGetMachine } = await import('./commands-DexO3t-N.mjs');
228
+ const { connectAndGetMachine } = await import('./commands-DEZLlXJ3.mjs');
229
229
  const { server, machine } = await connectAndGetMachine();
230
230
  try {
231
231
  await machine.tunnelStop({ name });
232
- const { removeSessionLinkByService } = await import('./agentCommands-BiSVzb5D.mjs');
232
+ const { removeSessionLinkByService } = await import('./agentCommands-DefL1pIo.mjs');
233
233
  removeSessionLinkByService("tunnel", name);
234
234
  console.log(`Stopped tunnel '${name}'.`);
235
235
  } catch (err) {
@@ -1,8 +1,8 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { f as flushAndExit } from './cli-CQlIsRxF.mjs';
5
- import { f as resolveProjectRoot, n as searchIssues, l as listIssues, m as isPendingIssue, i as resumeIssue, p as pauseIssue, j as addComment, u as updateIssue, h as getIssue, o as isVisibleTo, H as summarize, k as addIssue } from './run-DdTcDN3e.mjs';
4
+ import { f as flushAndExit } from './cli-BCN1uAze.mjs';
5
+ import { f as resolveProjectRoot, n as searchIssues, l as listIssues, m as isPendingIssue, i as resumeIssue, p as pauseIssue, j as addComment, u as updateIssue, h as getIssue, o as isVisibleTo, H as summarize, k as addIssue } from './run-CSV6OgQ8.mjs';
6
6
  import './serviceManager-hlOVxkhW.mjs';
7
7
  import 'os';
8
8
  import 'fs/promises';
@@ -1,7 +1,7 @@
1
1
  import os from 'os';
2
2
  import fs__default from 'fs';
3
3
  import { resolve, join, relative } from 'path';
4
- import { Q as parseFrontmatter, T as getSkillsServer, U as getSkillsWorkspaceName, V as getSkillsCollectionName, W as fetchWithTimeout, X as searchSkills, Y as SKILLS_DIR, Z as getSkillInfo, _ as downloadSkillFile, $ as listSkillFiles } from './run-DdTcDN3e.mjs';
4
+ import { Q as parseFrontmatter, T as getSkillsServer, U as getSkillsWorkspaceName, V as getSkillsCollectionName, W as fetchWithTimeout, X as searchSkills, Y as SKILLS_DIR, Z as getSkillInfo, _ as downloadSkillFile, $ as listSkillFiles } from './run-CSV6OgQ8.mjs';
5
5
  import 'fs/promises';
6
6
  import 'url';
7
7
  import 'child_process';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { execSync, execFileSync } from 'node:child_process';
3
3
  import { basename, resolve, join, isAbsolute } from 'node:path';
4
4
  import os from 'node:os';
5
- import { a1 as formatHandle, a2 as normalizeAllowedUser, a3 as loadSecurityContextConfig, a4 as resolveSecurityContext, a5 as buildSecurityContextFromFlags, a6 as mergeSecurityContexts, c as connectToHypha, a7 as buildSessionShareUrl, a8 as computeOutboundHop, e as shortId, a9 as registerAwaitingReply, aa as buildMachineShareUrl, ab as parseHandle, ac as handleMatchesMetadata } from './run-DdTcDN3e.mjs';
5
+ import { a1 as formatHandle, a2 as normalizeAllowedUser, a3 as loadSecurityContextConfig, a4 as resolveSecurityContext, a5 as buildSecurityContextFromFlags, a6 as mergeSecurityContexts, c as connectToHypha, a7 as buildSessionShareUrl, a8 as computeOutboundHop, e as shortId, a9 as registerAwaitingReply, aa as buildMachineShareUrl, ab as parseHandle, ac as handleMatchesMetadata } from './run-CSV6OgQ8.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1,12 +1,12 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-DexO3t-N.mjs';
4
- import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
3
+ import { connectAndGetMachine } from './commands-DEZLlXJ3.mjs';
4
+ import { w as wantsHelp } from './cli-BCN1uAze.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:child_process';
7
7
  import 'node:path';
8
8
  import 'node:os';
9
- import './run-DdTcDN3e.mjs';
9
+ import './run-CSV6OgQ8.mjs';
10
10
  import 'os';
11
11
  import 'fs/promises';
12
12
  import 'url';
@@ -1,10 +1,10 @@
1
- import { connectAndResolveSession } from './commands-DexO3t-N.mjs';
2
- import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
1
+ import { connectAndResolveSession } from './commands-DEZLlXJ3.mjs';
2
+ import { w as wantsHelp } from './cli-BCN1uAze.mjs';
3
3
  import 'node:fs';
4
4
  import 'node:child_process';
5
5
  import 'node:path';
6
6
  import 'node:os';
7
- import './run-DdTcDN3e.mjs';
7
+ import './run-CSV6OgQ8.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'fs';
@@ -1,9 +1,9 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
- import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-DexO3t-N.mjs';
3
+ import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-DEZLlXJ3.mjs';
4
4
  import { execFileSync } from 'node:child_process';
5
- import { ag as withFileLock, u as updateIssue, j as addComment, k as addIssue, e as shortId } from './run-DdTcDN3e.mjs';
6
- import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
5
+ import { ag as withFileLock, u as updateIssue, j as addComment, k as addIssue, e as shortId } from './run-CSV6OgQ8.mjs';
6
+ import { w as wantsHelp } from './cli-BCN1uAze.mjs';
7
7
  import 'node:os';
8
8
  import 'os';
9
9
  import 'fs/promises';
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { f as resolveProjectRoot, I as workflowSteps, x as setWorkflowEnabled, D as isWorkflowEnabled, y as removeWorkflow, v as getWorkflow, C as listWorkflows, A as saveWorkflow, B as rawWorkflow } from './run-DdTcDN3e.mjs';
3
- import { w as wantsHelp } from './cli-CQlIsRxF.mjs';
2
+ import { f as resolveProjectRoot, I as workflowSteps, x as setWorkflowEnabled, D as isWorkflowEnabled, y as removeWorkflow, v as getWorkflow, C as listWorkflows, A as saveWorkflow, B as rawWorkflow } from './run-CSV6OgQ8.mjs';
3
+ import { w as wantsHelp } from './cli-BCN1uAze.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { ad as PINNED_CODEX_VERSION, c as connectToHypha } from './run-DdTcDN3e.mjs';
4
+ import { ad as PINNED_CODEX_VERSION, c as connectToHypha } from './run-CSV6OgQ8.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-BO0Qs_kE.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -1,5 +1,5 @@
1
- import { a0 as resolveModel, ah as describeMisconfiguration, ai as buildMachineDeps } from './run-DdTcDN3e.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-BRHu4M46.mjs';
1
+ import { a0 as resolveModel, ah as describeMisconfiguration, ai as buildMachineDeps } from './run-CSV6OgQ8.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-B7sDP61Z.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-DdTcDN3e.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-CSV6OgQ8.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { c as connectToHypha, S as SharingNotificationSync, J as parseJwtEmail } from './run-DdTcDN3e.mjs';
1
+ import { c as connectToHypha, S as SharingNotificationSync, J as parseJwtEmail } from './run-CSV6OgQ8.mjs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.327";
2
+ var version = "0.2.328";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -1,4 +1,4 @@
1
- import { f as resolveProjectRoot, q as getRun, t as listRuns, v as getWorkflow, w as runWorkflow, x as setWorkflowEnabled, y as removeWorkflow, z as validateWorkflowName, A as saveWorkflow, B as rawWorkflow, C as listWorkflows } from './run-DdTcDN3e.mjs';
1
+ import { f as resolveProjectRoot, q as getRun, t as listRuns, v as getWorkflow, w as runWorkflow, x as setWorkflowEnabled, y as removeWorkflow, z as validateWorkflowName, A as saveWorkflow, B as rawWorkflow, C as listWorkflows } from './run-CSV6OgQ8.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { f as resolveProjectRoot, u as updateIssue, h as getIssue, i as resumeIssue, p as pauseIssue, j as addComment, k as addIssue, l as listIssues, m as isPendingIssue, n as searchIssues, o as isVisibleTo } from './run-DdTcDN3e.mjs';
1
+ import { f as resolveProjectRoot, u as updateIssue, h as getIssue, i as resumeIssue, p as pauseIssue, j as addComment, k as addIssue, l as listIssues, m as isPendingIssue, n as searchIssues, o as isVisibleTo } from './run-CSV6OgQ8.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -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 { aj as applyClaudeProxyEnv, ak as composeSessionId, al as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, am as generateHookSettings } from './run-CSV6OgQ8.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';
@@ -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,7 @@ 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 reusableToken = replaced && access === "link" && replaced.linkToken && !spec.regenerateUrl && !crossSessionTakeover ? replaced.linkToken : void 0;
3352
3354
  const mount = {
3353
3355
  name: spec.name,
3354
3356
  directory: resolvedDir,
@@ -3356,10 +3358,10 @@ class ServeManager {
3356
3358
  sessionId: spec.sessionId,
3357
3359
  ownerEmail: spec.ownerEmail,
3358
3360
  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,
3361
+ // Capability token for 'link' access. Precedence: an explicit spec.linkToken
3362
+ // (used by restore to preserve URLs across daemon restarts) → the existing
3363
+ // mount's token on a stable re-apply (#1072) → a freshly generated token.
3364
+ linkToken: access === "link" ? spec.linkToken || reusableToken || generateLinkToken() : void 0,
3363
3365
  addedAt: Date.now()
3364
3366
  };
3365
3367
  this.mounts.set(spec.name, mount);
@@ -7940,7 +7942,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
7940
7942
  process: params.process,
7941
7943
  sessionId: params.sessionId,
7942
7944
  access,
7943
- ownerEmail: ownerEmail2
7945
+ ownerEmail: ownerEmail2,
7946
+ regenerateUrl: params.regenerateUrl
7944
7947
  });
7945
7948
  },
7946
7949
  /** Remove a mount from the shared static file server. */
@@ -8302,7 +8305,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8302
8305
  }
8303
8306
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
8304
8307
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
8305
- const { toolsForRole } = await import('./sideband-BRHu4M46.mjs');
8308
+ const { toolsForRole } = await import('./sideband-B7sDP61Z.mjs');
8306
8309
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
8307
8310
  return fmt(r2);
8308
8311
  }
@@ -8407,7 +8410,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
8407
8410
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
8408
8411
  }
8409
8412
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
8410
- const { queryCore } = await import('./commands-DexO3t-N.mjs');
8413
+ const { queryCore } = await import('./commands-DEZLlXJ3.mjs');
8411
8414
  const timeout = c.reply?.timeout_sec || 120;
8412
8415
  let result;
8413
8416
  try {
@@ -8971,6 +8974,11 @@ function nextBackoffMs(prevMs) {
8971
8974
  if (!prevMs || prevMs <= 0) return RL_BACKOFF_MIN_MS;
8972
8975
  return Math.min(prevMs * 2, RL_BACKOFF_MAX_MS);
8973
8976
  }
8977
+ const LISTENER_ERROR_STRIKE_LIMIT = 3;
8978
+ function decideListenerErrorAction(prevStrikes, limit = LISTENER_ERROR_STRIKE_LIMIT) {
8979
+ const strikes = (prevStrikes > 0 ? prevStrikes : 0) + 1;
8980
+ return { strikes, drop: strikes >= limit };
8981
+ }
8974
8982
  function retryDecision(listenerPresent, now, backoffUntil) {
8975
8983
  if (!listenerPresent) return "drop";
8976
8984
  if (backoffUntil !== void 0 && now < backoffUntil) return "reschedule";
@@ -9712,11 +9720,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9712
9720
  const listeners = [];
9713
9721
  const rateLimitBackoff = /* @__PURE__ */ new Map();
9714
9722
  const rateLimitRetry = /* @__PURE__ */ new Map();
9723
+ const listenerErrorStrikes = /* @__PURE__ */ new Map();
9715
9724
  const removeListener = (listener, reason) => {
9716
9725
  const idx = listeners.indexOf(listener);
9717
9726
  if (idx >= 0) {
9718
9727
  listeners.splice(idx, 1);
9719
9728
  rateLimitBackoff.delete(listener);
9729
+ listenerErrorStrikes.delete(listener);
9720
9730
  const t = rateLimitRetry.get(listener);
9721
9731
  if (t) {
9722
9732
  clearTimeout(t);
@@ -9741,8 +9751,15 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9741
9751
  } else if (kind === "stale") {
9742
9752
  removeListener(listener, "stale connection");
9743
9753
  } else {
9744
- console.error(`[HYPHA SESSION ${sessionId}] Async listener error:`, err);
9745
- removeListener(listener, "async error");
9754
+ const { strikes, drop } = decideListenerErrorAction(listenerErrorStrikes.get(listener) || 0);
9755
+ if (drop) {
9756
+ listenerErrorStrikes.delete(listener);
9757
+ console.error(`[HYPHA SESSION ${sessionId}] Listener error (strike ${strikes}/${LISTENER_ERROR_STRIKE_LIMIT}, dropping):`, err);
9758
+ removeListener(listener, "async error");
9759
+ } else {
9760
+ listenerErrorStrikes.set(listener, strikes);
9761
+ console.warn(`[HYPHA SESSION ${sessionId}] Listener error (strike ${strikes}/${LISTENER_ERROR_STRIKE_LIMIT}, keeping listener):`, String(err?.message || err || ""));
9762
+ }
9746
9763
  }
9747
9764
  };
9748
9765
  const scheduleBackoffRetry = (listener) => {
@@ -9790,11 +9807,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
9790
9807
  if (result && typeof result.catch === "function") {
9791
9808
  result.then(() => {
9792
9809
  rateLimitBackoff.delete(listener);
9810
+ listenerErrorStrikes.delete(listener);
9793
9811
  }).catch((err) => handleListenerRejection(listener, err));
9812
+ } else {
9813
+ listenerErrorStrikes.delete(listener);
9794
9814
  }
9795
9815
  } catch (err) {
9796
- console.error(`[HYPHA SESSION ${sessionId}] Listener error:`, err);
9797
- removeListener(listener, "sync error");
9816
+ handleListenerRejection(listener, err);
9798
9817
  }
9799
9818
  }
9800
9819
  };
@@ -15871,6 +15890,30 @@ function classifyRestoreContinuation(opts) {
15871
15890
  if (opts.wasProcessing && opts.hasResumeId) return "auto-continue";
15872
15891
  return "none";
15873
15892
  }
15893
+ const PERMANENT_RESTORE_PATTERNS = [
15894
+ /failed to create directory/i,
15895
+ /no such file or directory/i,
15896
+ /\benoent\b/i,
15897
+ // spawn ENOENT = launcher/path missing
15898
+ /no agent launchers?/i,
15899
+ /cli (?:is )?(?:not )?installed|command not found/i,
15900
+ /not authorized|permission denied|\beacces\b/i,
15901
+ /\binvalid\b|malformed|unsupported/i
15902
+ ];
15903
+ function classifyRestoreFailure(errorMessage) {
15904
+ const m = errorMessage || "";
15905
+ for (const re of PERMANENT_RESTORE_PATTERNS) if (re.test(m)) return "permanent";
15906
+ return "transient";
15907
+ }
15908
+ const RESTORE_MAX_RETRIES = 3;
15909
+ const RESTORE_BACKOFF_MS = [500, 1500, 4e3];
15910
+ function restoreBackoffMs(attempt) {
15911
+ const i = attempt < 0 ? 0 : Math.min(attempt, RESTORE_BACKOFF_MS.length - 1);
15912
+ return RESTORE_BACKOFF_MS[i];
15913
+ }
15914
+ function shouldRetryRestore(kind, attempt, maxRetries = RESTORE_MAX_RETRIES) {
15915
+ return kind === "transient" && attempt < maxRetries;
15916
+ }
15874
15917
 
15875
15918
  function getShareBaseUrl() {
15876
15919
  return getSvampWebBaseUrl();
@@ -18177,6 +18220,10 @@ function nextFailuresAfterZombieProbeFailure(consecutiveHeartbeatFailures) {
18177
18220
  function shouldRunServiceProbe(args) {
18178
18221
  return !args.inGrace && !args.zombieProbeRan;
18179
18222
  }
18223
+ function decideDisconnectRecovery(args) {
18224
+ if (args.shutdownRequested) return "stay";
18225
+ return args.supervised ? "exit-for-respawn" : "stay";
18226
+ }
18180
18227
  function shouldForceReconnect(consecutiveHeartbeatFailures) {
18181
18228
  if (consecutiveHeartbeatFailures < 2) return false;
18182
18229
  return consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0;
@@ -20224,7 +20271,7 @@ async function startDaemon(options) {
20224
20271
  try {
20225
20272
  const dir = loadSessionIndex()[sessionId]?.directory;
20226
20273
  if (!dir) return;
20227
- const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20274
+ const { reconcileServiceLinks } = await import('./agentCommands-DefL1pIo.mjs');
20228
20275
  const configPath = getSvampConfigPath(dir, sessionId);
20229
20276
  const config = readSvampConfig(configPath);
20230
20277
  const entries = Array.from(urls.entries());
@@ -20246,7 +20293,7 @@ async function startDaemon(options) {
20246
20293
  try {
20247
20294
  const dir = loadSessionIndex()[sessionId]?.directory;
20248
20295
  if (!dir) return;
20249
- const { reconcileServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20296
+ const { reconcileServiceLinks } = await import('./agentCommands-DefL1pIo.mjs');
20250
20297
  const configPath = getSvampConfigPath(dir, sessionId);
20251
20298
  const config = readSvampConfig(configPath);
20252
20299
  const incoming = [{
@@ -20267,7 +20314,7 @@ async function startDaemon(options) {
20267
20314
  try {
20268
20315
  const dir = loadSessionIndex()[sessionId]?.directory;
20269
20316
  if (!dir) return;
20270
- const { dropServiceLinks } = await import('./agentCommands-BiSVzb5D.mjs');
20317
+ const { dropServiceLinks } = await import('./agentCommands-DefL1pIo.mjs');
20271
20318
  const configPath = getSvampConfigPath(dir, sessionId);
20272
20319
  const config = readSvampConfig(configPath);
20273
20320
  if (dropServiceLinks(config, "serve", mountName)) {
@@ -20341,7 +20388,14 @@ async function startDaemon(options) {
20341
20388
  supervised: process.env.SVAMP_SUPERVISED === "1"
20342
20389
  });
20343
20390
  server.on("disconnected", (reason) => {
20344
- logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
20391
+ const supervised2 = process.env.SVAMP_SUPERVISED === "1";
20392
+ const recovery = decideDisconnectRecovery({ supervised: supervised2, shutdownRequested });
20393
+ if (recovery === "exit-for-respawn") {
20394
+ logger.log(`Hypha connection permanently lost: ${reason}. Exiting for a clean supervised respawn (sessions restore via --resume).`);
20395
+ setTimeout(() => process.exit(1), 250);
20396
+ } else {
20397
+ logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running (unsupervised) \u2014 restart manually to reconnect.`);
20398
+ }
20345
20399
  });
20346
20400
  const pidToTrackedSession = /* @__PURE__ */ new Map();
20347
20401
  let sessionCoreRef = {
@@ -22643,11 +22697,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
22643
22697
  });
22644
22698
  },
22645
22699
  onIssue: async (params) => {
22646
- const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
22700
+ const { issueRpc } = await import('./rpc-CZqCc9Pr.mjs');
22647
22701
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
22648
22702
  },
22649
22703
  onWorkflow: async (params) => {
22650
- const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
22704
+ const { workflowRpc } = await import('./rpc-BCLZpI5H.mjs');
22651
22705
  return workflowRpc(params?.cwd || directory, params || {});
22652
22706
  },
22653
22707
  onRipgrep: async (args, cwd) => {
@@ -23353,11 +23407,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
23353
23407
  });
23354
23408
  },
23355
23409
  onIssue: async (params) => {
23356
- const { issueRpc } = await import('./rpc-DXY3KqiA.mjs');
23410
+ const { issueRpc } = await import('./rpc-CZqCc9Pr.mjs');
23357
23411
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
23358
23412
  },
23359
23413
  onWorkflow: async (params) => {
23360
- const { workflowRpc } = await import('./rpc-hdw0ZlCj.mjs');
23414
+ const { workflowRpc } = await import('./rpc-BCLZpI5H.mjs');
23361
23415
  return workflowRpc(params?.cwd || directory, params || {});
23362
23416
  },
23363
23417
  onRipgrep: async (args, cwd) => {
@@ -24443,7 +24497,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24443
24497
  }
24444
24498
  logger.log(`Restoring ${persistedSessions.length} persisted session(s)...`);
24445
24499
  const restoreConcurrency = Math.max(1, parseInt(process.env.SVAMP_RESTORE_CONCURRENCY || "8", 10) || 8);
24446
- const restoreOne = async (persisted) => {
24500
+ const restoreOne = async (persisted, attempt = 0) => {
24447
24501
  try {
24448
24502
  const isOrphaned = persisted.machineId && persisted.machineId !== machineId;
24449
24503
  if (isOrphaned) {
@@ -24500,10 +24554,26 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24500
24554
  sessionsToLoopResume.push({ sessionId: persisted.sessionId, directory: persisted.directory });
24501
24555
  }
24502
24556
  } else {
24503
- logger.log(`Failed to restore session ${persisted.sessionId}: ${result.type}`);
24557
+ const reason = result.errorMessage || result.type;
24558
+ const kind = classifyRestoreFailure(reason);
24559
+ if (shouldRetryRestore(kind, attempt)) {
24560
+ const wait = restoreBackoffMs(attempt);
24561
+ logger.log(`Restore of ${persisted.sessionId} failed (${reason}) \u2014 transient, retrying in ${wait}ms (attempt ${attempt + 1}/${RESTORE_MAX_RETRIES})`);
24562
+ await new Promise((r) => setTimeout(r, wait));
24563
+ return restoreOne(persisted, attempt + 1);
24564
+ }
24565
+ logger.log(`Failed to restore session ${persisted.sessionId}: ${reason} (${kind}; preserved on disk for a later restart)`);
24504
24566
  }
24505
24567
  } catch (err) {
24506
- logger.error(`Error restoring session ${persisted.sessionId}:`, err.message);
24568
+ const reason = err?.message || String(err);
24569
+ const kind = classifyRestoreFailure(reason);
24570
+ if (shouldRetryRestore(kind, attempt)) {
24571
+ const wait = restoreBackoffMs(attempt);
24572
+ logger.log(`Error restoring ${persisted.sessionId}: ${reason} \u2014 transient, retrying in ${wait}ms (attempt ${attempt + 1}/${RESTORE_MAX_RETRIES})`);
24573
+ await new Promise((r) => setTimeout(r, wait));
24574
+ return restoreOne(persisted, attempt + 1);
24575
+ }
24576
+ logger.error(`Error restoring session ${persisted.sessionId} (${kind}, giving up this lifetime):`, reason);
24507
24577
  }
24508
24578
  };
24509
24579
  let restoreCursor = 0;
@@ -24694,7 +24764,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
24694
24764
  const PING_TIMEOUT_MS = 15e3;
24695
24765
  const POST_RECONNECT_GRACE_MS = 2e4;
24696
24766
  const RECONNECT_JITTER_MS = 2500;
24697
- const { WorkflowScheduler } = await import('./scheduler-3LUL1jFD.mjs');
24767
+ const { WorkflowScheduler } = await import('./scheduler-s_j089nA.mjs');
24698
24768
  const workflowProjectRoots = () => {
24699
24769
  const dirs = /* @__PURE__ */ new Set();
24700
24770
  for (const s of pidToTrackedSession.values()) {
@@ -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, C as listWorkflows, D as isWorkflowEnabled, E as workflowSchedules, F as inZone, w as runWorkflow, G as cronMatches } from './run-CSV6OgQ8.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-BCN1uAze.mjs';
3
+ import './run-CSV6OgQ8.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-DEZLlXJ3.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-DefL1pIo.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-DEZLlXJ3.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-DefL1pIo.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-DEZLlXJ3.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-DefL1pIo.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-DEZLlXJ3.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-DEZLlXJ3.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, M as loadMachineContext, N as buildMachineInstructions, O as machineToolsForRole, P as buildMachineTools } from './run-CSV6OgQ8.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.327",
3
+ "version": "0.2.328",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",