svamp-cli 0.2.204 → 0.2.206

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/bin/skills/loop/bin/stop-gate.mjs +84 -3
  2. package/bin/skills/loop/test/test-loop-gate.mjs +65 -12
  3. package/dist/{agentCommands-D92DfTGP.mjs → agentCommands-akIoZemu.mjs} +5 -5
  4. package/dist/{auth-BvO7QtaP.mjs → auth-Dzf0cndq.mjs} +1 -1
  5. package/dist/cli.mjs +63 -61
  6. package/dist/{commands-CHqMRENk.mjs → commands-8Rd6p4B4.mjs} +1 -1
  7. package/dist/{commands-wtMi-xJY.mjs → commands-BPhgtKi2.mjs} +2 -2
  8. package/dist/{commands-u68ODpBt.mjs → commands-BRmWVCNf.mjs} +34 -11
  9. package/dist/{commands-BduSUevP.mjs → commands-BfakuMvA.mjs} +2 -2
  10. package/dist/{commands-5AerKSMm.mjs → commands-Dp7BFqYl.mjs} +1 -1
  11. package/dist/{commands-DwWuWW99.mjs → commands-OMKB5-oS.mjs} +29 -11
  12. package/dist/{commands-Dyy_1c0I.mjs → commands-V6wL2BtU.mjs} +5 -5
  13. package/dist/{fleet-CFe5Kt6k.mjs → fleet-Ci190s0w.mjs} +1 -1
  14. package/dist/{frpc-DMV0fZBM.mjs → frpc-H52IJYcb.mjs} +1 -1
  15. package/dist/{headlessCli-COLHIWk8.mjs → headlessCli-tLrOER4U.mjs} +2 -2
  16. package/dist/index.mjs +1 -1
  17. package/dist/{package-CqGr9_jQ.mjs → package-MNPXOWOP.mjs} +1 -1
  18. package/dist/{rpc-CoNLjY5x.mjs → rpc-BHUHF_Mv.mjs} +10 -3
  19. package/dist/{rpc-DBpyHJUs.mjs → rpc-CFrVMETs.mjs} +1 -1
  20. package/dist/{run-CR8y6yQ7.mjs → run-C4OsABqC.mjs} +1 -1
  21. package/dist/{run-BfgkuBEg.mjs → run-C9ZeF2iG.mjs} +101 -18
  22. package/dist/{scheduler-C1_HeBdn.mjs → scheduler-ErjEISCv.mjs} +1 -1
  23. package/dist/{serveCommands-d3t4RkrF.mjs → serveCommands-feGogIWC.mjs} +5 -5
  24. package/dist/{serveManager-DA9elK4m.mjs → serveManager-Be-R7R8_.mjs} +2 -2
  25. package/dist/{sideband-DNLuglX3.mjs → sideband-cU4KDceu.mjs} +1 -1
  26. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { m as resolveProjectRoot, w as searchIssues, v as listIssues, o as resumeIssue, p as pauseIssue, q as addComment, u as updateIssue, n as getIssue, x as isVisibleTo, z as summarize, t as addIssue } from './run-BfgkuBEg.mjs';
2
+ import { m as resolveProjectRoot, w as searchIssues, v as listIssues, o as resumeIssue, p as pauseIssue, q as addComment, u as updateIssue, n as getIssue, x as isVisibleTo, z as summarize, t as addIssue } from './run-C9ZeF2iG.mjs';
3
3
  import 'os';
4
4
  import 'fs/promises';
5
5
  import 'fs';
@@ -63,7 +63,8 @@ function fmtIssue(i) {
63
63
  const label = i.labels.length ? ` [${i.labels.join(", ")}]` : "";
64
64
  const v = i.verify ? ` (verify:${i.verify.type})` : "";
65
65
  const br = i.branch ? ` {branch:${i.branch}}` : "";
66
- return `#${i.id} ${STATUS_GLYPH[i.status]} ${i.title}${tri}${disp}${label}${v}${br}`;
66
+ const ans = i.answer ? " \u21A9answered" : "";
67
+ return `#${i.id} ${STATUS_GLYPH[i.status]} ${i.title}${tri}${disp}${label}${v}${br}${ans}`;
67
68
  }
