svamp-cli 0.2.251 → 0.2.253

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 (25) hide show
  1. package/dist/{agentCommands-Bcqo8iMs.mjs → agentCommands-DYoNzX4K.mjs} +5 -5
  2. package/dist/{auth-aefPotnn.mjs → auth-D0nW0Gyj.mjs} +1 -1
  3. package/dist/cli.mjs +77 -70
  4. package/dist/{commands-Cy6dc7Ey.mjs → commands-B9wShol7.mjs} +1 -1
  5. package/dist/{commands-BwvIihFY.mjs → commands-C3YGnWyy.mjs} +1 -1
  6. package/dist/{commands-BF-wFyla.mjs → commands-CfDWN8Ej.mjs} +2 -2
  7. package/dist/{commands-DV7Somb0.mjs → commands-CvWkQB_c.mjs} +1 -1
  8. package/dist/{commands-CBOY46Ig.mjs → commands-EhQiKpwu.mjs} +6 -6
  9. package/dist/{commands-hPm8AYNG.mjs → commands-YPQSZ4Qx.mjs} +9 -2
  10. package/dist/{commands-D7Sifr5h.mjs → commands-_Tr8pO3_.mjs} +2 -2
  11. package/dist/{fleet-CBIJW3k7.mjs → fleet-Ndfbo8Ni.mjs} +2 -2
  12. package/dist/{frpc-DshAO_N4.mjs → frpc-CLQB-GM1.mjs} +1 -1
  13. package/dist/{headlessCli-l72fsaU0.mjs → headlessCli-CbCWVR81.mjs} +2 -2
  14. package/dist/index.mjs +1 -1
  15. package/dist/{package-ayXBWSo-.mjs → package-BTW_-LFn.mjs} +1 -1
  16. package/dist/{pinnedClaudeCode-ivV7xAp3.mjs → pinnedClaudeCode-DuLXaoGP.mjs} +1 -1
  17. package/dist/{rpc-B2BhUPgt.mjs → rpc-0HSLhpCb.mjs} +1 -1
  18. package/dist/{rpc-CtfcD14b.mjs → rpc-BB-AxQa_.mjs} +1 -1
  19. package/dist/{run-BCU137ie.mjs → run-BtWV7xhR.mjs} +1 -1
  20. package/dist/{run-CRe59VaY.mjs → run-hYUl-Fln.mjs} +146 -71
  21. package/dist/{scheduler-CW_l0_B6.mjs → scheduler-DaFydJPf.mjs} +1 -1
  22. package/dist/{serveCommands-JgPmRokC.mjs → serveCommands-BgtRllaY.mjs} +5 -5
  23. package/dist/{serveManager-CL0QrW7Z.mjs → serveManager-BK5OjhXU.mjs} +2 -2
  24. package/dist/{sideband-CiE3Nqih.mjs → sideband-C0qzprWp.mjs} +1 -1
  25. package/package.json +1 -1
@@ -1869,20 +1869,7 @@ class ChannelOutbox {
1869
1869
  this.byChannel.delete(channelId);
1870
1870
  this.seqByChannel.delete(channelId);
1871
1871
  if (!existsSync(this.file)) return;
1872
- try {
1873
- const kept = readFileSync(this.file, "utf8").split("\n").filter((l) => {
1874
- if (!l.trim()) return false;
1875
- try {
1876
- return JSON.parse(l).channelId !== channelId;
1877
- } catch {
1878
- return false;
1879
- }
1880
- });
1881
- const tmp = this.file + ".tmp";
1882
- writeFileSync(tmp, kept.join("\n") + (kept.length ? "\n" : ""));
1883
- renameSync(tmp, this.file);
1884
- } catch {
1885
- }
1872
+ this._compact();
1886
1873
  }
1887
1874
  }
1888
1875
 
@@ -2998,7 +2985,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2998
2985
  const tunnels = handlers.tunnels;
2999
2986
  if (!tunnels) throw new Error("Tunnel management not available");
