svamp-cli 0.2.230 → 0.2.231
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.
- package/dist/{agentCommands-C__ZDnGy.mjs → agentCommands-V9V2kh0Q.mjs} +5 -5
- package/dist/{auth-B0FVB29Q.mjs → auth-DkjnoUxX.mjs} +1 -1
- package/dist/cli.mjs +61 -61
- package/dist/{commands-DZsbsqkr.mjs → commands-BGRljSMD.mjs} +1 -1
- package/dist/{commands-B-kINrK9.mjs → commands-BtaFWs9X.mjs} +2 -2
- package/dist/{commands-CSfdrg2h.mjs → commands-Bvdq1b3h.mjs} +10 -7
- package/dist/{commands-CvKU6_YX.mjs → commands-CQP12ojC.mjs} +1 -1
- package/dist/{commands-o-RWBC8N.mjs → commands-CdpWrKls.mjs} +2 -2
- package/dist/{commands-BNsxJYMS.mjs → commands-DBHDbRVm.mjs} +1 -1
- package/dist/{commands-C-9UCf3L.mjs → commands-qu4qKm64.mjs} +1 -1
- package/dist/{fleet-HW2op7Lu.mjs → fleet-BPNSihXR.mjs} +1 -1
- package/dist/{frpc-o7YfuAtf.mjs → frpc-CUBlxR7B.mjs} +1 -1
- package/dist/{headlessCli-C0ySAt1z.mjs → headlessCli-DxhjiAyU.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-B56skiR7.mjs → package-omNKWcir.mjs} +1 -1
- package/dist/{rpc-D_Suxu2Q.mjs → rpc-CPHva9-3.mjs} +1 -1
- package/dist/{rpc-RpVqDF8O.mjs → rpc-Cm2Uy3Hf.mjs} +1 -1
- package/dist/{run-DVqhiYP9.mjs → run-BzobWMYv.mjs} +335 -122
- package/dist/{run-D3SEkDfQ.mjs → run-DS_XkUa2.mjs} +1 -1
- package/dist/{scheduler-B2ZHfyig.mjs → scheduler-CwyAdWQa.mjs} +1 -1
- package/dist/{serveCommands-DZ3BWJTu.mjs → serveCommands-YMWshAkF.mjs} +5 -5
- package/dist/{serveManager-J2VwaLs1.mjs → serveManager-CkfMDcNQ.mjs} +2 -2
- package/dist/{sideband-BDwb3wnc.mjs → sideband-DDCUdyBK.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1967,6 +1967,33 @@ function savePersistedMachineMetadata(svampHomeDir, data) {
|
|
|
1967
1967
|
console.error("[HYPHA MACHINE] Failed to persist machine metadata:", err);
|
|
1968
1968
|
}
|
|
1969
1969
|
}
|
|
1970
|
+
function buildSessionTunnels(handlers, sessionId) {
|
|
1971
|
+
const specs = handlers.listExposedTunnels?.() ?? [];
|
|
1972
|
+
const tunnels = handlers.tunnels;
|
|
1973
|
+
const stateOf = (s) => {
|
|
1974
|
+
if (!s) return "reconnecting";
|
|
1975
|
+
if (s.probe && !s.probe.ok) return "failed";
|
|
1976
|
+
if (s.connected) return "connected";
|
|
1977
|
+
return s.restartAttempts > 0 ? "reconnecting" : "failed";
|
|
1978
|
+
};
|
|
1979
|
+
const rows = [];
|
|
1980
|
+
for (const spec of specs) {
|
|
1981
|
+
if (spec.sessionId !== sessionId) continue;
|
|
1982
|
+
const tunnel = tunnels?.get(spec.name);
|
|
1983
|
+
const status = tunnel ? tunnel.status : null;
|
|
1984
|
+
const urls = {};
|
|
1985
|
+
if (tunnel) for (const [port, url] of tunnel.getUrls()) urls[port] = url;
|
|
1986
|
+
rows.push({
|
|
1987
|
+
name: spec.name,
|
|
1988
|
+
ports: spec.ports,
|
|
1989
|
+
group: spec.group,
|
|
1990
|
+
live: !!tunnel,
|
|
1991
|
+
state: stateOf(status),
|
|
1992
|
+
urls: Object.keys(urls).length ? urls : void 0
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
return rows;
|
|
1996
|
+
}
|
|
1970
1997
|
async function registerMachineService(server, machineId, metadata, daemonState, handlers) {
|
|
1971
1998
|
let currentMetadata = { ...metadata };
|
|
1972
1999
|
let currentDaemonState = { ...daemonState };
|
|
@@ -2773,30 +2800,27 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2773
2800
|
// user can manage their own session's tasks without machine-level
|
|
2774
2801
|
// access.
|
|
2775
2802
|
/**
|
|
2776
|
-
* Snapshot of everything the session's Tasks panel needs to render
|
|
2803
|
+
* Snapshot of everything the session's Tasks panel needs to render.
|
|
2804
|
+
* Only svamp-MANAGED processes for this session are surfaced (#0192):
|
|
2777
2805
|
* - supervised: ProcessSpec/state filtered to this session
|
|
2778
|
-
* -
|
|
2806
|
+
* - tunnels: `svamp service expose` tunnels owned by this session
|
|
2807
|
+
* Unsupervised ps-tree descendants of the agent are no longer shown —
|
|
2808
|
+
* `detected` is kept as an empty array for wire back-compat with older
|
|
2809
|
+
* app clients that read `snap.detected`.
|
|
2779
2810
|
*/
|
|
2780
2811
|
sessionProcessList: async (params, context) => {
|
|
2781
2812
|
await authorizeSessionAccess(params.sessionId, "view", context);
|
|
2782
2813
|
const supervisor = handlers.supervisor;
|
|
2783
2814
|
const all = supervisor ? supervisor.list() : [];
|
|
2784
2815
|
const supervised = all.filter((p) => p.spec.sessionId === params.sessionId);
|
|
2816
|
+
const tunnels = buildSessionTunnels(handlers, params.sessionId);
|
|
2785
2817
|
const agentPid = handlers.getSessionPid?.(params.sessionId);
|
|
2786
|
-
|
|
2787
|
-
if (agentPid) {
|
|
2788
|
-
const supervisedPids = /* @__PURE__ */ new Set();
|
|
2789
|
-
for (const info of all) {
|
|
2790
|
-
if (info.state.pid) supervisedPids.add(info.state.pid);
|
|
2791
|
-
}
|
|
2792
|
-
detected = await detectDescendants(agentPid, supervisedPids);
|
|
2793
|
-
}
|
|
2794
|
-
return { supervised, detected, agentPid };
|
|
2818
|
+
return { supervised, tunnels, detected: [], agentPid };
|
|
2795
2819
|
},
|
|
2796
2820
|
/**
|
|
2797
|
-
* Cheap counts for the input-bar badge
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2821
|
+
* Cheap counts for the input-bar badge. Counts only svamp-managed
|
|
2822
|
+
* processes for this session (supervised specs + exposed tunnels);
|
|
2823
|
+
* `detected` stays 0 for wire back-compat.
|
|
2800
2824
|
*/
|
|
2801
2825
|
sessionProcessCounts: async (params, context) => {
|
|
2802
2826
|
await authorizeSessionAccess(params.sessionId, "view", context);
|
|
@@ -2804,15 +2828,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2804
2828
|
const all = supervisor ? supervisor.list() : [];
|
|
2805
2829
|
const supervised = all.filter((p) => p.spec.sessionId === params.sessionId);
|
|
2806
2830
|
const supervisedRunning = supervised.filter((p) => p.state.status === "running" || p.state.status === "starting").length;
|
|
2831
|
+
const tunnels = buildSessionTunnels(handlers, params.sessionId).length;
|
|
2807
2832
|
const agentPid = handlers.getSessionPid?.(params.sessionId);
|
|
2808
|
-
|
|
2809
|
-
if (agentPid) {
|
|
2810
|
-
const supervisedPids = /* @__PURE__ */ new Set();
|
|
2811
|
-
for (const info of all) if (info.state.pid) supervisedPids.add(info.state.pid);
|
|
2812
|
-
const list = await detectDescendants(agentPid, supervisedPids);
|
|
2813
|
-
detected = list.length;
|
|
2814
|
-
}
|
|
2815
|
-
return { supervised: supervised.length, supervisedRunning, detected, agentPid };
|
|
2833
|
+
return { supervised: supervised.length, supervisedRunning, tunnels, detected: 0, agentPid };
|
|
2816
2834
|
},
|
|
2817
2835
|
/** Logs for a supervised process, gated by session ownership. */
|
|
2818
2836
|
sessionProcessLogs: async (params, context) => {
|
|
@@ -2916,7 +2934,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2916
2934
|
const tunnels = handlers.tunnels;
|
|
2917
2935
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
2918
2936
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
2919
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
2937
|
+
const { FrpcTunnel } = await import('./frpc-CUBlxR7B.mjs');
|
|
2920
2938
|
const tunnel = new FrpcTunnel({
|
|
2921
2939
|
name: params.name,
|
|
2922
2940
|
ports: params.ports,
|
|
@@ -2943,6 +2961,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
2943
2961
|
healthCheckType: params.healthCheckType,
|
|
2944
2962
|
healthCheckPath: params.healthCheckPath,
|
|
2945
2963
|
healthCheckInterval: params.healthCheckInterval,
|
|
2964
|
+
sessionId: params.sessionId,
|
|
2946
2965
|
addedAt: Date.now()
|
|
2947
2966
|
});
|
|
2948
2967
|
const urls = {};
|
|
@@ -3363,7 +3382,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
3363
3382
|
}
|
|
3364
3383
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
3365
3384
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
3366
|
-
const { toolsForRole } = await import('./sideband-
|
|
3385
|
+
const { toolsForRole } = await import('./sideband-DDCUdyBK.mjs');
|
|
3367
3386
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
3368
3387
|
return fmt(r2);
|
|
3369
3388
|
}
|
|
@@ -3462,7 +3481,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
3462
3481
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
3463
3482
|
const callId = "call_" + Math.random().toString(16).slice(2, 12);
|
|
3464
3483
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
3465
|
-
const { queryCore } = await import('./commands-
|
|
3484
|
+
const { queryCore } = await import('./commands-qu4qKm64.mjs');
|
|
3466
3485
|
const timeout = c.reply?.timeout_sec || 120;
|
|
3467
3486
|
let result;
|
|
3468
3487
|
try {
|
|
@@ -10719,6 +10738,7 @@ ${o.task.trim()}`);
|
|
|
10719
10738
|
${o.until.trim()}`);
|
|
10720
10739
|
lines.push("");
|
|
10721
10740
|
lines.push("Use your tools (Read, Bash, Grep, Glob) to inspect the ACTUAL repository state \u2014 do NOT trust the transcript. Check the working tree, git log/diff, build/test output, and whether changes were actually built/published/deployed where the request implied it.");
|
|
10741
|
+
lines.push('IMPORTANT \u2014 the loop oracle only counts OPEN (ready/in_progress) issues, so an issue removed from the queue without genuine resolution is invisible to it. Run `svamp issue list --all` (or read `.svamp/issues/`) and confirm NO issue tied to this goal was sidelined merely to make the queue look empty. A PAUSED issue is FINE if it is genuinely blocked on a user answer OR clearly delegated/handed to another session (crew child or a named peer) \u2014 do NOT penalize those. Reject "done" ONLY if goal-relevant work is neither genuinely complete, nor legitimately blocked on the user, nor handed to another session.');
|
|
10722
10742
|
if (o.oracle) {
|
|
10723
10743
|
lines.push("");
|
|
10724
10744
|
lines.push(`The loop oracle is \`${o.oracle.trim()}\` and it currently PASSES${o.oracleOutput ? ` (output: ${o.oracleOutput.trim().slice(0, 400)})` : ""}. A green oracle here only means "no OPEN issues" \u2014 it does NOT mean each CLOSED issue actually works.`);
|
|
@@ -10753,6 +10773,54 @@ function buildGoalCondition(o) {
|
|
|
10753
10773
|
}
|
|
10754
10774
|
return parts.join(" ");
|
|
10755
10775
|
}
|
|
10776
|
+
const HOUR_MS = 36e5;
|
|
10777
|
+
function sumUsageTokens(usage) {
|
|
10778
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
10779
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
10780
|
+
return n(usage.input_tokens) + n(usage.output_tokens) + n(usage.cache_creation_input_tokens);
|
|
10781
|
+
}
|
|
10782
|
+
function accumulateLedger(prev, sample) {
|
|
10783
|
+
const base = prev && typeof prev === "object" ? { turns: prev.turns || 0, tokens: prev.tokens || 0, window: Array.isArray(prev.window) ? prev.window : [] } : { turns: 0, tokens: 0, window: [] };
|
|
10784
|
+
const addTurns = sample.turns > 0 ? sample.turns : 0;
|
|
10785
|
+
const addTokens = sample.tokens > 0 ? sample.tokens : 0;
|
|
10786
|
+
const window = [...base.window, [sample.ts, addTokens]].filter(([ts]) => sample.ts - ts < HOUR_MS);
|
|
10787
|
+
return { turns: base.turns + addTurns, tokens: base.tokens + addTokens, window };
|
|
10788
|
+
}
|
|
10789
|
+
function tokensInLastHour(ledger, now) {
|
|
10790
|
+
if (!ledger || !Array.isArray(ledger.window)) return 0;
|
|
10791
|
+
return ledger.window.filter(([ts]) => now - ts < HOUR_MS).reduce((s, [, t]) => s + (t > 0 ? t : 0), 0);
|
|
10792
|
+
}
|
|
10793
|
+
function checkLoopBudget(ledger, limits, now, startedAt) {
|
|
10794
|
+
const L = limits || {};
|
|
10795
|
+
const led = ledger || { turns: 0, tokens: 0, window: [] };
|
|
10796
|
+
const pos = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
10797
|
+
if (pos(L.max_iterations) && led.turns >= L.max_iterations) {
|
|
10798
|
+
return { exceeded: true, reason: `iteration budget reached (${led.turns}/${L.max_iterations} turns)` };
|
|
10799
|
+
}
|
|
10800
|
+
if (pos(L.max_runtime_sec) && pos(startedAt)) {
|
|
10801
|
+
const elapsed = (now - startedAt) / 1e3;
|
|
10802
|
+
if (elapsed >= L.max_runtime_sec) {
|
|
10803
|
+
return { exceeded: true, reason: `runtime budget reached (${Math.round(elapsed)}s/${L.max_runtime_sec}s)` };
|
|
10804
|
+
}
|
|
10805
|
+
}
|
|
10806
|
+
if (pos(L.max_tokens) && led.tokens >= L.max_tokens) {
|
|
10807
|
+
return { exceeded: true, reason: `token budget reached (${led.tokens}/${L.max_tokens} tokens)` };
|
|
10808
|
+
}
|
|
10809
|
+
if (pos(L.max_tokens_per_hour)) {
|
|
10810
|
+
const rate = tokensInLastHour(led, now);
|
|
10811
|
+
if (rate >= L.max_tokens_per_hour) {
|
|
10812
|
+
return { exceeded: true, reason: `token-rate budget reached (${rate}/${L.max_tokens_per_hour} tokens in the last hour)` };
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
return { exceeded: false };
|
|
10816
|
+
}
|
|
10817
|
+
const DEFAULT_LOOP_EVALUATOR_MODEL = "claude-haiku-4-5-20251001";
|
|
10818
|
+
function resolveEvaluatorModel(envValue) {
|
|
10819
|
+
if (envValue === void 0) return DEFAULT_LOOP_EVALUATOR_MODEL;
|
|
10820
|
+
const v = envValue.trim();
|
|
10821
|
+
if (!v || v.toLowerCase() === "inherit" || v.toLowerCase() === "default") return void 0;
|
|
10822
|
+
return v;
|
|
10823
|
+
}
|
|
10756
10824
|
function parseEvaluatorVerdict(text) {
|
|
10757
10825
|
if (!text) return null;
|
|
10758
10826
|
const candidates = [];
|
|
@@ -11636,6 +11704,7 @@ function writeSvampConfig(configPath, config) {
|
|
|
11636
11704
|
renameSync$1(tmpPath, configPath);
|
|
11637
11705
|
return content;
|
|
11638
11706
|
}
|
|
11707
|
+
const DEFAULT_LOOP_MAX_ITERATIONS = 1e3;
|
|
11639
11708
|
function getLoopDir(directory, sessionId) {
|
|
11640
11709
|
if (sessionId) {
|
|
11641
11710
|
const scoped = join$1(directory, ".svamp", sessionId, "loop");
|
|
@@ -11739,6 +11808,91 @@ function stripOldLoopHooks(directory) {
|
|
|
11739
11808
|
return false;
|
|
11740
11809
|
}
|
|
11741
11810
|
}
|
|
11811
|
+
function spawnHeadlessEvaluator(prompt, opts) {
|
|
11812
|
+
return new Promise((resolve2, reject) => {
|
|
11813
|
+
const evalArgs = [
|
|
11814
|
+
"--print",
|
|
11815
|
+
prompt,
|
|
11816
|
+
// Read-only reviewer (mirrors .claude/agents/loop-evaluator.md): it inspects real repo
|
|
11817
|
+
// state but must not mutate the working tree it's judging. The allowedTools whitelist
|
|
11818
|
+
// already excludes mutation tools; --disallowedTools is belt-and-suspenders. Kept to
|
|
11819
|
+
// Write/Edit only — MultiEdit/NotebookEdit aren't known tool names on some claude builds
|
|
11820
|
+
// and emit a noisy "matches no known tool" warning (#0191 canary).
|
|
11821
|
+
"--allowedTools",
|
|
11822
|
+
"Read",
|
|
11823
|
+
"Bash",
|
|
11824
|
+
"Grep",
|
|
11825
|
+
"Glob",
|
|
11826
|
+
"--disallowedTools",
|
|
11827
|
+
"Write",
|
|
11828
|
+
"Edit",
|
|
11829
|
+
"--permission-mode",
|
|
11830
|
+
"bypassPermissions",
|
|
11831
|
+
"--output-format",
|
|
11832
|
+
"stream-json",
|
|
11833
|
+
"--verbose",
|
|
11834
|
+
...opts.model ? ["--model", opts.model] : []
|
|
11835
|
+
];
|
|
11836
|
+
const evalEnv = { ...process.env };
|
|
11837
|
+
applyClaudeProxyEnv(evalEnv);
|
|
11838
|
+
delete evalEnv.CLAUDECODE;
|
|
11839
|
+
let proc;
|
|
11840
|
+
try {
|
|
11841
|
+
proc = spawn$1("claude", evalArgs, { cwd: opts.cwd, env: evalEnv, stdio: ["pipe", "pipe", "pipe"] });
|
|
11842
|
+
} catch (e) {
|
|
11843
|
+
reject(e);
|
|
11844
|
+
return;
|
|
11845
|
+
}
|
|
11846
|
+
let out = "", buf = "", stderr = "", settled = false;
|
|
11847
|
+
const timer = setTimeout(() => {
|
|
11848
|
+
if (settled) return;
|
|
11849
|
+
settled = true;
|
|
11850
|
+
try {
|
|
11851
|
+
proc.kill("SIGTERM");
|
|
11852
|
+
} catch {
|
|
11853
|
+
}
|
|
11854
|
+
reject(new Error("evaluator timeout"));
|
|
11855
|
+
}, opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 3e5);
|
|
11856
|
+
proc.stderr?.on("data", (c) => {
|
|
11857
|
+
stderr += c.toString();
|
|
11858
|
+
});
|
|
11859
|
+
proc.stdout.on("data", (chunk) => {
|
|
11860
|
+
buf += chunk.toString();
|
|
11861
|
+
let nl;
|
|
11862
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
11863
|
+
const line = buf.slice(0, nl);
|
|
11864
|
+
buf = buf.slice(nl + 1);
|
|
11865
|
+
if (!line.trim()) continue;
|
|
11866
|
+
try {
|
|
11867
|
+
const m = JSON.parse(line);
|
|
11868
|
+
if (m.type === "assistant" && m.message?.content) {
|
|
11869
|
+
for (const b of m.message.content) if (b.type === "text" && b.text) out += b.text;
|
|
11870
|
+
} else if (m.type === "result") {
|
|
11871
|
+
if (settled) continue;
|
|
11872
|
+
settled = true;
|
|
11873
|
+
clearTimeout(timer);
|
|
11874
|
+
if (m.is_error) reject(new Error(String(m.result || "evaluator error")));
|
|
11875
|
+
else resolve2(out || String(m.result || ""));
|
|
11876
|
+
}
|
|
11877
|
+
} catch {
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
});
|
|
11881
|
+
proc.on("exit", (code) => {
|
|
11882
|
+
if (settled) return;
|
|
11883
|
+
settled = true;
|
|
11884
|
+
clearTimeout(timer);
|
|
11885
|
+
if (code === 0) resolve2(out);
|
|
11886
|
+
else reject(new Error(stderr.trim() || `evaluator exited with code ${code}`));
|
|
11887
|
+
});
|
|
11888
|
+
proc.on("error", (e) => {
|
|
11889
|
+
if (settled) return;
|
|
11890
|
+
settled = true;
|
|
11891
|
+
clearTimeout(timer);
|
|
11892
|
+
reject(e);
|
|
11893
|
+
});
|
|
11894
|
+
});
|
|
11895
|
+
}
|
|
11742
11896
|
function deactivateLoop(directory, sessionId) {
|
|
11743
11897
|
try {
|
|
11744
11898
|
const p = join$1(getLoopDir(directory, sessionId), "loop-state.json");
|
|
@@ -11939,6 +12093,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
11939
12093
|
} catch {
|
|
11940
12094
|
}
|
|
11941
12095
|
const wasStopped = existing.active === false || existing.phase === "done" || existing.phase === "gave_up" || existing.phase === "cancelled";
|
|
12096
|
+
const bumpedBudget = parseLoopBudget(cfg.budget);
|
|
11942
12097
|
if (wasStopped) {
|
|
11943
12098
|
try {
|
|
11944
12099
|
const sp = join$1(getLoopDir(directory, sessionId), "loop-state.json");
|
|
@@ -11947,6 +12102,8 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
11947
12102
|
active: true,
|
|
11948
12103
|
phase: "continue",
|
|
11949
12104
|
iteration: 0,
|
|
12105
|
+
...newMax != null ? { max_iterations: newMax } : {},
|
|
12106
|
+
...bumpedBudget ? { budget: { ...existing.budget || {}, ...bumpedBudget } } : {},
|
|
11950
12107
|
completed_at: void 0,
|
|
11951
12108
|
gave_up_reason: void 0,
|
|
11952
12109
|
stall_hinted: false,
|
|
@@ -11958,18 +12115,33 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
11958
12115
|
setMetadata((m) => ({ ...m, messageQueue: [...eq, { id: randomUUID$1(), text: loopResumeMessage(directory, sessionId), displayText: "\u{1F501} Resuming loop", createdAt: Date.now() }] }));
|
|
11959
12116
|
onLoopActivated?.();
|
|
11960
12117
|
}
|
|
12118
|
+
if (!wasStopped && (newMax != null || bumpedBudget)) {
|
|
12119
|
+
try {
|
|
12120
|
+
const cur = readLoopState(directory, sessionId);
|
|
12121
|
+
if (cur) writeGoalLoopState(directory, sessionId, {
|
|
12122
|
+
...cur,
|
|
12123
|
+
...newMax != null ? { max_iterations: newMax } : {},
|
|
12124
|
+
...bumpedBudget ? { budget: { ...cur.budget || {}, ...bumpedBudget } } : {}
|
|
12125
|
+
});
|
|
12126
|
+
} catch {
|
|
12127
|
+
}
|
|
12128
|
+
}
|
|
11961
12129
|
sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop limit updated${newMax != null ? ` \u2192 max ${newMax} iterations` : ""}${wasStopped ? " \u2014 resuming" : ""}.` }, "event");
|
|
11962
12130
|
logger.log(`[svampConfig] Loop limit modified (max=${newMax}, resumed=${wasStopped}, restarted=${wasStopped})`);
|
|
11963
12131
|
}
|
|
11964
12132
|
} else if (cfg && typeof cfg === "object" && (task || until)) {
|
|
11965
12133
|
const oracle = typeof cfg.oracle === "string" && cfg.oracle.trim() ? cfg.oracle.trim() : void 0;
|
|
11966
|
-
const
|
|
11967
|
-
const
|
|
12134
|
+
const explicitMax = typeof cfg.max_iterations === "number" ? cfg.max_iterations : typeof cfg.max_rounds === "number" ? cfg.max_rounds : void 0;
|
|
12135
|
+
const hasExplicitMax = typeof explicitMax === "number" && explicitMax > 0;
|
|
12136
|
+
const hardMax = hasExplicitMax ? explicitMax : DEFAULT_LOOP_MAX_ITERATIONS;
|
|
12137
|
+
const softTurnCap = hasExplicitMax ? explicitMax : 20;
|
|
12138
|
+
const condition = buildGoalCondition({ task, until, oracle, maxIterations: softTurnCap });
|
|
12139
|
+
const budget = parseLoopBudget(cfg.budget);
|
|
11968
12140
|
try {
|
|
11969
12141
|
stripOldLoopHooks(directory);
|
|
11970
12142
|
} catch {
|
|
11971
12143
|
}
|
|
11972
|
-
writeGoalLoopState(directory, sessionId, { active: true, phase: "running", iteration: 0, task: condition, goal_task: task, until, oracle, max_iterations:
|
|
12144
|
+
writeGoalLoopState(directory, sessionId, { active: true, phase: "running", iteration: 0, task: condition, goal_task: task, until, oracle, max_iterations: hardMax, budget, started_at: Date.now(), holds: 0, ledger: { turns: 0, tokens: 0, window: [] } });
|
|
11973
12145
|
const existingQueue = getMetadata().messageQueue || [];
|
|
11974
12146
|
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID$1(), text: `/goal ${condition}`, displayText: `\u{1F3AF} Goal: ${(task || until || "").slice(0, 100)}`, createdAt: Date.now() }] }));
|
|
11975
12147
|
onLoopActivated?.();
|
|
@@ -12418,7 +12590,7 @@ async function startDaemon(options) {
|
|
|
12418
12590
|
saveExposedTunnels(list);
|
|
12419
12591
|
}
|
|
12420
12592
|
async function createExposedTunnel(spec) {
|
|
12421
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
12593
|
+
const { FrpcTunnel } = await import('./frpc-CUBlxR7B.mjs');
|
|
12422
12594
|
const tunnel = new FrpcTunnel({
|
|
12423
12595
|
name: spec.name,
|
|
12424
12596
|
ports: spec.ports,
|
|
@@ -12438,7 +12610,7 @@ async function startDaemon(options) {
|
|
|
12438
12610
|
return tunnel;
|
|
12439
12611
|
}
|
|
12440
12612
|
const tunnelRecreateState = /* @__PURE__ */ new Map();
|
|
12441
|
-
const { ServeManager } = await import('./serveManager-
|
|
12613
|
+
const { ServeManager } = await import('./serveManager-CkfMDcNQ.mjs');
|
|
12442
12614
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
12443
12615
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
12444
12616
|
});
|
|
@@ -13069,87 +13241,7 @@ ${parts.join("\n")}`);
|
|
|
13069
13241
|
};
|
|
13070
13242
|
sessionService.updateMetadata(sessionMetadata);
|
|
13071
13243
|
};
|
|
13072
|
-
const runHeadlessEvaluator = (prompt) =>
|
|
13073
|
-
const evalArgs = [
|
|
13074
|
-
"--print",
|
|
13075
|
-
prompt,
|
|
13076
|
-
// Read-only reviewer (mirrors .claude/agents/loop-evaluator.md): it inspects
|
|
13077
|
-
// real repo state but must not mutate the working tree it's judging.
|
|
13078
|
-
"--allowedTools",
|
|
13079
|
-
"Read",
|
|
13080
|
-
"Bash",
|
|
13081
|
-
"Grep",
|
|
13082
|
-
"Glob",
|
|
13083
|
-
"--disallowedTools",
|
|
13084
|
-
"Write",
|
|
13085
|
-
"Edit",
|
|
13086
|
-
"MultiEdit",
|
|
13087
|
-
"NotebookEdit",
|
|
13088
|
-
"--permission-mode",
|
|
13089
|
-
"bypassPermissions",
|
|
13090
|
-
"--output-format",
|
|
13091
|
-
"stream-json",
|
|
13092
|
-
"--verbose"
|
|
13093
|
-
];
|
|
13094
|
-
const evalEnv = { ...process.env };
|
|
13095
|
-
applyClaudeProxyEnv(evalEnv);
|
|
13096
|
-
delete evalEnv.CLAUDECODE;
|
|
13097
|
-
let proc;
|
|
13098
|
-
try {
|
|
13099
|
-
proc = spawn$1("claude", evalArgs, { cwd: directory, env: evalEnv, stdio: ["pipe", "pipe", "pipe"] });
|
|
13100
|
-
} catch (e) {
|
|
13101
|
-
reject(e);
|
|
13102
|
-
return;
|
|
13103
|
-
}
|
|
13104
|
-
let out = "", buf = "", stderr = "", settled = false;
|
|
13105
|
-
const timer = setTimeout(() => {
|
|
13106
|
-
if (settled) return;
|
|
13107
|
-
settled = true;
|
|
13108
|
-
try {
|
|
13109
|
-
proc.kill("SIGTERM");
|
|
13110
|
-
} catch {
|
|
13111
|
-
}
|
|
13112
|
-
reject(new Error("evaluator timeout"));
|
|
13113
|
-
}, 3e5);
|
|
13114
|
-
proc.stderr?.on("data", (c) => {
|
|
13115
|
-
stderr += c.toString();
|
|
13116
|
-
});
|
|
13117
|
-
proc.stdout.on("data", (chunk) => {
|
|
13118
|
-
buf += chunk.toString();
|
|
13119
|
-
let nl;
|
|
13120
|
-
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
13121
|
-
const line = buf.slice(0, nl);
|
|
13122
|
-
buf = buf.slice(nl + 1);
|
|
13123
|
-
if (!line.trim()) continue;
|
|
13124
|
-
try {
|
|
13125
|
-
const m = JSON.parse(line);
|
|
13126
|
-
if (m.type === "assistant" && m.message?.content) {
|
|
13127
|
-
for (const b of m.message.content) if (b.type === "text" && b.text) out += b.text;
|
|
13128
|
-
} else if (m.type === "result") {
|
|
13129
|
-
if (settled) continue;
|
|
13130
|
-
settled = true;
|
|
13131
|
-
clearTimeout(timer);
|
|
13132
|
-
if (m.is_error) reject(new Error(String(m.result || "evaluator error")));
|
|
13133
|
-
else resolve2(out || String(m.result || ""));
|
|
13134
|
-
}
|
|
13135
|
-
} catch {
|
|
13136
|
-
}
|
|
13137
|
-
}
|
|
13138
|
-
});
|
|
13139
|
-
proc.on("exit", (code) => {
|
|
13140
|
-
if (settled) return;
|
|
13141
|
-
settled = true;
|
|
13142
|
-
clearTimeout(timer);
|
|
13143
|
-
if (code === 0) resolve2(out);
|
|
13144
|
-
else reject(new Error(stderr.trim() || `evaluator exited with code ${code}`));
|
|
13145
|
-
});
|
|
13146
|
-
proc.on("error", (e) => {
|
|
13147
|
-
if (settled) return;
|
|
13148
|
-
settled = true;
|
|
13149
|
-
clearTimeout(timer);
|
|
13150
|
-
reject(e);
|
|
13151
|
-
});
|
|
13152
|
-
});
|
|
13244
|
+
const runHeadlessEvaluator = (prompt) => spawnHeadlessEvaluator(prompt, { cwd: directory, model: resolveEvaluatorModel(process.env.SVAMP_LOOP_EVALUATOR_MODEL) });
|
|
13153
13245
|
let goalVerifyInFlight = false;
|
|
13154
13246
|
const verifyGoalCompletion = async (ls) => {
|
|
13155
13247
|
if (goalVerifyInFlight) return;
|
|
@@ -13180,6 +13272,8 @@ ${parts.join("\n")}`);
|
|
|
13180
13272
|
log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
|
|
13181
13273
|
}
|
|
13182
13274
|
);
|
|
13275
|
+
const ledger = ls.ledger;
|
|
13276
|
+
const budget = ls.budget;
|
|
13183
13277
|
if (result.action === "rekick") {
|
|
13184
13278
|
const cond = ls.task || "";
|
|
13185
13279
|
writeGoalLoopState(directory, sessionId, {
|
|
@@ -13191,18 +13285,20 @@ ${parts.join("\n")}`);
|
|
|
13191
13285
|
until: ls.until,
|
|
13192
13286
|
oracle: oracleCmd,
|
|
13193
13287
|
max_iterations: ls.max_iterations,
|
|
13288
|
+
budget,
|
|
13194
13289
|
started_at: startedAt,
|
|
13195
|
-
holds: holds + 1
|
|
13290
|
+
holds: holds + 1,
|
|
13291
|
+
ledger
|
|
13196
13292
|
});
|
|
13197
13293
|
const guidance = result.guidance.replace(/\s+/g, " ").slice(0, 1200);
|
|
13198
13294
|
sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop not done \u2014 independent review: ${result.reason}`, level: "warning" }, "event");
|
|
13199
13295
|
enqueueLoopMessage(`/goal ${cond} \u2014 INDEPENDENT REVIEW FEEDBACK (resolve before declaring done): ${guidance}`, "\u{1F501} Re-verifying loop");
|
|
13200
13296
|
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
13201
13297
|
} else if (result.action === "gave_up") {
|
|
13202
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: result.reason, holds });
|
|
13298
|
+
writeGoalLoopState(directory, sessionId, { active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: result.reason, holds, ledger });
|
|
13203
13299
|
checkSvampConfig?.();
|
|
13204
13300
|
} else {
|
|
13205
|
-
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds });
|
|
13301
|
+
writeGoalLoopState(directory, sessionId, { active: false, phase: "done", completed_at: Date.now(), holds, ledger });
|
|
13206
13302
|
checkSvampConfig?.();
|
|
13207
13303
|
}
|
|
13208
13304
|
} catch (e) {
|
|
@@ -13663,9 +13759,30 @@ ${parts.join("\n")}`);
|
|
|
13663
13759
|
try {
|
|
13664
13760
|
const ls = readLoopState(directory, sessionId);
|
|
13665
13761
|
if (ls && ls.engine === "goal" && ls.active !== false && ls.phase !== "done" && ls.phase !== "gave_up" && ls.phase !== "cancelled") {
|
|
13666
|
-
|
|
13667
|
-
|
|
13762
|
+
const now = Date.now();
|
|
13763
|
+
const ledger = accumulateLedger(ls.ledger, {
|
|
13764
|
+
turns: Number(msg.num_turns) || 0,
|
|
13765
|
+
tokens: sumUsageTokens(msg.usage),
|
|
13766
|
+
ts: now
|
|
13767
|
+
});
|
|
13768
|
+
ls.ledger = ledger;
|
|
13769
|
+
const startedAt = typeof ls.started_at === "number" ? ls.started_at : typeof ls.resumed_at === "number" ? ls.resumed_at : void 0;
|
|
13770
|
+
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false } : checkLoopBudget(ledger, { max_iterations: ls.max_iterations, ...ls.budget || {} }, now, startedAt);
|
|
13771
|
+
if (budgetCheck.exceeded) {
|
|
13772
|
+
writeGoalLoopState(directory, sessionId, {
|
|
13773
|
+
...ls,
|
|
13774
|
+
active: false,
|
|
13775
|
+
phase: "gave_up",
|
|
13776
|
+
completed_at: now,
|
|
13777
|
+
gave_up_reason: `resource budget exhausted \u2014 ${budgetCheck.reason}`,
|
|
13778
|
+
ledger
|
|
13779
|
+
});
|
|
13780
|
+
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${budgetCheck.reason}. Resume & extend with: svamp session loop-resume ${sessionId} --max <N>`, level: "warning" }, "event");
|
|
13781
|
+
checkSvampConfig?.();
|
|
13782
|
+
} else if (process.env.SVAMP_LOOP_VERIFY === "0") {
|
|
13783
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: now, ledger });
|
|
13668
13784
|
} else {
|
|
13785
|
+
writeGoalLoopState(directory, sessionId, { ...ls, ledger });
|
|
13669
13786
|
void verifyGoalCompletion(ls);
|
|
13670
13787
|
}
|
|
13671
13788
|
}
|
|
@@ -14502,11 +14619,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
14502
14619
|
});
|
|
14503
14620
|
},
|
|
14504
14621
|
onIssue: async (params) => {
|
|
14505
|
-
const { issueRpc } = await import('./rpc-
|
|
14622
|
+
const { issueRpc } = await import('./rpc-Cm2Uy3Hf.mjs');
|
|
14506
14623
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
14507
14624
|
},
|
|
14508
14625
|
onWorkflow: async (params) => {
|
|
14509
|
-
const { workflowRpc } = await import('./rpc-
|
|
14626
|
+
const { workflowRpc } = await import('./rpc-CPHva9-3.mjs');
|
|
14510
14627
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
14511
14628
|
},
|
|
14512
14629
|
onRipgrep: async (args, cwd) => {
|
|
@@ -15039,11 +15156,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
15039
15156
|
});
|
|
15040
15157
|
},
|
|
15041
15158
|
onIssue: async (params) => {
|
|
15042
|
-
const { issueRpc } = await import('./rpc-
|
|
15159
|
+
const { issueRpc } = await import('./rpc-Cm2Uy3Hf.mjs');
|
|
15043
15160
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
15044
15161
|
},
|
|
15045
15162
|
onWorkflow: async (params) => {
|
|
15046
|
-
const { workflowRpc } = await import('./rpc-
|
|
15163
|
+
const { workflowRpc } = await import('./rpc-CPHva9-3.mjs');
|
|
15047
15164
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
15048
15165
|
},
|
|
15049
15166
|
onRipgrep: async (args, cwd) => {
|
|
@@ -15211,6 +15328,100 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
15211
15328
|
}
|
|
15212
15329
|
let acpStopped = false;
|
|
15213
15330
|
let acpBackendReady = false;
|
|
15331
|
+
let acpVerifyInFlight = false;
|
|
15332
|
+
const dispatchAcpPrompt = (text, displayText) => {
|
|
15333
|
+
if (acpStopped) return;
|
|
15334
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "running" };
|
|
15335
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
15336
|
+
sessionService.sendKeepAlive(true);
|
|
15337
|
+
sessionService.pushMessage(displayText, "user");
|
|
15338
|
+
agentBackend.sendPrompt(sessionId, text).catch((err) => {
|
|
15339
|
+
logger.error(`[Session ${sessionId}] ACP loop re-kick send failed: ${err.message}`);
|
|
15340
|
+
if (!acpStopped) {
|
|
15341
|
+
sessionMetadata = { ...sessionMetadata, lifecycleState: "idle" };
|
|
15342
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
15343
|
+
sessionService.sendSessionEnd();
|
|
15344
|
+
}
|
|
15345
|
+
});
|
|
15346
|
+
};
|
|
15347
|
+
const verifyAcpLoopCompletion = async () => {
|
|
15348
|
+
if (acpVerifyInFlight || acpStopped) return;
|
|
15349
|
+
const ls = readLoopState(directory, sessionId);
|
|
15350
|
+
if (!ls || ls.engine !== "goal" || ls.active === false || ls.phase === "done" || ls.phase === "gave_up" || ls.phase === "cancelled") return;
|
|
15351
|
+
if (!isLoopActiveForSession(directory, sessionId)) return;
|
|
15352
|
+
acpVerifyInFlight = true;
|
|
15353
|
+
const now = Date.now();
|
|
15354
|
+
const projectRoot = resolveProjectRoot(directory);
|
|
15355
|
+
const startedAt = typeof ls.started_at === "number" ? ls.started_at : typeof ls.resumed_at === "number" ? ls.resumed_at : void 0;
|
|
15356
|
+
const oracleCmd = typeof ls.oracle === "string" && ls.oracle.trim() ? ls.oracle.trim() : void 0;
|
|
15357
|
+
const ledger = accumulateLedger(ls.ledger, { turns: 1, tokens: 0, ts: now });
|
|
15358
|
+
ls.ledger = ledger;
|
|
15359
|
+
const holds = typeof ls.holds === "number" ? ls.holds : 0;
|
|
15360
|
+
const runOracle = async () => {
|
|
15361
|
+
if (oracleCmd) {
|
|
15362
|
+
return await new Promise((resolve2) => {
|
|
15363
|
+
exec$1(oracleCmd, { cwd: directory, timeout: 12e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
15364
|
+
if (err) resolve2({ ok: false, output: (String(stdout || "") + String(stderr || "")).slice(-500) });
|
|
15365
|
+
else resolve2({ ok: true, output: "" });
|
|
15366
|
+
});
|
|
15367
|
+
});
|
|
15368
|
+
}
|
|
15369
|
+
const pending = backlogOraclePending(projectRoot, sessionId);
|
|
15370
|
+
return { ok: pending.length === 0, output: pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues." };
|
|
15371
|
+
};
|
|
15372
|
+
try {
|
|
15373
|
+
const budgetCheck = process.env.SVAMP_LOOP_BUDGET === "0" ? { exceeded: false } : checkLoopBudget(ledger, { max_iterations: ls.max_iterations, ...ls.budget || {} }, now, startedAt);
|
|
15374
|
+
if (budgetCheck.exceeded) {
|
|
15375
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: now, gave_up_reason: `resource budget exhausted \u2014 ${budgetCheck.reason}`, ledger });
|
|
15376
|
+
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${budgetCheck.reason}. Resume & extend with: svamp session loop-resume ${sessionId} --max <N>`, level: "warning" }, "event");
|
|
15377
|
+
checkSvampConfig?.();
|
|
15378
|
+
return;
|
|
15379
|
+
}
|
|
15380
|
+
const oracle = await runOracle();
|
|
15381
|
+
if (!oracle.ok) {
|
|
15382
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "continue", ledger });
|
|
15383
|
+
dispatchAcpPrompt(`Continue toward the goal: ${ls.goal_task || ls.until || "the task"}. Not done yet \u2014 the loop oracle still reports pending work:
|
|
15384
|
+
${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
15385
|
+
return;
|
|
15386
|
+
}
|
|
15387
|
+
if (process.env.SVAMP_LOOP_VERIFY === "0") {
|
|
15388
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: now, ledger });
|
|
15389
|
+
checkSvampConfig?.();
|
|
15390
|
+
return;
|
|
15391
|
+
}
|
|
15392
|
+
sessionService.pushMessage({ type: "message", message: "\u{1F50D} Verifying loop completion (independent review)\u2026" }, "event");
|
|
15393
|
+
const result = await runLoopVerification(
|
|
15394
|
+
{ task: ls.goal_task || ls.task, until: ls.until, oracle: oracleCmd, startedAt, holds, maxHolds: 3 },
|
|
15395
|
+
{
|
|
15396
|
+
projectRoot,
|
|
15397
|
+
runOracle: async () => ({ ok: true, output: oracle.output }),
|
|
15398
|
+
runEvaluator: (prompt) => spawnHeadlessEvaluator(prompt, { cwd: directory, model: resolveEvaluatorModel(process.env.SVAMP_LOOP_EVALUATOR_MODEL) }),
|
|
15399
|
+
log: (m) => logger.log(`[Session ${sessionId}] ${m}`)
|
|
15400
|
+
}
|
|
15401
|
+
);
|
|
15402
|
+
if (result.action === "rekick") {
|
|
15403
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "continue", holds: holds + 1, started_at: startedAt, ledger });
|
|
15404
|
+
const guidance = result.guidance.replace(/\s+/g, " ").slice(0, 1200);
|
|
15405
|
+
sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop not done \u2014 independent review: ${result.reason}`, level: "warning" }, "event");
|
|
15406
|
+
dispatchAcpPrompt(`Continue toward the goal: ${ls.goal_task || ls.until || "the task"}. INDEPENDENT REVIEW FEEDBACK (resolve before declaring done): ${guidance}`, "\u{1F501} Re-verifying loop");
|
|
15407
|
+
} else if (result.action === "gave_up") {
|
|
15408
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "gave_up", completed_at: Date.now(), gave_up_reason: result.reason, holds, ledger });
|
|
15409
|
+
checkSvampConfig?.();
|
|
15410
|
+
} else {
|
|
15411
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger });
|
|
15412
|
+
checkSvampConfig?.();
|
|
15413
|
+
}
|
|
15414
|
+
} catch (e) {
|
|
15415
|
+
logger.log(`[Session ${sessionId}] verifyAcpLoopCompletion error \u2014 failing open (done): ${e?.message || e}`);
|
|
15416
|
+
try {
|
|
15417
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), ledger });
|
|
15418
|
+
checkSvampConfig?.();
|
|
15419
|
+
} catch {
|
|
15420
|
+
}
|
|
15421
|
+
} finally {
|
|
15422
|
+
acpVerifyInFlight = false;
|
|
15423
|
+
}
|
|
15424
|
+
};
|
|
15214
15425
|
const onTurnEnd = (lastAssistantText) => {
|
|
15215
15426
|
if (acpStopped) return;
|
|
15216
15427
|
insideOnTurnEnd = true;
|
|
@@ -15246,6 +15457,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
15246
15457
|
sessionService.sendSessionEnd();
|
|
15247
15458
|
}
|
|
15248
15459
|
});
|
|
15460
|
+
} else if (isLoopActiveForSession(directory, sessionId)) {
|
|
15461
|
+
void verifyAcpLoopCompletion();
|
|
15249
15462
|
}
|
|
15250
15463
|
} finally {
|
|
15251
15464
|
insideOnTurnEnd = false;
|
|
@@ -15521,7 +15734,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
15521
15734
|
sharingNotificationSync,
|
|
15522
15735
|
persistExposedTunnel,
|
|
15523
15736
|
forgetExposedTunnel,
|
|
15524
|
-
listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, addedAt: t.addedAt }))
|
|
15737
|
+
listExposedTunnels: () => loadExposedTunnels().map((t) => ({ name: t.name, ports: t.ports, group: t.group, sessionId: t.sessionId, addedAt: t.addedAt }))
|
|
15525
15738
|
});
|
|
15526
15739
|
const machineService = await registerMachineService(
|
|
15527
15740
|
server,
|
|
@@ -15817,7 +16030,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
15817
16030
|
const PING_TIMEOUT_MS = 15e3;
|
|
15818
16031
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
15819
16032
|
const RECONNECT_JITTER_MS = 2500;
|
|
15820
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
16033
|
+
const { WorkflowScheduler } = await import('./scheduler-CwyAdWQa.mjs');
|
|
15821
16034
|
const workflowScheduler = new WorkflowScheduler({
|
|
15822
16035
|
projectRoots: () => {
|
|
15823
16036
|
const dirs = /* @__PURE__ */ new Set();
|