68
69
  function buildVerify(args) {
69
70
  const cmd = flag(args, "--verify-cmd");
@@ -157,6 +158,10 @@ ${s.ready} ready \xB7 ${s.in_progress} in-progress \xB7 ${s.archived} archived`)
157
158
  }
158
159
  out(fmtIssue(issue));
159
160
  out(`status: ${issue.status} \xB7 scope: ${issue.scope} \xB7 created: ${issue.created}${issue.closed ? ` \xB7 closed: ${issue.closed}` : ""}`);
161
+ if (issue.answer) out(`
162
+ \u21A9 The user answered your question \u2014 act on this: ${issue.answer}`);
163
+ if (issue.question) out(`
164
+ \u23F8 Paused \u2014 awaiting your question: ${issue.question.text}`);
160
165
  if (issue.original) out(`
161
166
  Original request:
162
167
  ${issue.original}`);
@@ -175,7 +180,7 @@ ${issue.body}`);
175
180
  const worker = flag(rest, "--session") || sessionId;
176
181
  const cur = getIssue(root, id);
177
182
  const claim = worker && cur && cur.scope === "project" && !cur.session ? { session: worker } : {};
178
- const updated = updateIssue(root, id, { status: "in_progress", ...branch ? { branch } : {}, ...claim });
183
+ const updated = updateIssue(root, id, { status: "in_progress", answer: null, ...branch ? { branch } : {}, ...claim });
179
184
  if (!updated) {
180
185
  console.error(`Issue not found: ${id}`);
181
186
  process.exit(1);
@@ -367,7 +372,9 @@ ${tail}
367
372
  " - A question you can just answer \u2192 reply, then close.",
368
373
  " - Belongs to another session/repo \u2192 say where it should go (and route it), then close/redirect.",
369
374
  " B. UNDERSPECIFIED? Do not guess. Enrich from the codebase where you can, and CONFIRM with the user",
370
- " for anything genuinely ambiguous (ask 1-2 sharp questions) BEFORE marking it ready.",
375
+ " for anything genuinely ambiguous (ask 1-2 sharp questions) BEFORE marking it ready. TRIAGE is THE",
376
+ " place to ask \u2014 the work loop runs on sensible defaults and will NOT stop to ask, so surface every",
377
+ ' real question now (`svamp issue pause <id> --question "\u2026" [--option "A" --option "B"]`).',
371
378
  " C. SPLIT if the post bundles several independent asks: `svamp issue add` one per piece (carry the",
372
379
  " relevant context), triage each, keep the original as the primary.",
373
380
  " D. AUTOMATION: if it implies running on a schedule/event, `svamp workflow add` it (instead of / in",
@@ -387,22 +394,33 @@ ${tail}
387
394
  ].join("\n"));