3000
2987
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
3001
- const { FrpcTunnel } = await import('./frpc-DshAO_N4.mjs');
2988
+ const { FrpcTunnel } = await import('./frpc-CLQB-GM1.mjs');
3002
2989
  const tunnel = new FrpcTunnel({
3003
2990
  name: params.name,
3004
2991
  ports: params.ports,
@@ -3223,6 +3210,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
3223
3210
  };
3224
3211
  let wired = false;
3225
3212
  if (sid) {
3213
+ try {
3214
+ await authorizeSessionAccess(sid, "admin", context);
3215
+ } catch {
3216
+ return { success: false, error: `Not authorized: launching a meeting agent for session ${sid} requires admin access to that session.` };
3217
+ }
3226
3218
  const sessionRpc = handlers.getSessionRPCHandlers?.(sid);
3227
3219
  if (sessionRpc?.saveChannel) {
3228
3220
  try {
@@ -3304,10 +3296,12 @@ async function registerMachineService(server, machineId, metadata, daemonState,
3304
3296
  },
3305
3297
  // Drives the title-bar "WISE in a meeting" indicator (poll per session).
3306
3298
  wiseMeetingStatus: async (params, context) => {
3307
- if (!roleAtLeast(getEffectiveRole(context, currentMetadata.sharing), "view")) {
3308
- return { active: false, error: "Not authorized to view meeting status on this machine." };
3309
- }
3310
3299
  if (params?.sessionId) {
3300
+ try {
3301
+ await authorizeSessionAccess(params.sessionId, "view", context);
3302
+ } catch {
3303
+ return { active: false, error: `Not authorized to view meeting status for session ${params.sessionId}.` };
3304
+ }
3311
3305
  const rec = activeMeetingAgents.get(params.sessionId);
3312
3306
  let transcript = [];
3313
3307
  try {
@@ -3317,6 +3311,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
3317
3311
  }
3318
3312
  return { active: !!rec, meeting: rec || null, transcript };
3319
3313
  }
3314
+ if (!roleAtLeast(getEffectiveRole(context, currentMetadata.sharing), "view")) {
3315
+ return { active: false, error: "Not authorized to view meeting status on this machine." };
3316
+ }
3320
3317
  return { active: activeMeetingAgents.size > 0, meetings: Array.from(activeMeetingAgents.values()) };
3321
3318
  },
3322
3319
  // GAP 3 — post a chat message into the live meeting. The deep coding agent
@@ -3455,7 +3452,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3455
3452
  }
3456
3453
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3457
3454
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3458
- const { toolsForRole } = await import('./sideband-CiE3Nqih.mjs');
3455
+ const { toolsForRole } = await import('./sideband-C0qzprWp.mjs');
3459
3456
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3460
3457
  return fmt(r2);
3461
3458
  }
@@ -3554,7 +3551,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3554
3551
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3555
3552
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3556
3553
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3557
- const { queryCore } = await import('./commands-hPm8AYNG.mjs');
3554
+ const { queryCore } = await import('./commands-YPQSZ4Qx.mjs');
3558
3555
  const timeout = c.reply?.timeout_sec || 120;
3559
3556
  let result;
3560
3557
  try {
@@ -3816,14 +3813,19 @@ function registerAwaitingReply(sessionId, threadId, now = Date.now()) {
3816
3813
  }
3817
3814
  writeAwaiting(sessionId, map);
3818
3815
  }
3819
- function takeAwaitingReply(sessionId, threadId, now = Date.now()) {
3816
+ function peekAwaitingReply(sessionId, threadId, now = Date.now()) {
3820
3817
  if (!sessionId || !threadId) return false;
3821
3818
  const map = readAwaiting(sessionId);
3822
3819
  const at = map[threadId];
3823
3820
  if (typeof at !== "number") return false;
3821
+ return now - at <= AWAIT_TTL_MS;
3822
+ }
3823
+ function consumeAwaitingReply(sessionId, threadId) {
3824
+ if (!sessionId || !threadId) return;
3825
+ const map = readAwaiting(sessionId);
3826
+ if (!(threadId in map)) return;
3824
3827
  delete map[threadId];
3825
3828
  writeAwaiting(sessionId, map);
3826
- return now - at <= AWAIT_TTL_MS;
3827
3829
  }
3828
3830
  function computeOutboundHop(sessionId) {
3829
3831
  if (!sessionId) return { hopCount: 0, fromInboxTurn: false };
@@ -4180,6 +4182,26 @@ function isStructuredMessage(msg) {
4180
4182
  function escapeXml(s) {
4181
4183
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4182
4184
  }
4185
+ function sanitizeInboundInboxMessage(message, opts) {
4186
+ if (opts.trusted) return message;
4187
+ const email = opts.callerEmail;
4188
+ return {
4189
+ ...message,
4190
+ // Attribute to the SERVER-KNOWN identity, never the caller's self-asserted `from`.
4191
+ // An authenticated collaborator is an honest verified human (`user:<email>` → may
4192
+ // wake, attributed to THEM not the owner); an anonymous caller cannot claim human.
4193
+ from: email ? `user:${email}` : "agent:anonymous",
4194
+ verified: !!email,
4195
+ // Strip self-asserted channel provenance + async-reply routing a direct caller
4196
+ // has no business setting (legit channel ingress goes via the trusted channel path).
4197
+ channel: void 0,
4198
+ channelId: void 0,
4199
+ correlationId: void 0,
4200
+ // Escape the body so a nested </svamp-message> can't break out and forge a second
4201
+ // attributed envelope in the agent's transcript.
4202
+ body: xmlEscape(String(message.body ?? "").slice(0, 16 * 1024))
4203
+ };
4204
+ }
4183
4205
  function formatInboxMessageXml(msg) {
4184
4206
  if (!isStructuredMessage(msg)) return msg.body;
4185
4207
  const attrs = [`message-id="${escapeXml(msg.messageId)}"`];
@@ -5308,12 +5330,14 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
5308
5330
  // ── Inbox ──
5309
5331
  sendInboxMessage: async (message, context) => {
5310
5332
  authorizeRequest(context, metadata.sharing, "interact");
5311
- inbox.push(message);
5333
+ const trusted = getEffectiveRole(context, metadata.sharing) === "admin";
5334
+ const msg = sanitizeInboundInboxMessage(message, { trusted, callerEmail: context?.user?.email });
5335
+ inbox.push(msg);
5312
5336
  while (inbox.length > INBOX_MAX) inbox.shift();
5313
5337
  syncInboxToMetadata();
5314
- callbacks.onInboxMessage?.(message);
5315
- notifyListeners({ type: "inbox-update", sessionId, message });
5316
- return { success: true, messageId: message.messageId };
5338
+ callbacks.onInboxMessage?.(msg);
5339
+ notifyListeners({ type: "inbox-update", sessionId, message: msg });
5340
+ return { success: true, messageId: msg.messageId };
5317
5341
  },
5318
5342
  getInbox: async (opts, context) => {
5319
5343
  authorizeRequest(context, metadata.sharing, "view");
@@ -6282,12 +6306,10 @@ async function registerDebugService(server, machineId, deps) {
6282
6306
  // require_context: true → Hypha injects the caller context as the last arg of every
6283
6307
  // method, which the authorize() gate below enforces. 'unlisted' only hides discovery.
6284
6308
  config: { visibility: "unlisted", require_context: true },
6285
- healthCheck: async () => ({
6286
- ok: true,
6287
- machineId,
6288
- uptime: process.uptime(),
6289
- timestamp: Date.now()
6290
- }),
6309
+ healthCheck: async (context) => {
6310
+ authorize(context, "view");
6311
+ return { ok: true, machineId, uptime: process.uptime(), timestamp: Date.now() };
6312
+ },
6291
6313
  getSessionStates: async (context) => {
6292
6314
  authorize(context, "view");
6293
6315
  const sessions = deps.getTrackedSessions();
@@ -10130,6 +10152,18 @@ var api = /*#__PURE__*/Object.freeze({
10130
10152
  searchSkills: searchSkills
10131
10153
  });
10132
10154
 
10155
+ function signalProcessTree(child, signal) {
10156
+ const pid = child.pid;
10157
+ if (pid === void 0) return;
10158
+ try {
10159
+ process.kill(-pid, signal);
10160
+ } catch {
10161
+ try {
10162
+ child.kill(signal);
10163
+ } catch {
10164
+ }
10165
+ }
10166
+ }
10133
10167
  const DEFAULT_PROBE_INTERVAL_S = 10;
10134
10168
  const DEFAULT_PROBE_TIMEOUT_S = 5;
10135
10169
  const DEFAULT_PROBE_FAILURE_THRESHOLD = 3;
@@ -10153,7 +10187,7 @@ class ProcessSupervisor {
10153
10187
  /** Stop all managed processes (called on daemon shutdown). */
10154
10188
  async stopAll() {
10155
10189
  const ids = Array.from(this.entries.keys());
10156
- await Promise.all(ids.map((id) => this.stop(id).catch(() => {
10190
+ await Promise.all(ids.map((id) => this.stop(id, { markDesiredStopped: false }).catch(() => {
10157
10191
  })));
10158
10192
  }
10159
10193
  // ── Public API ────────────────────────────────────────────────────────────
@@ -10191,11 +10225,20 @@ class ProcessSupervisor {
10191
10225
  throw new Error(`Process '${entry.spec.name}' is already running`);
10192
10226
  }
10193
10227
  entry.stopping = false;
10228
+ if (entry.spec.desiredStopped) {
10229
+ entry.spec.desiredStopped = false;
10230
+ await this.persistSpec(entry.spec);
10231
+ }
10194
10232
  this.resetFailureTracking(entry);
10195
10233
  await this.startEntry(entry, false);
10196
10234
  }
10197
- /** Stop a running process. Does NOT remove it from supervision. */
10198
- async stop(idOrName) {
10235
+ /**
10236
+ * Stop a running process. Does NOT remove it from supervision.
10237
+ * @param opts.markDesiredStopped When true (default — an EXPLICIT user `stop`), persist
10238
+ * a desired-stopped intent so a daemon restart does not resurrect it. `stopAll` (a daemon
10239
+ * shutdown, not a user action) passes false so keepAlive processes auto-restore (#nightly).
10240
+ */
10241
+ async stop(idOrName, opts) {
10199
10242
  const entry = this.require(idOrName);
10200
10243
  entry.stopping = true;
10201
10244
  this.clearTimers(entry);
@@ -10206,6 +10249,10 @@ class ProcessSupervisor {
10206
10249
  entry.state.status = "stopped";
10207
10250
  entry.state.stoppedAt = Date.now();
10208
10251
  entry.state.pid = void 0;
10252
+ if (opts?.markDesiredStopped !== false && !entry.deleted && !entry.spec.desiredStopped) {
10253
+ entry.spec.desiredStopped = true;
10254
+ await this.persistSpec(entry.spec);
10255
+ }
10209
10256
  }
10210
10257
  /** Restart a process (stop if running, then start again). */
10211
10258
  async restart(idOrName) {
@@ -10223,6 +10270,10 @@ class ProcessSupervisor {
10223
10270
  if (entry.deleted) return;
10224
10271
  entry.stopping = false;
10225
10272
  this.resetFailureTracking(entry);
10273
+ if (entry.spec.desiredStopped) {
10274
+ entry.spec.desiredStopped = false;
10275
+ await this.persistSpec(entry.spec);
10276
+ }
10226
10277
  entry.state.restartCount++;
10227
10278
  await this.startEntry(entry, false);
10228
10279
  } finally {
@@ -10357,12 +10408,17 @@ class ProcessSupervisor {
10357
10408
  const spec = JSON.parse(raw);
10358
10409
  const entry = this.makeEntry(spec);
10359
10410
  this.entries.set(spec.id, entry);
10360
- if (spec.keepAlive) {
10411
+ if (spec.keepAlive && !spec.desiredStopped) {
10361
10412
  await this.startEntry(
10362
10413
  entry,
10363
10414
  true
10364
10415
  /* onRestore */
10365
10416
  );
10417
+ } else if (spec.keepAlive && spec.desiredStopped) {
10418
+ entry.state.status = "stopped";
10419
+ if (spec.ttl !== void 0 && spec.ttl > 0) this.setupTTL(entry);
10420
+ } else if (spec.ttl !== void 0 && spec.ttl > 0) {
10421
+ this.setupTTL(entry);
10366
10422
  }
10367
10423
  loaded++;
10368
10424
  } catch (err) {
@@ -10439,7 +10495,13 @@ class ProcessSupervisor {
10439
10495
  const child = spawn$1(spec.command, spec.args, {
10440
10496
  cwd: spec.workdir,
10441
10497
  env,
10442
- stdio: ["ignore", "pipe", "pipe"]
10498
+ stdio: ["ignore", "pipe", "pipe"],
10499
+ // #0316: run each supervised process in its OWN process group (setsid) so a
10500
+ // shell-wrapping spec (`bash -c …`, `npm start`) can be torn down with the whole
10501
+ // tree — otherwise stop/restart/TTL only kills the direct PID and leaks
10502
+ // grandchildren. Kills below signal the group (negative PID). POSIX only; on
10503
+ // Windows `detached` just decouples the console and the group-kill falls back.
10504
+ detached: process.platform !== "win32"
10443
10505
  });
10444
10506
  entry.child = child;
10445
10507
  state.status = "running";
@@ -10489,7 +10551,6 @@ class ProcessSupervisor {
10489
10551
  const uptime = state.startedAt ? Date.now() - state.startedAt : 0;
10490
10552
  const now = Date.now();
10491
10553
  if (uptime > BACKOFF_RESET_WINDOW_MS) {
10492
- state.restartCount = 0;
10493
10554
  state.consecutiveFailures = 0;
10494
10555
  entry.failureWindowStart = void 0;
10495
10556
  } else {
@@ -10602,10 +10663,7 @@ class ProcessSupervisor {
10602
10663
  console.warn(`[SUPERVISOR] Probe failed for '${entry.spec.name}' \u2014 killing to trigger restart`);
10603
10664
  entry.state.consecutiveProbeFailures = 0;
10604
10665
  if (entry.child) {
10605
- try {
10606
- entry.child.kill("SIGTERM");
10607
- } catch {
10608
- }
10666
+ signalProcessTree(entry.child, "SIGTERM");
10609
10667
  }
10610
10668
  }
10611
10669
  // ── TTL ───────────────────────────────────────────────────────────────────
@@ -10650,15 +10708,9 @@ class ProcessSupervisor {
10650
10708
  };
10651
10709
  child.once("exit", done);
10652
10710
  child.once("close", done);
10653
- try {
10654
- child.kill("SIGTERM");
10655
- } catch {
10656
- }
10711
+ signalProcessTree(child, "SIGTERM");
10657
10712
  forceKillTimer = setTimeout(() => {
10658
- try {
10659
- child.kill("SIGKILL");
10660
- } catch {
10661
- }
10713
+ signalProcessTree(child, "SIGKILL");
10662
10714
  hardDeadlineTimer = setTimeout(() => {
10663
10715
  if (!resolved) {
10664
10716
  resolved = true;
@@ -11361,6 +11413,9 @@ function fireIdleWorkflows(root, sessionId, deps = {}) {
11361
11413
  }
11362
11414
 
11363
11415
  const UNBOUNDED_LOOP_ITERATIONS = 1e6;
11416
+ function loopCancelledDuringVerify(current) {
11417
+ return !!current && (current.phase === "cancelled" || current.active === false);
11418
+ }
11364
11419
  function backlogOraclePending(projectRoot, sessionId) {
11365
11420
  const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
11366
11421
  return listIssues(projectRoot).filter(inScope).filter((i) => i.status === "ready" || i.status === "in_progress").map((i) => ({ id: i.id }));
@@ -11488,7 +11543,7 @@ function computeSoftTurnCap(hardMax, cadence) {
11488
11543
  return Math.ceil(hardMax / chunks);
11489
11544
  }
11490
11545
  const DEFAULT_LOOP_STUCK_CHECKPOINTS = 3;
11491
- const DEFAULT_LOOP_MAX_AUTORESUMES = 3;
11546
+ const DEFAULT_LOOP_MAX_AUTORESUMES = Number.POSITIVE_INFINITY;
11492
11547
  const PROGRESS_HISTORY_CAP = 12;
11493
11548
  function resolveStuckLimit(envValue) {
11494
11549
  const n = Number(envValue);
@@ -11533,7 +11588,8 @@ function decideLoopContinuation(input) {
11533
11588
  return { action: "continue", kind: "progress", consumeAutoResume: false, reason: `still making progress (${stuckCheckpoints}/${stuckLimit} stuck checkpoints)` };
11534
11589
  }
11535
11590
  if (autoResumes < maxAutoResumes) {
11536
- return { action: "continue", kind: "autoresume", consumeAutoResume: true, reason: `no progress across ${stuckCheckpoints} checkpoints \u2014 auto-resume ${autoResumes + 1}/${maxAutoResumes}` };
11591
+ const label = Number.isFinite(maxAutoResumes) ? `auto-resume ${autoResumes + 1}/${maxAutoResumes}` : `auto-resume ${autoResumes + 1} (unbounded \u2014 bounded only by the cost/iteration ceiling)`;
11592
+ return { action: "continue", kind: "autoresume", consumeAutoResume: true, reason: `no progress across ${stuckCheckpoints} checkpoints \u2014 ${label}` };
11537
11593
  }
11538
11594
  const tail = `no progress across ${stuckCheckpoints} checkpoints; ${maxAutoResumes} auto-resume(s) exhausted`;
11539
11595
  return { action: "stop", kind: "stuck", reason: input.stuckReason ? `${input.stuckReason} \u2014 ${tail}` : tail };
@@ -12505,17 +12561,30 @@ function classifyOptsForMessage(sessionId, message, ownerEmail) {
12505
12561
  const fromVal = message.from || "";
12506
12562
  const senderEmail = message.verified && fromVal.includes("@") ? fromVal.replace(/^user:/, "").toLowerCase() : void 0;
12507
12563
  const self = !!(ownerEmail && senderEmail && senderEmail === String(ownerEmail).toLowerCase());
12508
- const openThreadReply = takeAwaitingReply(sessionId, message.threadId);
12564
+ const openThreadReply = peekAwaitingReply(sessionId, message.threadId);
12509
12565
  return { self, openThreadReply };
12510
12566
  }
12567
+ function classifyInboundForSession(sessionId, message, ownerEmail) {
12568
+ const opts = classifyOptsForMessage(sessionId, message, ownerEmail);
12569
+ const decision = classifyInbound(message, Date.now(), opts);
12570
+ if (opts.openThreadReply && decision.action === "wake") {
12571
+ consumeAwaitingReply(sessionId, message.threadId);
12572
+ }
12573
+ return decision;
12574
+ }
12511
12575
  function writeGoalLoopState(directory, sessionId, state) {
12512
12576
  try {
12513
12577
  const dir = getLoopDir(directory, sessionId);
12514
12578
  mkdirSync$1(dir, { recursive: true });
12515
- writeFileSync$1(join$1(dir, "loop-state.json"), JSON.stringify({ session_id: sessionId, engine: "goal", ...state }, null, 2));
12579
+ atomicWriteLoopState(join$1(dir, "loop-state.json"), { session_id: sessionId, engine: "goal", ...state });
12516
12580
  } catch {
12517
12581
  }
12518
12582
  }
12583
+ function atomicWriteLoopState(path, obj) {
12584
+ const tmp = path + ".tmp";
12585
+ writeFileSync$1(tmp, JSON.stringify(obj, null, 2));
12586
+ renameSync$1(tmp, path);
12587
+ }
12519
12588
  function safeBacklogPendingCount(projectRoot, sessionId) {
12520
12589
  try {
12521
12590
  return backlogOraclePending(projectRoot, sessionId).length;
@@ -12881,7 +12950,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
12881
12950
  if (wasStopped) {
12882
12951
  try {
12883
12952
  const sp = join$1(getLoopDir(directory, sessionId), "loop-state.json");
12884
- writeFileSync$1(sp, JSON.stringify({
12953
+ atomicWriteLoopState(sp, {
12885
12954
  ...existing,
12886
12955
  active: true,
12887
12956
  phase: "continue",
@@ -12893,7 +12962,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
12893
12962
  gave_up_reason: void 0,
12894
12963
  stall_hinted: false,
12895
12964
  resumed_at: Date.now()
12896
- }, null, 2));
12965
+ });
12897
12966
  } catch {
12898
12967
  }
12899
12968
  const eq = getMetadata().messageQueue || [];
@@ -13380,7 +13449,7 @@ async function startDaemon(options) {
13380
13449
  try {
13381
13450
  const dir = loadSessionIndex()[sessionId]?.directory;
13382
13451
  if (!dir) return;
13383
- const { reconcileServiceLinks } = await import('./agentCommands-Bcqo8iMs.mjs');
13452
+ const { reconcileServiceLinks } = await import('./agentCommands-DYoNzX4K.mjs');
13384
13453
  const configPath = getSvampConfigPath(dir, sessionId);
13385
13454
  const config = readSvampConfig(configPath);
13386
13455
  const entries = Array.from(urls.entries());
@@ -13398,7 +13467,7 @@ async function startDaemon(options) {
13398
13467
  }
13399
13468
  }
13400
13469
  async function createExposedTunnel(spec) {
13401
- const { FrpcTunnel } = await import('./frpc-DshAO_N4.mjs');
13470
+ const { FrpcTunnel } = await import('./frpc-CLQB-GM1.mjs');
13402
13471
  const tunnel = new FrpcTunnel({
13403
13472
  name: spec.name,
13404
13473
  ports: spec.ports,
@@ -13418,14 +13487,14 @@ async function startDaemon(options) {
13418
13487
  return tunnel;
13419
13488
  }
13420
13489
  const tunnelRecreateState = /* @__PURE__ */ new Map();
13421
- const { ServeManager } = await import('./serveManager-CL0QrW7Z.mjs');
13490
+ const { ServeManager } = await import('./serveManager-BK5OjhXU.mjs');
13422
13491
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
13423
13492
  ensureAutoInstalledSkills(logger).catch(() => {
13424
13493
  });
13425
13494
  ensureAutoInstalledCommands(logger);
13426
13495
  (async () => {
13427
13496
  try {
13428
- const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-ivV7xAp3.mjs');
13497
+ const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-DuLXaoGP.mjs');
13429
13498
  beginClaudeVersionReconcile((msg) => logger.log(msg));
13430
13499
  } catch (e) {
13431
13500
  logger.log(`[claude-version] check failed: ${e?.message || e}`);
@@ -14096,6 +14165,10 @@ ${parts.join("\n")}`);
14096
14165
  log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
14097
14166
  }
14098
14167
  );
14168
+ if (loopCancelledDuringVerify(readLoopState(directory, sessionId))) {
14169
+ logger.log(`[Session ${sessionId}] loop was cancelled during verification \u2014 not re-kicking (#0308)`);
14170
+ return;
14171
+ }
14099
14172
  const ledger = ls.ledger;
14100
14173
  const budget = ls.budget;
14101
14174
  const progress_history = ls.progress_history;
@@ -14144,7 +14217,9 @@ ${parts.join("\n")}`);
14144
14217
  max_iterations: ls.max_iterations,
14145
14218
  budget,
14146
14219
  started_at: startedAt,
14147
- holds,
14220
+ // #0319: a PROGRESSING loop resets its hold counter so the evaluator-guided re-kick
14221
+ // mechanism re-engages fresh (it's making headway, not stuck); an auto-resume keeps it.
14222
+ holds: decision.kind === "progress" ? 0 : holds,
14148
14223
  ledger,
14149
14224
  progress_history,
14150
14225
  auto_resumes: nextAuto
@@ -15410,7 +15485,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15410
15485
  },
15411
15486
  onInboxMessage: (message) => {
15412
15487
  if (trackedSession?.stopped) return;
15413
- const decision = classifyInbound(message, Date.now(), classifyOptsForMessage(sessionId, message, sessionMetadata?.sharing?.owner));
15488
+ const decision = classifyInboundForSession(sessionId, message, sessionMetadata?.sharing?.owner);
15414
15489
  logger.log(`[Session ${sessionId}] Inbox message (urgency: ${message.urgency || "normal"}, hop: ${message.hopCount ?? 0}, from: ${message.from || "unknown"}) \u2192 ${decision.action} (${decision.reason})`);
15415
15490
  if (decision.breakerTripped) {
15416
15491
  logger.error(`[Session ${sessionId}] Inbox circuit breaker TRIPPED \u2014 cross-agent wake rate too high; auto-waking paused.`);
@@ -15506,11 +15581,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15506
15581
  });
15507
15582
  },
15508
15583
  onIssue: async (params) => {
15509
- const { issueRpc } = await import('./rpc-B2BhUPgt.mjs');
15584
+ const { issueRpc } = await import('./rpc-0HSLhpCb.mjs');
15510
15585
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
15511
15586
  },
15512
15587
  onWorkflow: async (params) => {
15513
- const { workflowRpc } = await import('./rpc-CtfcD14b.mjs');
15588
+ const { workflowRpc } = await import('./rpc-BB-AxQa_.mjs');
15514
15589
  return workflowRpc(params?.cwd || directory, params || {});
15515
15590
  },
15516
15591
  onRipgrep: async (args, cwd) => {
@@ -15692,7 +15767,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15692
15767
  if (s.active === false || s.phase === "done") {
15693
15768
  try {
15694
15769
  const lp = join$1(getLoopDir(directory, sessionId), "loop-state.json");
15695
- writeFileSync$1(lp, JSON.stringify({
15770
+ atomicWriteLoopState(lp, {
15696
15771
  ...s,
15697
15772
  active: true,
15698
15773
  phase: "continue",
@@ -15702,7 +15777,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15702
15777
  stall_hinted: false,
15703
15778
  resumed_at: Date.now()
15704
15779
  // #0156: re-arm the stall hint
15705
- }, null, 2));
15780
+ });
15706
15781
  } catch {
15707
15782
  }
15708
15783
  }
@@ -15919,7 +15994,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15919
15994
  },
15920
15995
  onInboxMessage: (message) => {
15921
15996
  if (acpStopped) return;
15922
- const decision = classifyInbound(message, Date.now(), classifyOptsForMessage(sessionId, message, sessionMetadata?.sharing?.owner));
15997
+ const decision = classifyInboundForSession(sessionId, message, sessionMetadata?.sharing?.owner);
15923
15998
  logger.log(`[${agentName} Session ${sessionId}] Inbox message (urgency: ${message.urgency || "normal"}, hop: ${message.hopCount ?? 0}, from: ${message.from || "unknown"}) \u2192 ${decision.action} (${decision.reason})`);
15924
15999
  if (decision.breakerTripped) {
15925
16000
  logger.error(`[${agentName} Session ${sessionId}] Inbox circuit breaker TRIPPED \u2014 auto-waking paused.`);
@@ -16046,11 +16121,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
16046
16121
  });
16047
16122
  },
16048
16123
  onIssue: async (params) => {
16049
- const { issueRpc } = await import('./rpc-B2BhUPgt.mjs');
16124
+ const { issueRpc } = await import('./rpc-0HSLhpCb.mjs');
16050
16125
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
16051
16126
  },
16052
16127
  onWorkflow: async (params) => {
16053
- const { workflowRpc } = await import('./rpc-CtfcD14b.mjs');
16128
+ const { workflowRpc } = await import('./rpc-BB-AxQa_.mjs');
16054
16129
  return workflowRpc(params?.cwd || directory, params || {});
16055
16130
  },
16056
16131
  onRipgrep: async (args, cwd) => {
@@ -16812,7 +16887,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
16812
16887
  }
16813
16888
  if (persistedSessions.length > 0) {
16814
16889
  try {
16815
- const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-ivV7xAp3.mjs');
16890
+ const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-DuLXaoGP.mjs');
16816
16891
  await awaitClaudeVersionReady();
16817
16892
  } catch {
16818
16893
  }
@@ -17009,7 +17084,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
17009
17084
  const PING_TIMEOUT_MS = 15e3;
17010
17085
  const POST_RECONNECT_GRACE_MS = 2e4;
17011
17086
  const RECONNECT_JITTER_MS = 2500;
17012
- const { WorkflowScheduler } = await import('./scheduler-CW_l0_B6.mjs');
17087
+ const { WorkflowScheduler } = await import('./scheduler-DaFydJPf.mjs');
17013
17088
  const workflowScheduler = new WorkflowScheduler({
17014
17089
  projectRoots: () => {
17015
17090
  const dirs = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import { n as resolveProjectRoot, H as listWorkflows, I as isWorkflowEnabled, J as workflowCrons, C as runWorkflow, K as cronMatches } from './run-CRe59VaY.mjs';
1
+ import { n as resolveProjectRoot, H as listWorkflows, I as isWorkflowEnabled, J as workflowCrons, C as runWorkflow, K as cronMatches } from './run-hYUl-Fln.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-hPm8AYNG.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-YPQSZ4Qx.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-hPm8AYNG.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-YPQSZ4Qx.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-hPm8AYNG.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-YPQSZ4Qx.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-hPm8AYNG.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-YPQSZ4Qx.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-hPm8AYNG.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-YPQSZ4Qx.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { l as getHyphaServerUrl, S as ServeAuth, m as hasCookieToken } from './run-CRe59VaY.mjs';
7
+ import { l as getHyphaServerUrl, S as ServeAuth, m as hasCookieToken } from './run-hYUl-Fln.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -751,7 +751,7 @@ class ServeManager {
751
751
  const mount = this.mounts.get(mountName);
752
752
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
753
753
  try {
754
- const { FrpcTunnel } = await import('./frpc-DshAO_N4.mjs');
754
+ const { FrpcTunnel } = await import('./frpc-CLQB-GM1.mjs');
755
755
  let tunnel;
756
756
  tunnel = new FrpcTunnel({
757
757
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-CRe59VaY.mjs';
1
+ import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-hYUl-Fln.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.251",
3
+ "version": "0.2.253",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",