388
395
  } else if (topic === "work") {
389
396
  out([
390
- "WORK the ready issues in THIS session's backlog (your own + shared project items; leave other",
391
- "sessions' private items alone). List: svamp issue list --status ready --session <id>.",
397
+ "WORK the actionable issues in THIS session's backlog (your own + shared project items; leave other",
398
+ "sessions' private items alone). List them with: svamp issue list --session <id> (shows ready AND",
399
+ "in-progress \u2014 a RESUMED issue is in-progress, so a ready-only filter would hide it). An issue badged",
400
+ "\u21A9answered carries a fresh user answer \u2014 work that one FIRST.",
392
401
  " TRIAGE FIRST any \u25B3triage (untriaged) issue \u2014 see `svamp issue guide triage`.",
393
402
  " For each issue:",
394
- " 1. On pickup: svamp issue work <id> (status \u2192 in_progress; live status reflects the work).",
403
+ " 1. On pickup: `svamp issue show <id>` FIRST, then `svamp issue work <id>`. ALWAYS read show \u2014 its",
404
+ ' top surfaces any "\u21A9 The user answered your question \u2014 act on this: \u2026" line. Picking up via',
405
+ " `work` clears that badge, so read it before you do. (status \u2192 in_progress; live status updates.)",
395
406
  ' 2. As you go: leave substantive `svamp issue comment <id> "\u2026"` notes \u2014 decisions, takeaways, blockers.',
396
407
  " 3. Before closing: VERIFY with evidence \u2014 run its verify-cmd; for verify:agent cite the files/commit",
397
408
  " that resolve it in a summary comment. Post the summary, THEN `svamp issue close <id>`.",
398
409
  " NEVER close an issue that isn't genuinely resolved.",
399
410
  " CONVERGE TO MAIN: if an issue used an isolated branch/worktree, merge it back to `main` (and remove",
400
411
  " the worktree) BEFORE closing \u2014 never leave unmerged branches.",
401
- " BLOCKED ON THE USER? Do NOT churn. PAUSE the issue with the question (and options, if any):",
412
+ "",
413
+ " DEFAULT FORWARD \u2014 DON'T PAUSE TO ASK QUESTIONS DURING WORK. Clarifying/preference questions belong in",
414
+ " TRIAGE, not here. While working, if something is ambiguous, pick the most sensible default, PROCEED, and",
415
+ " DOCUMENT the assumption in a `svamp issue comment` (so the user can correct it). An over-eager pause",
416
+ " defeats the whole point of an automated loop.",
417
+ " PAUSE ONLY when you are genuinely BLOCKED and cannot proceed by any reasonable default \u2014 e.g. you need a",
418
+ " secret/credential/access only the user has, an irreversible or destructive action needs sign-off, or the",
419
+ " user EXPLICITLY reserved the decision. In that case:",
402
420
  ' svamp issue pause <id> --question "\u2026" [--option "A" --option "B"]',
403
- " A paused issue is EXCLUDED from `pending`, so the loop can STOP cleanly instead of re-running on",
404
- " something only the user can unblock. The user answers (the app renders the options as buttons) \u2192",
405
- ' the issue resumes (`svamp issue resume <id> [--answer "\u2026"]`) and you pick it up again.',
421
+ " A paused issue is EXCLUDED from `pending`, so the loop stops cleanly. The user answers (app renders the",
422
+ " options as buttons) \u2192 it resumes in-progress, badged \u21A9answered with the answer surfaced at the top of",
423
+ " `show` \u2014 re-pick it up and act on the answer (do NOT re-ask the same question).",
406
424
  " Keep going until `svamp issue pending --session <id>` is empty (paused issues don't count)."
407
425
  ].join("\n"));
408
426
  } else {
@@ -58,7 +58,7 @@ async function serviceExpose(args) {
58
58
  process.exit(1);
59
59
  }
60
60
  if (foreground) {
61
- const { runFrpcTunnel } = await import('./frpc-DMV0fZBM.mjs');
61
+ const { runFrpcTunnel } = await import('./frpc-H52IJYcb.mjs');
62
62
  await runFrpcTunnel(name, ports, void 0, {
63
63
  group,
64
64
  groupKey,
@@ -68,7 +68,7 @@ async function serviceExpose(args) {
68
68
  });
69
69
  return;
70
70
  }
71
- const { connectAndGetMachine } = await import('./commands-u68ODpBt.mjs');
71
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.mjs');
72
72
  const { server, machine } = await connectAndGetMachine();
73
73
  try {
74
74
  const status = await machine.tunnelStart({
@@ -123,7 +123,7 @@ async function serviceServe(args) {
123
123
  };
124
124
  process.on("SIGINT", cleanup);
125
125
  process.on("SIGTERM", cleanup);
126
- const { runFrpcTunnel } = await import('./frpc-DMV0fZBM.mjs');
126
+ const { runFrpcTunnel } = await import('./frpc-H52IJYcb.mjs');
127
127
  await runFrpcTunnel(name, [caddyPort]);
128
128
  } catch (err) {
129
129
  console.error(`Error serving directory: ${err.message}`);
@@ -132,7 +132,7 @@ async function serviceServe(args) {
132
132
  }
133
133
  async function serviceList(_args) {
134
134
  try {
135
- const { connectAndGetMachine } = await import('./commands-u68ODpBt.mjs');
135
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.mjs');
136
136
  const { server, machine } = await connectAndGetMachine();
137
137
  try {
138
138
  const tunnels = await machine.tunnelList({});
@@ -172,7 +172,7 @@ async function serviceDelete(args) {
172
172
  process.exit(1);
173
173
  }
174
174
  try {
175
- const { connectAndGetMachine } = await import('./commands-u68ODpBt.mjs');
175
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.mjs');
176
176
  const { server, machine } = await connectAndGetMachine();
177
177
  try {
178
178
  await machine.tunnelStop({ name });
@@ -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 { c as connectToHypha } from './run-BfgkuBEg.mjs';
4
+ import { c as connectToHypha } from './run-C9ZeF2iG.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-BfgkuBEg.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-C9ZeF2iG.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { P as resolveModel, a1 as describeMisconfiguration, a2 as buildMachineDeps } from './run-BfgkuBEg.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-DNLuglX3.mjs';
1
+ import { P as resolveModel, a1 as describeMisconfiguration, a2 as buildMachineDeps } from './run-C9ZeF2iG.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-cU4KDceu.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-BfgkuBEg.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-C9ZeF2iG.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.204";
2
+ var version = "0.2.206";
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 { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-BfgkuBEg.mjs';
1
+ import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-C9ZeF2iG.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -94,7 +94,7 @@ function issueRpc(cwd, params, deps = {}) {
94
94
  original = src;
95
95
  body = void 0;
96
96
  }
97
- return addIssue(root, {
97
+ const created = addIssue(root, {
98
98
  title,
99
99
  status: "ready",
100
100
  scope,
@@ -108,6 +108,11 @@ function issueRpc(cwd, params, deps = {}) {
108
108
  original,
109
109
  body
110
110
  });
111
+ if (params.rekick) {
112
+ const loopOwner = created.session || created.owner;
113
+ if (loopOwner) deps.rekickLoopOwner?.(String(loopOwner), { skipStalled: true });
114
+ }
115
+ return created;
111
116
  }
112
117
  case "comment": {
113
118
  const text = String(params.text || "");
@@ -125,6 +130,8 @@ function issueRpc(cwd, params, deps = {}) {
125
130
  case "resume": {
126
131
  const updated = resumeIssue(root, String(params.id), params.answer ? String(params.answer) : void 0);
127
132
  if (!updated) throw new Error(`Issue not found: ${params.id}`);
133
+ const loopOwner = updated.session || updated.owner;
134
+ if (loopOwner) deps.rekickLoopOwner?.(String(loopOwner));
128
135
  return updated;
129
136
  }
130
137
  case "status": {
@@ -140,7 +147,7 @@ function issueRpc(cwd, params, deps = {}) {
140
147
  return updated;
141
148
  }
142
149
  case "work": {
143
- const patch = { status: "in_progress" };
150
+ const patch = { status: "in_progress", answer: null };
144
151
  if (params.branch) patch.branch = String(params.branch);
145
152
  const worker = params.session || null;
146
153
  const cur = getIssue(root, String(params.id));
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot } from './run-BfgkuBEg.mjs';
1
+ import { m as resolveProjectRoot } from './run-C9ZeF2iG.mjs';
2
2
  import { g as getWorkflow, s as setWorkflowEnabled, r as removeWorkflow, a as saveWorkflow, b as rawWorkflow, l as listWorkflows } from './store-BTs0H_y0.mjs';
3
3
  import { g as getRun, l as listRuns, r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a3 as applyClaudeProxyEnv, a4 as composeSessionId, a5 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a6 as generateHookSettings } from './run-BfgkuBEg.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a3 as applyClaudeProxyEnv, a4 as composeSessionId, a5 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a6 as generateHookSettings } from './run-C9ZeF2iG.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';
@@ -2912,7 +2912,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2912
2912
  const tunnels = handlers.tunnels;
2913
2913
  if (!tunnels) throw new Error("Tunnel management not available");
2914
2914
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2915
- const { FrpcTunnel } = await import('./frpc-DMV0fZBM.mjs');
2915
+ const { FrpcTunnel } = await import('./frpc-H52IJYcb.mjs');
2916
2916
  const tunnel = new FrpcTunnel({
2917
2917
  name: params.name,
2918
2918
  ports: params.ports,
@@ -3359,7 +3359,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3359
3359
  }
3360
3360
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3361
3361
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3362
- const { toolsForRole } = await import('./sideband-DNLuglX3.mjs');
3362
+ const { toolsForRole } = await import('./sideband-cU4KDceu.mjs');
3363
3363
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3364
3364
  return fmt(r2);
3365
3365
  }
@@ -3458,7 +3458,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3458
3458
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3459
3459
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3460
3460
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3461
- const { queryCore } = await import('./commands-u68ODpBt.mjs');
3461
+ const { queryCore } = await import('./commands-BRmWVCNf.mjs');
3462
3462
  const timeout = c.reply?.timeout_sec || 120;
3463
3463
  let result;
3464
3464
  try {
@@ -10406,7 +10406,7 @@ function normalizeStatus(s) {
10406
10406
  if (s === "done") return "archived";
10407
10407
  return s === "ready" || s === "in_progress" || s === "paused" || s === "archived" ? s : "ready";
10408
10408
  }
10409
- const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "disposition", "triaged", "branch", "session", "owner", "original", "created", "closed", "question"];
10409
+ const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "disposition", "triaged", "branch", "session", "owner", "original", "created", "closed", "question", "answer"];
10410
10410
  function resolveProjectRoot(start = process.cwd()) {
10411
10411
  let dir = start;
10412
10412
  for (let i = 0; i < 40; i++) {
@@ -10468,6 +10468,7 @@ function parseIssue(content) {
10468
10468
  created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
10469
10469
  closed: fm.closed ?? null,
10470
10470
  question: fm.question && typeof fm.question === "object" ? fm.question : null,
10471
+ answer: typeof fm.answer === "string" && fm.answer.trim() ? fm.answer : null,
10471
10472
  body: (m[2] || "").trim() || void 0
10472
10473
  };
10473
10474
  }
@@ -10586,14 +10587,15 @@ Options: ${q.options.join(" \xB7 ")}` : "";
10586
10587
  } else {
10587
10588
  addComment(projectRoot, id, "\u23F8 Paused \u2014 waiting on your input.");
10588
10589
  }
10589
- return updateIssue(projectRoot, id, { status: "paused", question: q });
10590
+ return updateIssue(projectRoot, id, { status: "paused", question: q, answer: null });
10590
10591
  }
10591
10592
  function resumeIssue(projectRoot, id, answer) {
10592
10593
  const cur = getIssue(projectRoot, id);
10593
10594
  if (!cur) return null;
10594
- if (answer && answer.trim()) addComment(projectRoot, id, `\u25B6 Resumed \u2014 your answer: ${answer.trim()}`);
10595
+ const a = answer && answer.trim() ? answer.trim() : "";
10596
+ if (a) addComment(projectRoot, id, `\u25B6 Resumed \u2014 your answer: ${a}`);
10595
10597
  else addComment(projectRoot, id, "\u25B6 Resumed.");
10596
- return updateIssue(projectRoot, id, { status: "in_progress", question: null });
10598
+ return updateIssue(projectRoot, id, { status: "in_progress", question: null, answer: a || null });
10597
10599
  }
10598
10600
  function routeCrewVerdict(projectRoot, childSessionId, v) {
10599
10601
  const linked = listIssues(projectRoot, { includeArchived: true }).find((i) => i.disposition === "crew" && i.session === childSessionId);
@@ -11603,7 +11605,10 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
11603
11605
  announcedLoopStart = key;
11604
11606
  const iter = typeof s.iteration === "number" ? s.iteration : 0;
11605
11607
  const plural = iter === 1 ? "" : "s";
11606
- const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop stopped \u{1F6D1} \u2014 gave up after ${iter} iteration${plural}${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}.`;
11608
+ const gaveUpCfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
11609
+ const curMax = typeof gaveUpCfg.max_iterations === "number" ? gaveUpCfg.max_iterations : iter;
11610
+ const nextMax = Math.max(curMax * 2, iter + 10);
11611
+ const msg = s.phase === "done" ? `\u{1F501} Loop complete \u2705 \u2014 finished after ${iter} iteration${plural}.` : `\u{1F501} Loop paused \u{1F6D1} \u2014 hit its iteration limit (${iter}/${curMax}) and did NOT finish${s.gave_up_reason ? ` (${s.gave_up_reason})` : ""}. Resume & extend with: svamp session loop-resume ${sessionId} --max ${nextMax}`;
11607
11612
  sessionService.pushMessage(
11608
11613
  s.phase === "gave_up" ? { type: "message", message: msg, level: "warning" } : { type: "message", message: msg },
11609
11614
  "event"
@@ -11656,7 +11661,46 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
11656
11661
  }
11657
11662
  const task = cfg && typeof cfg.task === "string" && cfg.task.trim() ? cfg.task.trim() : void 0;
11658
11663
  const until = cfg && typeof cfg.until === "string" && cfg.until.trim() ? cfg.until.trim() : cfg && typeof cfg.criteria === "string" && cfg.criteria.trim() ? cfg.criteria.trim() : void 0;
11659
- if (cfg && typeof cfg === "object" && (task || until)) {
11664
+ const onlyLimit = cfg && typeof cfg === "object" && !task && !until && (typeof cfg.max_iterations === "number" || typeof cfg.max_rounds === "number" || cfg.budget);
11665
+ if (onlyLimit) {
11666
+ const existing = readLoopState(directory, sessionId);
11667
+ if (!existing) {
11668
+ sessionService.pushMessage({ type: "message", message: 'No loop to modify \u2014 start one with: svamp session loop <id> "<task>" --until "<criteria>" --max N', level: "warning" }, "event");
11669
+ } else {
11670
+ const newMax = typeof cfg.max_iterations === "number" ? cfg.max_iterations : typeof cfg.max_rounds === "number" ? cfg.max_rounds : void 0;
11671
+ try {
11672
+ const lcPath = join$1(getLoopDir(directory, sessionId), "loop.config.json");
11673
+ const lc = readLoopJson(directory, sessionId, "loop.config.json") || {};
11674
+ if (newMax != null) lc.max_iterations = newMax;
11675
+ const b = parseLoopBudget(cfg.budget);
11676
+ if (b) lc.budget = { ...lc.budget || {}, ...b };
11677
+ writeFileSync$1(lcPath, JSON.stringify(lc, null, 2));
11678
+ } catch {
11679
+ }
11680
+ const wasStopped = existing.active === false || existing.phase === "done" || existing.phase === "gave_up";
11681
+ if (wasStopped && existing.phase !== "cancelled") {
11682
+ try {
11683
+ const sp = join$1(getLoopDir(directory, sessionId), "loop-state.json");
11684
+ writeFileSync$1(sp, JSON.stringify({
11685
+ ...existing,
11686
+ active: true,
11687
+ phase: "continue",
11688
+ iteration: 0,
11689
+ completed_at: void 0,
11690
+ gave_up_reason: void 0,
11691
+ stall_hinted: false,
11692
+ resumed_at: Date.now()
11693
+ }, null, 2));
11694
+ } catch {
11695
+ }
11696
+ const eq = getMetadata().messageQueue || [];
11697
+ setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID$1(), text: "Continue the loop. Read LOOP.md and keep working toward the exit conditions until the Stop gate confirms completion.", displayText: "\u{1F501} Resuming loop", createdAt: Date.now() }] }));
11698
+ }
11699
+ onLoopActivated?.();
11700
+ sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop limit updated${newMax != null ? ` \u2192 max ${newMax} iterations` : ""}${wasStopped ? " \u2014 resuming" : ""}.` }, "event");
11701
+ logger.log(`[svampConfig] Loop limit modified (max=${newMax}, resumed=${wasStopped})`);
11702
+ }
11703
+ } else if (cfg && typeof cfg === "object" && (task || until)) {
11660
11704
  const oracle = typeof cfg.oracle === "string" && cfg.oracle.trim() ? cfg.oracle.trim() : void 0;
11661
11705
  const evaluator = cfg.evaluator !== false;
11662
11706
  const maxIterations = typeof cfg.max_iterations === "number" ? cfg.max_iterations : typeof cfg.max_rounds === "number" ? cfg.max_rounds : 20;
@@ -12137,7 +12181,7 @@ async function startDaemon(options) {
12137
12181
  saveExposedTunnels(list);
12138
12182
  }
12139
12183
  async function createExposedTunnel(spec) {
12140
- const { FrpcTunnel } = await import('./frpc-DMV0fZBM.mjs');
12184
+ const { FrpcTunnel } = await import('./frpc-H52IJYcb.mjs');
12141
12185
  const tunnel = new FrpcTunnel({
12142
12186
  name: spec.name,
12143
12187
  ports: spec.ports,
@@ -12157,7 +12201,7 @@ async function startDaemon(options) {
12157
12201
  return tunnel;
12158
12202
  }
12159
12203
  const tunnelRecreateState = /* @__PURE__ */ new Map();
12160
- const { ServeManager } = await import('./serveManager-DA9elK4m.mjs');
12204
+ const { ServeManager } = await import('./serveManager-Be-R7R8_.mjs');
12161
12205
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
12162
12206
  ensureAutoInstalledSkills(logger).catch(() => {
12163
12207
  });
@@ -12260,6 +12304,17 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
12260
12304
  } catch {
12261
12305
  }
12262
12306
  };
12307
+ const rekickLoopOwner = (sessionId, opts) => {
12308
+ try {
12309
+ const target = Array.from(pidToTrackedSession.values()).find((t) => t.svampSessionId === sessionId && !t.stopped);
12310
+ if (!target?.rekickLoop) {
12311
+ logger.log(`[issue] resume re-kick skipped \u2014 owner session ${sessionId.slice(0, 8)} not live on this machine`);
12312
+ return;
12313
+ }
12314
+ target.rekickLoop(opts);
12315
+ } catch {
12316
+ }
12317
+ };
12263
12318
  server.on("services_registered", () => {
12264
12319
  if (consecutiveHeartbeatFailures > 0) {
12265
12320
  logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
@@ -14038,11 +14093,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14038
14093
  });
14039
14094
  },
14040
14095
  onIssue: async (params) => {
14041
- const { issueRpc } = await import('./rpc-CoNLjY5x.mjs');
14042
- return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner });
14096
+ const { issueRpc } = await import('./rpc-BHUHF_Mv.mjs');
14097
+ return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14043
14098
  },
14044
14099
  onWorkflow: async (params) => {
14045
- const { workflowRpc } = await import('./rpc-DBpyHJUs.mjs');
14100
+ const { workflowRpc } = await import('./rpc-CFrVMETs.mjs');
14046
14101
  return workflowRpc(params?.cwd || directory, params || {});
14047
14102
  },
14048
14103
  onRipgrep: async (args, cwd) => {
@@ -14216,6 +14271,34 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14216
14271
  return claudeProcess || void 0;
14217
14272
  }
14218
14273
  };
14274
+ trackedSession.rekickLoop = (opts) => {
14275
+ if (trackedSession.stopped) return;
14276
+ const s = readLoopState(directory, sessionId);
14277
+ if (!s) return;
14278
+ if (s.phase === "cancelled") return;
14279
+ if (opts?.skipStalled && s.phase === "gave_up") return;
14280
+ if (typeof s.session_id === "string" && s.session_id !== sessionId) return;
14281
+ if (s.active === false || s.phase === "done" || s.phase === "gave_up") {
14282
+ try {
14283
+ const lp = join$1(getLoopDir(directory, sessionId), "loop-state.json");
14284
+ writeFileSync$1(lp, JSON.stringify({
14285
+ ...s,
14286
+ active: true,
14287
+ phase: "continue",
14288
+ iteration: 0,
14289
+ completed_at: void 0,
14290
+ gave_up_reason: void 0,
14291
+ stall_hinted: false,
14292
+ resumed_at: Date.now()
14293
+ // #0156: re-arm the stall hint
14294
+ }, null, 2));
14295
+ } catch {
14296
+ }
14297
+ }
14298
+ logger.log(`[Session ${sessionId}] resumed issue \u2192 reactivating + re-kicking loop`);
14299
+ enqueueLoopContinue();
14300
+ processMessageQueueRef?.();
14301
+ };
14219
14302
  pidToTrackedSession.set(randomUUID$1(), trackedSession);
14220
14303
  sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
14221
14304
  sessionService.updateMetadata(sessionMetadata);
@@ -14552,11 +14635,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14552
14635
  });
14553
14636
  },
14554
14637
  onIssue: async (params) => {
14555
- const { issueRpc } = await import('./rpc-CoNLjY5x.mjs');
14556
- return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner });
14638
+ const { issueRpc } = await import('./rpc-BHUHF_Mv.mjs');
14639
+ return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14557
14640
  },
14558
14641
  onWorkflow: async (params) => {
14559
- const { workflowRpc } = await import('./rpc-DBpyHJUs.mjs');
14642
+ const { workflowRpc } = await import('./rpc-CFrVMETs.mjs');
14560
14643
  return workflowRpc(params?.cwd || directory, params || {});
14561
14644
  },
14562
14645
  onRipgrep: async (args, cwd) => {
@@ -15330,7 +15413,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15330
15413
  const PING_TIMEOUT_MS = 15e3;
15331
15414
  const POST_RECONNECT_GRACE_MS = 2e4;
15332
15415
  const RECONNECT_JITTER_MS = 2500;
15333
- const { WorkflowScheduler } = await import('./scheduler-C1_HeBdn.mjs');
15416
+ const { WorkflowScheduler } = await import('./scheduler-ErjEISCv.mjs');
15334
15417
  const workflowScheduler = new WorkflowScheduler({
15335
15418
  projectRoots: () => {
15336
15419
  const dirs = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, y as cronMatches } from './run-BfgkuBEg.mjs';
1
+ import { m as resolveProjectRoot, y as cronMatches } from './run-C9ZeF2iG.mjs';
2
2
  import { l as listWorkflows, i as isWorkflowEnabled, w as workflowCrons } from './store-BTs0H_y0.mjs';
3
3
  import { r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-u68ODpBt.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.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-u68ODpBt.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.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-u68ODpBt.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.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-u68ODpBt.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.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-u68ODpBt.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-BRmWVCNf.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 { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-BfgkuBEg.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-C9ZeF2iG.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -733,7 +733,7 @@ class ServeManager {
733
733
  const mount = this.mounts.get(mountName);
734
734
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
735
735
  try {
736
- const { FrpcTunnel } = await import('./frpc-DMV0fZBM.mjs');
736
+ const { FrpcTunnel } = await import('./frpc-H52IJYcb.mjs');
737
737
  let tunnel;
738
738
  tunnel = new FrpcTunnel({
739
739
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-BfgkuBEg.mjs';
1
+ import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-C9ZeF2iG.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.204",
3
+ "version": "0.2.206",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",