svamp-cli 0.2.310 → 0.2.312
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/{adminCommands-BcH3OlSL.mjs → adminCommands-CRQQhXsw.mjs} +1 -1
- package/dist/{agentCommands-9GHpj3mp.mjs → agentCommands-hLTce38n.mjs} +5 -5
- package/dist/{auth-CuN-K8i9.mjs → auth-B2ZuhK_S.mjs} +1 -1
- package/dist/{cli-Co_GXbrT.mjs → cli-HI-J8Y7U.mjs} +70 -70
- package/dist/cli.mjs +2 -2
- package/dist/{commands-Dx4S6f_G.mjs → commands-12pIcOWK.mjs} +1 -1
- package/dist/{commands-D-RsuMqs.mjs → commands-C2zQmf3Y.mjs} +1 -1
- package/dist/{commands-DBiIsRQ_.mjs → commands-CmMh6R6S.mjs} +1 -1
- package/dist/{commands-DiuHHJ6r.mjs → commands-D6BqvSLe.mjs} +10 -5
- package/dist/{commands-DuLlH97k.mjs → commands-DGUgZoou.mjs} +2 -2
- package/dist/{commands-Eo0kocpm.mjs → commands-DpfYt_SX.mjs} +2 -2
- package/dist/{commands-B0cRa6cc.mjs → commands-IPkpFWbi.mjs} +2 -2
- package/dist/{commands-DrVsg0Q1.mjs → commands-ND3vwGkQ.mjs} +8 -8
- package/dist/{fleet-CFpET-T5.mjs → fleet-Cuv2uu1t.mjs} +2 -2
- package/dist/{frpc-DxIjZ8o_.mjs → frpc-DbZDLphz.mjs} +1 -1
- package/dist/{headlessCli-BIY-YePw.mjs → headlessCli-DakQSPzS.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{notifyCommands-DldLQlDU.mjs → notifyCommands-DhRbelQA.mjs} +1 -1
- package/dist/package-BKX7lisO.mjs +64 -0
- package/dist/{pinnedClaudeCode-zDzkOTi2.mjs → pinnedClaudeCode-mM9Q6Q2G.mjs} +1 -1
- package/dist/{rpc-BVw8Tv9l.mjs → rpc-C7uDtxFw.mjs} +1 -1
- package/dist/{rpc-D7_Gj0Kt.mjs → rpc-Q4PWHeNn.mjs} +1 -1
- package/dist/{run-CqO23ro_.mjs → run-Ccf8_4AF.mjs} +389 -88
- package/dist/{run-DLk7NGih.mjs → run-DG0yDh6U.mjs} +1 -1
- package/dist/{scheduler-q7Refevo.mjs → scheduler-B1xThM3A.mjs} +1 -1
- package/dist/{serveCommands-BJjvhDxp.mjs → serveCommands-DDq9qzoN.mjs} +5 -5
- package/dist/{sideband-Dv3qNlQN.mjs → sideband-CDdpZAXO.mjs} +1 -1
- package/package.json +2 -2
- package/dist/package-BKmrvBsb.mjs +0 -64
|
@@ -1121,7 +1121,13 @@ function buildSessionDeps(rpc, opts = {}) {
|
|
|
1121
1121
|
loop: {
|
|
1122
1122
|
task: opts2?.prompt?.trim() || "Work the ready issues in this session backlog until none remain (`svamp issue list --status ready`).",
|
|
1123
1123
|
oracle: "svamp issue pending",
|
|
1124
|
-
|
|
1124
|
+
// Durable backlog drainer: no forced iteration cap. A finite `?? 20` sent the loop
|
|
1125
|
+
// to a terminal `gave_up` after 20 turns even with issues remaining — a concrete
|
|
1126
|
+
// "loop stops easily" cause. Only an EXPLICIT user-set cap bounds it; otherwise the
|
|
1127
|
+
// loop runs unbounded (real runaway guard = the rolling per-hour token rate cap) and
|
|
1128
|
+
// goes DORMANT (armed) when the backlog clears. `durable:true` marks it re-arming.
|
|
1129
|
+
...opts2?.maxIterations ? { max_iterations: opts2.maxIterations } : {},
|
|
1130
|
+
durable: true,
|
|
1125
1131
|
evaluator: true
|
|
1126
1132
|
}
|
|
1127
1133
|
}, ctx);
|
|
@@ -3147,7 +3153,7 @@ Connection: close\r
|
|
|
3147
3153
|
const mount = this.mounts.get(mountName);
|
|
3148
3154
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
3149
3155
|
try {
|
|
3150
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
3156
|
+
const { FrpcTunnel } = await import('./frpc-DbZDLphz.mjs');
|
|
3151
3157
|
let tunnel;
|
|
3152
3158
|
tunnel = new FrpcTunnel({
|
|
3153
3159
|
name: tunnelName,
|
|
@@ -6280,7 +6286,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
6280
6286
|
const tunnels = handlers.tunnels;
|
|
6281
6287
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
6282
6288
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
6283
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
6289
|
+
const { FrpcTunnel } = await import('./frpc-DbZDLphz.mjs');
|
|
6284
6290
|
const tunnel = new FrpcTunnel({
|
|
6285
6291
|
name: params.name,
|
|
6286
6292
|
ports: params.ports,
|
|
@@ -6773,7 +6779,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6773
6779
|
}
|
|
6774
6780
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
6775
6781
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
6776
|
-
const { toolsForRole } = await import('./sideband-
|
|
6782
|
+
const { toolsForRole } = await import('./sideband-CDdpZAXO.mjs');
|
|
6777
6783
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
6778
6784
|
return fmt(r2);
|
|
6779
6785
|
}
|
|
@@ -6872,13 +6878,13 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
6872
6878
|
hyphaWorkspace: u?.scope?.current_workspace
|
|
6873
6879
|
});
|
|
6874
6880
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
6875
|
-
const callId = "call_" +
|
|
6881
|
+
const callId = "call_" + randomUUID().slice(0, 10);
|
|
6876
6882
|
const senderKey = (r.sender.name || "anonymous").toLowerCase();
|
|
6877
6883
|
if (!statelessLimiter.tryAcquire(senderKey)) {
|
|
6878
6884
|
return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
|
|
6879
6885
|
}
|
|
6880
6886
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
6881
|
-
const { queryCore } = await import('./commands-
|
|
6887
|
+
const { queryCore } = await import('./commands-C2zQmf3Y.mjs');
|
|
6882
6888
|
const timeout = c.reply?.timeout_sec || 120;
|
|
6883
6889
|
let result;
|
|
6884
6890
|
try {
|
|
@@ -7018,7 +7024,7 @@ ${d?.error || "not found"}`;
|
|
|
7018
7024
|
try {
|
|
7019
7025
|
const saved = writeUpload(dir, v.target, dec.data);
|
|
7020
7026
|
try {
|
|
7021
|
-
new ChannelStore(dir).recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId: "up_" +
|
|
7027
|
+
new ChannelStore(dir).recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId: "up_" + randomUUID().slice(0, 10), outcome: "uploaded" });
|
|
7022
7028
|
} catch {
|
|
7023
7029
|
}
|
|
7024
7030
|
return { ok: true, name: saved.name, path: saved.relPath, bytes: saved.bytes };
|
|
@@ -7670,7 +7676,15 @@ async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
|
|
|
7670
7676
|
}
|
|
7671
7677
|
|
|
7672
7678
|
const OUTPOST_STALE_MS = 9e4;
|
|
7673
|
-
const OUTPOST_EXEC_TIMEOUT_MS =
|
|
7679
|
+
const OUTPOST_EXEC_TIMEOUT_MS = 12 * 6e4;
|
|
7680
|
+
const OUTPOST_MAX_OUTPUT_BYTES = 1 << 20;
|
|
7681
|
+
function capOutpostOutput(s) {
|
|
7682
|
+
if (!s) return "";
|
|
7683
|
+
if (s.length <= OUTPOST_MAX_OUTPUT_BYTES) return s;
|
|
7684
|
+
const dropped = s.length - OUTPOST_MAX_OUTPUT_BYTES;
|
|
7685
|
+
return `[svamp-daemon] output truncated: ${dropped} earlier characters dropped]
|
|
7686
|
+
${s.slice(-OUTPOST_MAX_OUTPUT_BYTES)}`;
|
|
7687
|
+
}
|
|
7674
7688
|
class OutpostCoordinator {
|
|
7675
7689
|
conns = /* @__PURE__ */ new Map();
|
|
7676
7690
|
queue = /* @__PURE__ */ new Map();
|
|
@@ -7816,7 +7830,14 @@ class OutpostCoordinator {
|
|
|
7816
7830
|
const timer = this.setTimer(() => {
|
|
7817
7831
|
if (!this.pending.has(id)) return;
|
|
7818
7832
|
this.pending.delete(id);
|
|
7819
|
-
resolve({
|
|
7833
|
+
resolve({
|
|
7834
|
+
id,
|
|
7835
|
+
stdout: "",
|
|
7836
|
+
stderr: `command timed out after ${Math.round(this.execTimeoutMs / 1e3)}s with no result from the outpost \u2014 it may still be running remotely, so re-running it is NOT necessarily safe`,
|
|
7837
|
+
exitCode: -1,
|
|
7838
|
+
synthetic: true,
|
|
7839
|
+
ts: this.now()
|
|
7840
|
+
});
|
|
7820
7841
|
}, this.execTimeoutMs);
|
|
7821
7842
|
this.pending.set(id, { machine, resolve, timer, enqueuedAt: this.now() });
|
|
7822
7843
|
});
|
|
@@ -7840,8 +7861,11 @@ class OutpostCoordinator {
|
|
|
7840
7861
|
this.touch(p.machine);
|
|
7841
7862
|
p.resolve({
|
|
7842
7863
|
id: result.id,
|
|
7843
|
-
|
|
7844
|
-
|
|
7864
|
+
// #0814: the outpost caps its own output, but clamp again here — a hostile or
|
|
7865
|
+
// out-of-date outpost must not be able to push an unbounded string through the
|
|
7866
|
+
// daemon and out over Hypha RPC (the #0598 oversized-message class).
|
|
7867
|
+
stdout: capOutpostOutput(result.stdout),
|
|
7868
|
+
stderr: capOutpostOutput(result.stderr),
|
|
7845
7869
|
exitCode: typeof result.exitCode === "number" ? result.exitCode : 0,
|
|
7846
7870
|
ts: this.now()
|
|
7847
7871
|
});
|
|
@@ -8273,7 +8297,9 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
8273
8297
|
const who = conn.label || conn.machine;
|
|
8274
8298
|
const plat = [conn.os, conn.arch].filter(Boolean).join("/") || "unknown platform";
|
|
8275
8299
|
const msg = {
|
|
8276
|
-
|
|
8300
|
+
// #0816: CSPRNG — this id routes `svamp session inbox reply`, so a predictable
|
|
8301
|
+
// one lets a third party target another agent's reply thread.
|
|
8302
|
+
messageId: "op_" + randomUUID().slice(0, 10),
|
|
8277
8303
|
body: `A remote machine connected as an outpost: ${xmlEscape(who)} (${xmlEscape(plat)}). You can now run shell commands on it \u2014 its output returns synchronously, like a local command. Run one with: svamp outpost exec "uname -a"`,
|
|
8278
8304
|
timestamp: Date.now(),
|
|
8279
8305
|
read: false,
|
|
@@ -13937,6 +13963,7 @@ function shouldIsolate(input) {
|
|
|
13937
13963
|
function classifyRestoreContinuation(opts) {
|
|
13938
13964
|
if (opts.isOrphaned) return "none";
|
|
13939
13965
|
if (opts.loopActive) return "loop-resume";
|
|
13966
|
+
if (opts.loopArmed) return opts.armedHasPendingWork ? "loop-resume" : "none";
|
|
13940
13967
|
if (opts.wasProcessing && opts.hasResumeId) return "auto-continue";
|
|
13941
13968
|
return "none";
|
|
13942
13969
|
}
|
|
@@ -15048,7 +15075,7 @@ function searchIssues(projectRoot, query) {
|
|
|
15048
15075
|
}
|
|
15049
15076
|
function summarize(issues) {
|
|
15050
15077
|
const by = (s) => issues.filter((i) => i.status === s).length;
|
|
15051
|
-
return { total: issues.length, ready: by("ready"), in_progress: by("in_progress"), archived: by("archived") };
|
|
15078
|
+
return { total: issues.length, ready: by("ready"), in_progress: by("in_progress"), paused: by("paused"), archived: by("archived") };
|
|
15052
15079
|
}
|
|
15053
15080
|
|
|
15054
15081
|
function isWorkflowEnabled(wf) {
|
|
@@ -15606,7 +15633,15 @@ function loopCancelledDuringVerify(current) {
|
|
|
15606
15633
|
}
|
|
15607
15634
|
function backlogOraclePending(projectRoot, sessionId) {
|
|
15608
15635
|
const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
|
|
15609
|
-
return listIssues(projectRoot).filter(inScope).filter((i) => i.status
|
|
15636
|
+
return listIssues(projectRoot).filter(inScope).filter((i) => i.status !== "archived" && i.status !== "paused").map((i) => ({ id: i.id }));
|
|
15637
|
+
}
|
|
15638
|
+
function isBacklogDrainerOracle(oracle) {
|
|
15639
|
+
if (!oracle || typeof oracle !== "string") return false;
|
|
15640
|
+
return /(^|\s)svamp\s+issue\s+pending(\s|$)/.test(oracle.trim());
|
|
15641
|
+
}
|
|
15642
|
+
function resolveLoopDurable(cfg) {
|
|
15643
|
+
if (typeof cfg.durable === "boolean") return cfg.durable;
|
|
15644
|
+
return isBacklogDrainerOracle(cfg.oracle);
|
|
15610
15645
|
}
|
|
15611
15646
|
function issuesClosedSince(projectRoot, sinceMs) {
|
|
15612
15647
|
const all = listIssues(projectRoot, { includeArchived: true, status: "archived" });
|
|
@@ -15831,6 +15866,25 @@ function decideLoopContinuation(input) {
|
|
|
15831
15866
|
const tail = `no progress across ${stuckCheckpoints} checkpoints; ${maxAutoResumes} auto-resume(s) exhausted`;
|
|
15832
15867
|
return { action: "stop", kind: "stuck", reason: input.stuckReason ? `${input.stuckReason} \u2014 ${tail}` : tail };
|
|
15833
15868
|
}
|
|
15869
|
+
function terminalPhaseForDone(durable) {
|
|
15870
|
+
return durable ? "dormant" : "done";
|
|
15871
|
+
}
|
|
15872
|
+
const DEFAULT_LOOP_VERIFY_ERROR_RETRIES = 3;
|
|
15873
|
+
function resolveVerifyErrorRetries(envValue) {
|
|
15874
|
+
if (envValue == null || envValue.trim() === "") return DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
15875
|
+
const n = Number(envValue);
|
|
15876
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
15877
|
+
return DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
15878
|
+
}
|
|
15879
|
+
function decideVerifyErrorHandling(input) {
|
|
15880
|
+
const maxRetries = typeof input.maxRetries === "number" && input.maxRetries >= 0 ? Math.floor(input.maxRetries) : DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
15881
|
+
const attempt = (typeof input.priorErrors === "number" && input.priorErrors > 0 ? Math.floor(input.priorErrors) : 0) + 1;
|
|
15882
|
+
const why = input.reason ? String(input.reason).slice(0, 200) : "verification error";
|
|
15883
|
+
if (attempt <= maxRetries) {
|
|
15884
|
+
return { action: "retry", attempt, reason: `${why} \u2014 retry ${attempt}/${maxRetries} (keeping the loop running, not declaring done)` };
|
|
15885
|
+
}
|
|
15886
|
+
return { action: "park", attempt, reason: `${why} \u2014 ${maxRetries} verification retries exhausted; parking safely instead of a false "done"` };
|
|
15887
|
+
}
|
|
15834
15888
|
const DEFAULT_LOOP_EVALUATOR_MODEL = "claude-haiku-4-5-20251001";
|
|
15835
15889
|
function resolveEvaluatorModel(envValue) {
|
|
15836
15890
|
if (envValue === void 0) return DEFAULT_LOOP_EVALUATOR_MODEL;
|
|
@@ -15905,13 +15959,13 @@ Keep working until it is genuinely empty.` };
|
|
|
15905
15959
|
try {
|
|
15906
15960
|
raw = await deps.runEvaluator(prompt);
|
|
15907
15961
|
} catch (e) {
|
|
15908
|
-
log(`[loopVerify] evaluator spawn failed \u2014
|
|
15909
|
-
return { action: "
|
|
15962
|
+
log(`[loopVerify] evaluator spawn failed \u2014 signalling verify_error (fail-closed, will retry): ${e?.message || e}`);
|
|
15963
|
+
return { action: "verify_error", reason: `evaluator spawn failed: ${e?.message || e}` };
|
|
15910
15964
|
}
|
|
15911
15965
|
const verdict = parseEvaluatorVerdict(raw);
|
|
15912
15966
|
if (!verdict) {
|
|
15913
|
-
log(`[loopVerify] could not parse evaluator verdict \u2014
|
|
15914
|
-
return { action: "
|
|
15967
|
+
log(`[loopVerify] could not parse evaluator verdict \u2014 signalling verify_error (fail-closed, will retry)`);
|
|
15968
|
+
return { action: "verify_error", reason: "could not parse evaluator verdict" };
|
|
15915
15969
|
}
|
|
15916
15970
|
if (verdict.verdict === "done") {
|
|
15917
15971
|
log(`[loopVerify] evaluator confirms done${verdict.reason ? `: ${verdict.reason}` : ""}`);
|
|
@@ -16358,6 +16412,28 @@ async function writeSessionFileChunk(directory, sessionMetadata, path, content,
|
|
|
16358
16412
|
}
|
|
16359
16413
|
}
|
|
16360
16414
|
|
|
16415
|
+
function isStdinWritable(proc) {
|
|
16416
|
+
if (!proc || proc.exitCode !== null) return false;
|
|
16417
|
+
const stdin = proc.stdin;
|
|
16418
|
+
if (!stdin) return false;
|
|
16419
|
+
if (stdin.destroyed === true) return false;
|
|
16420
|
+
if (stdin.writable === false) return false;
|
|
16421
|
+
return true;
|
|
16422
|
+
}
|
|
16423
|
+
function serializeUserFrame(text) {
|
|
16424
|
+
return JSON.stringify({ type: "user", message: { role: "user", content: text } }) + "\n";
|
|
16425
|
+
}
|
|
16426
|
+
function writeUserFrame(proc, text, onError) {
|
|
16427
|
+
if (!isStdinWritable(proc)) return false;
|
|
16428
|
+
try {
|
|
16429
|
+
proc.stdin.write(serializeUserFrame(text));
|
|
16430
|
+
return true;
|
|
16431
|
+
} catch (err) {
|
|
16432
|
+
onError?.(err);
|
|
16433
|
+
return false;
|
|
16434
|
+
}
|
|
16435
|
+
}
|
|
16436
|
+
|
|
16361
16437
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
16362
16438
|
const __dirname$1 = dirname$1(__filename$1);
|
|
16363
16439
|
const CLAUDE_SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
|
|
@@ -16900,18 +16976,27 @@ function readLoopState(directory, sessionId) {
|
|
|
16900
16976
|
return null;
|
|
16901
16977
|
}
|
|
16902
16978
|
}
|
|
16979
|
+
function isTerminalLoopPhase(phase) {
|
|
16980
|
+
return phase === "done" || phase === "gave_up" || phase === "cancelled";
|
|
16981
|
+
}
|
|
16903
16982
|
function isLoopActive(directory, sessionId) {
|
|
16904
16983
|
const s = readLoopState(directory, sessionId);
|
|
16905
|
-
return !!s && s.active !== false && s.phase !== "
|
|
16984
|
+
return !!s && s.active !== false && s.phase !== "dormant" && !isTerminalLoopPhase(s.phase);
|
|
16906
16985
|
}
|
|
16907
16986
|
function loopOwnerSession(directory, sessionId) {
|
|
16908
16987
|
const s = readLoopState(directory, sessionId);
|
|
16909
|
-
if (!s || s.active === false || s.phase === "
|
|
16988
|
+
if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return null;
|
|
16910
16989
|
return typeof s.session_id === "string" ? s.session_id : null;
|
|
16911
16990
|
}
|
|
16912
16991
|
function isLoopActiveForSession(directory, sessionId) {
|
|
16913
16992
|
const s = readLoopState(directory, sessionId);
|
|
16914
|
-
if (!s || s.active === false || s.phase === "
|
|
16993
|
+
if (!s || s.active === false || s.phase === "dormant" || isTerminalLoopPhase(s.phase)) return false;
|
|
16994
|
+
if (typeof s.session_id !== "string") return true;
|
|
16995
|
+
return s.session_id === sessionId;
|
|
16996
|
+
}
|
|
16997
|
+
function isLoopArmedForSession(directory, sessionId) {
|
|
16998
|
+
const s = readLoopState(directory, sessionId);
|
|
16999
|
+
if (!s || s.active === false || s.phase !== "dormant") return false;
|
|
16915
17000
|
if (typeof s.session_id !== "string") return true;
|
|
16916
17001
|
return s.session_id === sessionId;
|
|
16917
17002
|
}
|
|
@@ -16980,6 +17065,14 @@ function loopResumeMessage(directory, sessionId) {
|
|
|
16980
17065
|
function stripOldLoopHooks(directory) {
|
|
16981
17066
|
try {
|
|
16982
17067
|
const p = join(directory, ".claude", "settings.json");
|
|
17068
|
+
if (!existsSync$1(p)) return false;
|
|
17069
|
+
return withFileLock(p + ".svamp-lock", () => stripOldLoopHooksLocked(p));
|
|
17070
|
+
} catch {
|
|
17071
|
+
return false;
|
|
17072
|
+
}
|
|
17073
|
+
}
|
|
17074
|
+
function stripOldLoopHooksLocked(p) {
|
|
17075
|
+
try {
|
|
16983
17076
|
if (!existsSync$1(p)) return false;
|
|
16984
17077
|
const cfg = JSON.parse(readFileSync$1(p, "utf-8"));
|
|
16985
17078
|
if (!cfg || typeof cfg.hooks !== "object" || !cfg.hooks) return false;
|
|
@@ -17003,13 +17096,31 @@ function stripOldLoopHooks(directory) {
|
|
|
17003
17096
|
}
|
|
17004
17097
|
if (changed) {
|
|
17005
17098
|
if (Object.keys(cfg.hooks).length === 0) delete cfg.hooks;
|
|
17006
|
-
|
|
17099
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
17100
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
17101
|
+
renameSync$1(tmp, p);
|
|
17007
17102
|
}
|
|
17008
17103
|
return changed;
|
|
17009
17104
|
} catch {
|
|
17010
17105
|
return false;
|
|
17011
17106
|
}
|
|
17012
17107
|
}
|
|
17108
|
+
const HELPER_KILL_GRACE_MS = 3e3;
|
|
17109
|
+
const BTW_TIMEOUT_MS = Number(process.env.SVAMP_BTW_TIMEOUT_MS) > 0 ? Number(process.env.SVAMP_BTW_TIMEOUT_MS) : 10 * 6e4;
|
|
17110
|
+
function killHelperTree(proc, graceMs = HELPER_KILL_GRACE_MS) {
|
|
17111
|
+
const pid = proc.pid;
|
|
17112
|
+
if (!pid) return;
|
|
17113
|
+
try {
|
|
17114
|
+
process.kill(-pid, "SIGTERM");
|
|
17115
|
+
} catch {
|
|
17116
|
+
}
|
|
17117
|
+
setTimeout(() => {
|
|
17118
|
+
try {
|
|
17119
|
+
process.kill(-pid, "SIGKILL");
|
|
17120
|
+
} catch {
|
|
17121
|
+
}
|
|
17122
|
+
}, graceMs).unref();
|
|
17123
|
+
}
|
|
17013
17124
|
function spawnHeadlessEvaluator(prompt, opts) {
|
|
17014
17125
|
return new Promise((resolve2, reject) => {
|
|
17015
17126
|
const evalArgs = [
|
|
@@ -17040,7 +17151,7 @@ function spawnHeadlessEvaluator(prompt, opts) {
|
|
|
17040
17151
|
delete evalEnv.CLAUDECODE;
|
|
17041
17152
|
let proc;
|
|
17042
17153
|
try {
|
|
17043
|
-
proc = spawn("claude", evalArgs, { cwd: opts.cwd, env: evalEnv, stdio: ["pipe", "pipe", "pipe"] });
|
|
17154
|
+
proc = spawn("claude", evalArgs, { cwd: opts.cwd, env: evalEnv, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
17044
17155
|
} catch (e) {
|
|
17045
17156
|
reject(e);
|
|
17046
17157
|
return;
|
|
@@ -17049,10 +17160,7 @@ function spawnHeadlessEvaluator(prompt, opts) {
|
|
|
17049
17160
|
const timer = setTimeout(() => {
|
|
17050
17161
|
if (settled) return;
|
|
17051
17162
|
settled = true;
|
|
17052
|
-
|
|
17053
|
-
proc.kill("SIGTERM");
|
|
17054
|
-
} catch {
|
|
17055
|
-
}
|
|
17163
|
+
killHelperTree(proc);
|
|
17056
17164
|
reject(new Error("evaluator timeout"));
|
|
17057
17165
|
}, opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 3e5);
|
|
17058
17166
|
proc.stderr?.on("data", (c) => {
|
|
@@ -17398,11 +17506,12 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
17398
17506
|
const softTurnCap = computeSoftTurnCap(hardMax, LOOP_CHECKPOINT_CADENCE);
|
|
17399
17507
|
const condition = buildGoalCondition({ task, until, oracle, maxIterations: softTurnCap });
|
|
17400
17508
|
const budget = parseLoopBudget(cfg.budget);
|
|
17509
|
+
const durable = resolveLoopDurable({ durable: cfg.durable, oracle });
|
|
17401
17510
|
try {
|
|
17402
17511
|
stripOldLoopHooks(directory);
|
|
17403
17512
|
} catch {
|
|
17404
17513
|
}
|
|
17405
|
-
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: [] }, progress_history: [], auto_resumes: 0 });
|
|
17514
|
+
writeGoalLoopState(directory, sessionId, { active: true, phase: "running", durable, 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: [] }, progress_history: [], auto_resumes: 0, verify_errors: 0 });
|
|
17406
17515
|
const existingQueue = getMetadata().messageQueue || [];
|
|
17407
17516
|
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID$1(), text: `/goal ${condition}`, displayText: `\u{1F3AF} Goal: ${(task || until || "").slice(0, 100)}`, createdAt: Date.now() }] }));
|
|
17408
17517
|
onLoopActivated?.();
|
|
@@ -17855,7 +17964,7 @@ async function startDaemon(options) {
|
|
|
17855
17964
|
try {
|
|
17856
17965
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
17857
17966
|
if (!dir) return;
|
|
17858
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
17967
|
+
const { reconcileServiceLinks } = await import('./agentCommands-hLTce38n.mjs');
|
|
17859
17968
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
17860
17969
|
const config = readSvampConfig(configPath);
|
|
17861
17970
|
const entries = Array.from(urls.entries());
|
|
@@ -17873,7 +17982,7 @@ async function startDaemon(options) {
|
|
|
17873
17982
|
}
|
|
17874
17983
|
}
|
|
17875
17984
|
async function createExposedTunnel(spec) {
|
|
17876
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
17985
|
+
const { FrpcTunnel } = await import('./frpc-DbZDLphz.mjs');
|
|
17877
17986
|
const tunnel = new FrpcTunnel({
|
|
17878
17987
|
name: spec.name,
|
|
17879
17988
|
ports: spec.ports,
|
|
@@ -17901,7 +18010,7 @@ async function startDaemon(options) {
|
|
|
17901
18010
|
ensureAutoInstalledCommands(logger);
|
|
17902
18011
|
(async () => {
|
|
17903
18012
|
try {
|
|
17904
|
-
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-
|
|
18013
|
+
const { beginClaudeVersionReconcile } = await import('./pinnedClaudeCode-mM9Q6Q2G.mjs');
|
|
17905
18014
|
beginClaudeVersionReconcile((msg) => logger.log(msg));
|
|
17906
18015
|
} catch (e) {
|
|
17907
18016
|
logger.log(`[claude-version] check failed: ${e?.message || e}`);
|
|
@@ -18396,12 +18505,16 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
18396
18505
|
"json",
|
|
18397
18506
|
"--max-turns",
|
|
18398
18507
|
"6"
|
|
18399
|
-
|
|
18508
|
+
// #0820: detached → own process group. Node's `timeout` option kills only
|
|
18509
|
+
// the direct child, so without this a tool grandchild survives the 60s cap
|
|
18510
|
+
// and reparents to init. The exit/error handlers below kill the group.
|
|
18511
|
+
], { cwd, timeout: 6e4, stdio: ["ignore", "pipe", "pipe"], env: forkEnv, detached: true });
|
|
18400
18512
|
let stdout = "";
|
|
18401
18513
|
child.stdout?.on("data", (d) => {
|
|
18402
18514
|
stdout += d.toString();
|
|
18403
18515
|
});
|
|
18404
18516
|
child.on("close", () => {
|
|
18517
|
+
killHelperTree(child);
|
|
18405
18518
|
try {
|
|
18406
18519
|
const r = JSON.parse(stdout);
|
|
18407
18520
|
resolve2(r.result || r.text || null);
|
|
@@ -18409,7 +18522,10 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
|
18409
18522
|
resolve2(null);
|
|
18410
18523
|
}
|
|
18411
18524
|
});
|
|
18412
|
-
child.on("error", () =>
|
|
18525
|
+
child.on("error", () => {
|
|
18526
|
+
killHelperTree(child);
|
|
18527
|
+
resolve2(null);
|
|
18528
|
+
});
|
|
18413
18529
|
} catch {
|
|
18414
18530
|
resolve2(null);
|
|
18415
18531
|
}
|
|
@@ -18492,6 +18608,13 @@ ${parts.join("\n")}`);
|
|
|
18492
18608
|
let startupRetryMessage;
|
|
18493
18609
|
let startupNonJsonLines = [];
|
|
18494
18610
|
let errorOutputLines = [];
|
|
18611
|
+
const MAX_ERROR_OUTPUT_LINES = 200;
|
|
18612
|
+
const pushErrorOutputLine = (line) => {
|
|
18613
|
+
errorOutputLines.push(line);
|
|
18614
|
+
if (errorOutputLines.length > MAX_ERROR_OUTPUT_LINES) {
|
|
18615
|
+
errorOutputLines.splice(0, errorOutputLines.length - MAX_ERROR_OUTPUT_LINES);
|
|
18616
|
+
}
|
|
18617
|
+
};
|
|
18495
18618
|
const STUCK_PROCESS_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
18496
18619
|
const STUCK_CHECK_INTERVAL_MS = 2 * 60 * 1e3;
|
|
18497
18620
|
let lastOutputTime = Date.now();
|
|
@@ -18598,6 +18721,7 @@ ${parts.join("\n")}`);
|
|
|
18598
18721
|
const budget = ls.budget;
|
|
18599
18722
|
const progress_history = curLs.progress_history ?? ls.progress_history;
|
|
18600
18723
|
const extensions = curLs.extensions ?? ls.extensions;
|
|
18724
|
+
const durable = resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd });
|
|
18601
18725
|
if (result.action === "rekick") {
|
|
18602
18726
|
const cond = ls.task || "";
|
|
18603
18727
|
writeGoalLoopState(directory, sessionId, {
|
|
@@ -18615,7 +18739,9 @@ ${parts.join("\n")}`);
|
|
|
18615
18739
|
ledger,
|
|
18616
18740
|
progress_history,
|
|
18617
18741
|
auto_resumes: ls.auto_resumes,
|
|
18618
|
-
extensions
|
|
18742
|
+
extensions,
|
|
18743
|
+
durable: ls.durable,
|
|
18744
|
+
verify_errors: 0
|
|
18619
18745
|
});
|
|
18620
18746
|
const guidance = result.guidance.replace(/\s+/g, " ").slice(0, 1200);
|
|
18621
18747
|
sessionService.pushMessage({ type: "message", message: `\u{1F501} Loop not done \u2014 independent review: ${result.reason}`, level: "warning" }, "event");
|
|
@@ -18652,7 +18778,9 @@ ${parts.join("\n")}`);
|
|
|
18652
18778
|
ledger,
|
|
18653
18779
|
progress_history,
|
|
18654
18780
|
auto_resumes: nextAuto,
|
|
18655
|
-
extensions
|
|
18781
|
+
extensions,
|
|
18782
|
+
durable: ls.durable,
|
|
18783
|
+
verify_errors: 0
|
|
18656
18784
|
});
|
|
18657
18785
|
const label = decision.kind === "autoresume" ? "\u{1F501} Auto-resuming stalled loop" : "\u{1F501} Loop still progressing";
|
|
18658
18786
|
sessionService.pushMessage({ type: "message", message: `${label} \u2014 ${decision.reason}` }, "event");
|
|
@@ -18665,15 +18793,75 @@ ${parts.join("\n")}`);
|
|
|
18665
18793
|
logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
|
|
18666
18794
|
checkSvampConfig?.();
|
|
18667
18795
|
}
|
|
18796
|
+
} else if (result.action === "verify_error") {
|
|
18797
|
+
const decision = decideVerifyErrorHandling({
|
|
18798
|
+
priorErrors: ls.verify_errors,
|
|
18799
|
+
maxRetries: resolveVerifyErrorRetries(process.env.SVAMP_LOOP_VERIFY_ERROR_RETRIES),
|
|
18800
|
+
reason: result.reason
|
|
18801
|
+
});
|
|
18802
|
+
if (decision.action === "retry") {
|
|
18803
|
+
const cond = ls.task || "";
|
|
18804
|
+
writeGoalLoopState(directory, sessionId, {
|
|
18805
|
+
active: true,
|
|
18806
|
+
phase: "continue",
|
|
18807
|
+
iteration: ls.iteration || 0,
|
|
18808
|
+
task: ls.task,
|
|
18809
|
+
goal_task: ls.goal_task,
|
|
18810
|
+
until: ls.until,
|
|
18811
|
+
oracle: oracleCmd,
|
|
18812
|
+
max_iterations: ls.max_iterations,
|
|
18813
|
+
budget,
|
|
18814
|
+
started_at: startedAt,
|
|
18815
|
+
holds,
|
|
18816
|
+
ledger,
|
|
18817
|
+
progress_history,
|
|
18818
|
+
auto_resumes: ls.auto_resumes,
|
|
18819
|
+
extensions,
|
|
18820
|
+
durable: ls.durable,
|
|
18821
|
+
verify_errors: decision.attempt
|
|
18822
|
+
});
|
|
18823
|
+
sessionService.pushMessage({ type: "message", message: `\u26A0\uFE0F Loop verification hit a transient error \u2014 ${decision.reason}`, level: "warning" }, "event");
|
|
18824
|
+
logger.log(`[Session ${sessionId}] [loop-verify-error] ${decision.reason}`);
|
|
18825
|
+
enqueueLoopMessage(`/goal ${cond} \u2014 the independent completion check could not run (${result.reason}); CONTINUE the remaining work and it will be re-verified. Do NOT treat this as done.`, "\u26A0\uFE0F Re-verifying loop");
|
|
18826
|
+
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
18827
|
+
} else {
|
|
18828
|
+
const parkPhase = terminalPhaseForDone(durable);
|
|
18829
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: parkPhase === "dormant", phase: parkPhase, completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions, durable: ls.durable, verify_errors: 0 });
|
|
18830
|
+
sessionService.pushMessage({ type: "message", message: parkPhase === "dormant" ? `\u{1F319} Loop parked (armed) after repeated verification errors \u2014 it will re-arm when new work arrives. ${decision.reason}` : `\u26A0\uFE0F Loop stopped after repeated verification errors \u2014 ${decision.reason}`, level: "warning" }, "event");
|
|
18831
|
+
logger.log(`[Session ${sessionId}] [loop-verify-error] park ${parkPhase}: ${decision.reason}`);
|
|
18832
|
+
checkSvampConfig?.();
|
|
18833
|
+
}
|
|
18668
18834
|
} else {
|
|
18669
|
-
|
|
18835
|
+
const donePhase = terminalPhaseForDone(durable);
|
|
18836
|
+
if (donePhase === "dormant") {
|
|
18837
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "dormant", durable: true, completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions, verify_errors: 0 });
|
|
18838
|
+
sessionService.pushMessage({ type: "message", message: "\u{1F319} Backlog clear \u2014 loop is armed and watching for new issues (auto-resumes when work arrives)." }, "event");
|
|
18839
|
+
logger.log(`[Session ${sessionId}] [loop] backlog drained \u2192 dormant (armed)`);
|
|
18840
|
+
} else {
|
|
18841
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, progress_history, auto_resumes: ls.auto_resumes, extensions, verify_errors: 0 });
|
|
18842
|
+
}
|
|
18670
18843
|
checkSvampConfig?.();
|
|
18671
18844
|
}
|
|
18672
18845
|
} catch (e) {
|
|
18673
|
-
logger.log(`[Session ${sessionId}] verifyGoalCompletion
|
|
18846
|
+
logger.log(`[Session ${sessionId}] verifyGoalCompletion threw \u2014 failing closed (retry/park): ${e?.message || e}`);
|
|
18674
18847
|
try {
|
|
18675
|
-
|
|
18676
|
-
|
|
18848
|
+
const durable2 = resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd });
|
|
18849
|
+
const decision = decideVerifyErrorHandling({
|
|
18850
|
+
priorErrors: ls.verify_errors,
|
|
18851
|
+
maxRetries: resolveVerifyErrorRetries(process.env.SVAMP_LOOP_VERIFY_ERROR_RETRIES),
|
|
18852
|
+
reason: `verify threw: ${e?.message || e}`
|
|
18853
|
+
});
|
|
18854
|
+
if (decision.action === "retry") {
|
|
18855
|
+
const cond = ls.task || "";
|
|
18856
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "continue", started_at: startedAt, verify_errors: decision.attempt });
|
|
18857
|
+
sessionService.pushMessage({ type: "message", message: `\u26A0\uFE0F Loop verification error \u2014 ${decision.reason}`, level: "warning" }, "event");
|
|
18858
|
+
enqueueLoopMessage(`/goal ${cond} \u2014 the completion check could not run; CONTINUE and it will be re-verified. Not done.`, "\u26A0\uFE0F Re-verifying loop");
|
|
18859
|
+
if (!trackedSession.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
18860
|
+
} else {
|
|
18861
|
+
const parkPhase = terminalPhaseForDone(durable2);
|
|
18862
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: parkPhase === "dormant", phase: parkPhase, completed_at: Date.now(), verify_errors: 0 });
|
|
18863
|
+
checkSvampConfig?.();
|
|
18864
|
+
}
|
|
18677
18865
|
} catch {
|
|
18678
18866
|
}
|
|
18679
18867
|
} finally {
|
|
@@ -19191,7 +19379,7 @@ ${parts.join("\n")}`);
|
|
|
19191
19379
|
clearInboundContext(sessionId);
|
|
19192
19380
|
try {
|
|
19193
19381
|
const ls = readLoopState(directory, sessionId);
|
|
19194
|
-
if (ls && ls.engine === "goal" && ls.active !== false && ls.phase !== "done" && ls.phase !== "gave_up" && ls.phase !== "cancelled") {
|
|
19382
|
+
if (ls && ls.engine === "goal" && ls.active !== false && ls.phase !== "dormant" && ls.phase !== "done" && ls.phase !== "gave_up" && ls.phase !== "cancelled") {
|
|
19195
19383
|
const now = Date.now();
|
|
19196
19384
|
const ledger = accumulateLedger(ls.ledger, {
|
|
19197
19385
|
turns: Number(msg.num_turns) || 0,
|
|
@@ -19225,7 +19413,8 @@ ${parts.join("\n")}`);
|
|
|
19225
19413
|
sessionService.pushMessage({ type: "message", message: `\u{1F6D1} Loop stopped \u2014 ${detail}`, level: "warning" }, "event");
|
|
19226
19414
|
checkSvampConfig?.();
|
|
19227
19415
|
} else if (process.env.SVAMP_LOOP_VERIFY === "0") {
|
|
19228
|
-
|
|
19416
|
+
const donePhase = terminalPhaseForDone(resolveLoopDurable({ durable: ls.durable, oracle: ls.oracle }));
|
|
19417
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: donePhase === "dormant", phase: donePhase, completed_at: now, ledger, progress_history: prog.progress_history, auto_resumes: prog.auto_resumes });
|
|
19229
19418
|
} else {
|
|
19230
19419
|
writeGoalLoopState(directory, sessionId, { ...ls, ledger, progress_history: prog.progress_history, auto_resumes: prog.auto_resumes });
|
|
19231
19420
|
void verifyGoalCompletion(ls);
|
|
@@ -19361,7 +19550,7 @@ ${parts.join("\n")}`);
|
|
|
19361
19550
|
startupNonJsonLines.push(line.slice(0, 500));
|
|
19362
19551
|
}
|
|
19363
19552
|
if (looksLikeClaudeError(line)) {
|
|
19364
|
-
|
|
19553
|
+
pushErrorOutputLine(line.slice(0, 500));
|
|
19365
19554
|
}
|
|
19366
19555
|
}
|
|
19367
19556
|
}
|
|
@@ -19379,14 +19568,12 @@ ${parts.join("\n")}`);
|
|
|
19379
19568
|
stdoutBuffer = "";
|
|
19380
19569
|
}
|
|
19381
19570
|
});
|
|
19382
|
-
let stderrBuffer = "";
|
|
19383
19571
|
child.stderr?.on("data", (chunk) => {
|
|
19384
19572
|
const text = chunk.toString();
|
|
19385
19573
|
logger.log(`[Session ${sessionId}] Claude stderr: ${text.trim()}`);
|
|
19386
|
-
stderrBuffer += text;
|
|
19387
19574
|
for (const line of text.split("\n")) {
|
|
19388
19575
|
if (looksLikeClaudeError(line)) {
|
|
19389
|
-
|
|
19576
|
+
pushErrorOutputLine(line.trim().slice(0, 500));
|
|
19390
19577
|
}
|
|
19391
19578
|
}
|
|
19392
19579
|
});
|
|
@@ -19472,6 +19659,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19472
19659
|
}
|
|
19473
19660
|
return child;
|
|
19474
19661
|
};
|
|
19662
|
+
const writeUserFrameToClaude = (text) => writeUserFrame(claudeProcess, text, (err) => {
|
|
19663
|
+
logger.log(`[Session ${sessionId}] Claude stdin write failed: ${err?.message || err}`);
|
|
19664
|
+
});
|
|
19475
19665
|
const restartClaudeHandler = async (opts) => {
|
|
19476
19666
|
logger.log(`[Session ${sessionId}] Restart Claude requested`);
|
|
19477
19667
|
if (isRestartingClaude || isSwitchingMode) {
|
|
@@ -19510,6 +19700,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19510
19700
|
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
19511
19701
|
sessionService.updateMetadata(sessionMetadata);
|
|
19512
19702
|
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
19703
|
+
if ((sessionMetadata.messageQueue?.length ?? 0) > 0 && !trackedSession?.stopped) {
|
|
19704
|
+
setTimeout(() => processMessageQueueRef?.(), 300);
|
|
19705
|
+
}
|
|
19513
19706
|
if (beforeRespawnError) {
|
|
19514
19707
|
return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
|
|
19515
19708
|
}
|
|
@@ -19635,12 +19828,28 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19635
19828
|
const btwProcess = spawn("claude", btwArgs, {
|
|
19636
19829
|
cwd: directory,
|
|
19637
19830
|
env: btwEnv,
|
|
19638
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
19831
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
19832
|
+
detached: true
|
|
19833
|
+
// #0820: own process group so the timeout can kill grandchildren
|
|
19639
19834
|
});
|
|
19640
19835
|
let btwResult = "";
|
|
19641
19836
|
let btwBuffer = "";
|
|
19642
19837
|
let btwStderr = "";
|
|
19643
19838
|
let settled = false;
|
|
19839
|
+
const btwTimer = setTimeout(() => {
|
|
19840
|
+
if (settled) return;
|
|
19841
|
+
settled = true;
|
|
19842
|
+
killHelperTree(btwProcess);
|
|
19843
|
+
const err = new Error(`/btw timed out after ${Math.round(BTW_TIMEOUT_MS / 1e3)}s`);
|
|
19844
|
+
err.retryable = false;
|
|
19845
|
+
reject(err);
|
|
19846
|
+
}, BTW_TIMEOUT_MS);
|
|
19847
|
+
const settleBtw = () => {
|
|
19848
|
+
if (settled) return false;
|
|
19849
|
+
settled = true;
|
|
19850
|
+
clearTimeout(btwTimer);
|
|
19851
|
+
return true;
|
|
19852
|
+
};
|
|
19644
19853
|
btwProcess.stderr?.on("data", (chunk) => {
|
|
19645
19854
|
btwStderr += chunk.toString();
|
|
19646
19855
|
});
|
|
@@ -19660,8 +19869,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19660
19869
|
}
|
|
19661
19870
|
}
|
|
19662
19871
|
} else if (msg.type === "result") {
|
|
19663
|
-
if (
|
|
19664
|
-
settled = true;
|
|
19872
|
+
if (!settleBtw()) continue;
|
|
19665
19873
|
if (msg.is_error) {
|
|
19666
19874
|
const err = new Error(String(msg.result || "error"));
|
|
19667
19875
|
err.retryable = isRetryableRateLimit(String(msg.result || ""), msg.api_error_status);
|
|
@@ -19675,8 +19883,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19675
19883
|
}
|
|
19676
19884
|
});
|
|
19677
19885
|
btwProcess.on("exit", (code) => {
|
|
19678
|
-
if (
|
|
19679
|
-
settled = true;
|
|
19886
|
+
if (!settleBtw()) return;
|
|
19680
19887
|
if (code === 0) {
|
|
19681
19888
|
resolve2(btwResult || "(no response)");
|
|
19682
19889
|
return;
|
|
@@ -19687,8 +19894,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19687
19894
|
reject(err);
|
|
19688
19895
|
});
|
|
19689
19896
|
btwProcess.on("error", (e) => {
|
|
19690
|
-
if (
|
|
19691
|
-
settled = true;
|
|
19897
|
+
if (!settleBtw()) return;
|
|
19692
19898
|
const err = new Error(e.message);
|
|
19693
19899
|
err.retryable = false;
|
|
19694
19900
|
reject(err);
|
|
@@ -19734,15 +19940,26 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19734
19940
|
pendingCompactTurn = isCompactCommand(text);
|
|
19735
19941
|
if (!claudeProcess || claudeProcess.exitCode !== null) {
|
|
19736
19942
|
spawnClaude(text, msgMeta);
|
|
19943
|
+
signalProcessing(true);
|
|
19944
|
+
sessionWasProcessing = true;
|
|
19945
|
+
} else if (writeUserFrameToClaude(text)) {
|
|
19946
|
+
signalProcessing(true);
|
|
19947
|
+
sessionWasProcessing = true;
|
|
19737
19948
|
} else {
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19949
|
+
logger.log(`[Session ${sessionId}] Claude stdin unwritable \u2014 requeueing message for respawn delivery`);
|
|
19950
|
+
const existingQueue = sessionMetadata.messageQueue || [];
|
|
19951
|
+
sessionMetadata = {
|
|
19952
|
+
...sessionMetadata,
|
|
19953
|
+
messageQueue: [...existingQueue, { id: randomUUID$1(), text, createdAt: Date.now(), alreadyStored: true }]
|
|
19954
|
+
};
|
|
19955
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
19956
|
+
signalProcessing(true);
|
|
19957
|
+
sessionWasProcessing = true;
|
|
19958
|
+
try {
|
|
19959
|
+
claudeProcess.kill("SIGTERM");
|
|
19960
|
+
} catch {
|
|
19961
|
+
}
|
|
19743
19962
|
}
|
|
19744
|
-
signalProcessing(true);
|
|
19745
|
-
sessionWasProcessing = true;
|
|
19746
19963
|
},
|
|
19747
19964
|
onAbort: () => {
|
|
19748
19965
|
logger.log(`[Session ${sessionId}] Abort requested`);
|
|
@@ -19844,6 +20061,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
19844
20061
|
isKillingClaude = false;
|
|
19845
20062
|
if (trackedSession?.stopped) return;
|
|
19846
20063
|
spawnClaude(void 0, { permissionMode: normalizedMode });
|
|
20064
|
+
if ((sessionMetadata.messageQueue?.length ?? 0) > 0 && !trackedSession?.stopped) {
|
|
20065
|
+
setTimeout(() => processMessageQueueRef?.(), 300);
|
|
20066
|
+
}
|
|
19847
20067
|
} finally {
|
|
19848
20068
|
isKillingClaude = false;
|
|
19849
20069
|
isSwitchingMode = false;
|
|
@@ -20107,11 +20327,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20107
20327
|
});
|
|
20108
20328
|
},
|
|
20109
20329
|
onIssue: async (params) => {
|
|
20110
|
-
const { issueRpc } = await import('./rpc-
|
|
20330
|
+
const { issueRpc } = await import('./rpc-Q4PWHeNn.mjs');
|
|
20111
20331
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20112
20332
|
},
|
|
20113
20333
|
onWorkflow: async (params) => {
|
|
20114
|
-
const { workflowRpc } = await import('./rpc-
|
|
20334
|
+
const { workflowRpc } = await import('./rpc-C7uDtxFw.mjs');
|
|
20115
20335
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20116
20336
|
},
|
|
20117
20337
|
onRipgrep: async (args, cwd) => {
|
|
@@ -20257,12 +20477,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20257
20477
|
try {
|
|
20258
20478
|
if (!claudeProcess || claudeProcess.exitCode !== null) {
|
|
20259
20479
|
spawnClaude(next.text);
|
|
20260
|
-
} else {
|
|
20261
|
-
const stdinMsg = JSON.stringify({
|
|
20262
|
-
type: "user",
|
|
20263
|
-
message: { role: "user", content: next.text }
|
|
20264
|
-
});
|
|
20265
|
-
claudeProcess.stdin?.write(stdinMsg + "\n");
|
|
20480
|
+
} else if (writeUserFrameToClaude(next.text)) {
|
|
20266
20481
|
if (claudeResumeId && !trackedSession?.stopped) {
|
|
20267
20482
|
saveSession({
|
|
20268
20483
|
sessionId,
|
|
@@ -20276,6 +20491,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20276
20491
|
wasProcessing: true
|
|
20277
20492
|
});
|
|
20278
20493
|
}
|
|
20494
|
+
} else {
|
|
20495
|
+
logger.log(`[Session ${sessionId}] Claude stdin unwritable during queue drain \u2014 restoring head + respawning`);
|
|
20496
|
+
sessionMetadata = { ...sessionMetadata, messageQueue: [next, ...remaining] };
|
|
20497
|
+
sessionService.updateMetadata(sessionMetadata);
|
|
20498
|
+
sessionWasProcessing = false;
|
|
20499
|
+
try {
|
|
20500
|
+
claudeProcess.kill("SIGTERM");
|
|
20501
|
+
} catch {
|
|
20502
|
+
}
|
|
20279
20503
|
}
|
|
20280
20504
|
} catch (err) {
|
|
20281
20505
|
logger.log(`[Session ${sessionId}] Error in processMessageQueue spawn: ${err.message}`);
|
|
@@ -20316,7 +20540,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20316
20540
|
logger.log(`[Session ${sessionId}] re-kick skipped \u2014 loop is resource-limit-stalled; human must run \`loop --max N\``);
|
|
20317
20541
|
return;
|
|
20318
20542
|
}
|
|
20319
|
-
if (s.active === false || s.phase === "done") {
|
|
20543
|
+
if (s.active === false || s.phase === "done" || s.phase === "dormant") {
|
|
20320
20544
|
try {
|
|
20321
20545
|
const lp = join(getLoopDir(directory, sessionId), "loop-state.json");
|
|
20322
20546
|
atomicWriteLoopState(lp, {
|
|
@@ -20326,6 +20550,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20326
20550
|
iteration: 0,
|
|
20327
20551
|
completed_at: void 0,
|
|
20328
20552
|
gave_up_reason: void 0,
|
|
20553
|
+
verify_errors: 0,
|
|
20554
|
+
// fresh verification budget on re-arm
|
|
20329
20555
|
stall_hinted: false,
|
|
20330
20556
|
resumed_at: Date.now()
|
|
20331
20557
|
// #0156: re-arm the stall hint
|
|
@@ -20333,7 +20559,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20333
20559
|
} catch {
|
|
20334
20560
|
}
|
|
20335
20561
|
}
|
|
20336
|
-
logger.log(`[Session ${sessionId}] issue change \u2192 re-kicking loop (budget remaining)`);
|
|
20562
|
+
logger.log(`[Session ${sessionId}] issue change \u2192 re-kicking loop (${s.phase === "dormant" ? "was dormant/armed" : "budget remaining"})`);
|
|
20337
20563
|
enqueueLoopContinue();
|
|
20338
20564
|
processMessageQueueRef?.();
|
|
20339
20565
|
};
|
|
@@ -20781,11 +21007,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
20781
21007
|
});
|
|
20782
21008
|
},
|
|
20783
21009
|
onIssue: async (params) => {
|
|
20784
|
-
const { issueRpc } = await import('./rpc-
|
|
21010
|
+
const { issueRpc } = await import('./rpc-Q4PWHeNn.mjs');
|
|
20785
21011
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
20786
21012
|
},
|
|
20787
21013
|
onWorkflow: async (params) => {
|
|
20788
|
-
const { workflowRpc } = await import('./rpc-
|
|
21014
|
+
const { workflowRpc } = await import('./rpc-C7uDtxFw.mjs');
|
|
20789
21015
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
20790
21016
|
},
|
|
20791
21017
|
onRipgrep: async (args, cwd) => {
|
|
@@ -21129,7 +21355,9 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21129
21355
|
return;
|
|
21130
21356
|
}
|
|
21131
21357
|
if (process.env.SVAMP_LOOP_VERIFY === "0") {
|
|
21132
|
-
|
|
21358
|
+
const killPhase = terminalPhaseForDone(resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd }));
|
|
21359
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: killPhase === "dormant", phase: killPhase, completed_at: now, ledger, verify_errors: 0 });
|
|
21360
|
+
if (killPhase === "dormant") sessionService.pushMessage({ type: "message", message: "\u{1F319} Backlog clear \u2014 loop is armed and watching for new issues." }, "event");
|
|
21133
21361
|
checkSvampConfig?.();
|
|
21134
21362
|
return;
|
|
21135
21363
|
}
|
|
@@ -21171,15 +21399,48 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21171
21399
|
logger.log(`[Session ${sessionId}] [loop-persist] stuck-stop: ${decision.reason}`);
|
|
21172
21400
|
checkSvampConfig?.();
|
|
21173
21401
|
}
|
|
21402
|
+
} else if (result.action === "verify_error") {
|
|
21403
|
+
const decision = decideVerifyErrorHandling({
|
|
21404
|
+
priorErrors: ls.verify_errors,
|
|
21405
|
+
maxRetries: resolveVerifyErrorRetries(process.env.SVAMP_LOOP_VERIFY_ERROR_RETRIES),
|
|
21406
|
+
reason: result.reason
|
|
21407
|
+
});
|
|
21408
|
+
if (decision.action === "retry") {
|
|
21409
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "continue", holds, started_at: startedAt, ledger, verify_errors: decision.attempt });
|
|
21410
|
+
sessionService.pushMessage({ type: "message", message: `\u26A0\uFE0F Loop verification hit a transient error \u2014 ${decision.reason}`, level: "warning" }, "event");
|
|
21411
|
+
dispatchAcpPrompt(`Continue toward the goal: ${ls.goal_task || ls.until || "the task"}. The independent completion check could not run (${result.reason}); keep working \u2014 it will be re-verified. Do NOT treat this as done.`, "\u26A0\uFE0F Re-verifying loop");
|
|
21412
|
+
} else {
|
|
21413
|
+
const parkPhase = terminalPhaseForDone(resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd }));
|
|
21414
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: parkPhase === "dormant", phase: parkPhase, completed_at: Date.now(), holds, ledger, verify_errors: 0 });
|
|
21415
|
+
sessionService.pushMessage({ type: "message", message: parkPhase === "dormant" ? `\u{1F319} Loop parked (armed) after repeated verification errors \u2014 re-arms on new work. ${decision.reason}` : `\u26A0\uFE0F Loop stopped after repeated verification errors \u2014 ${decision.reason}`, level: "warning" }, "event");
|
|
21416
|
+
checkSvampConfig?.();
|
|
21417
|
+
}
|
|
21174
21418
|
} else {
|
|
21175
|
-
|
|
21419
|
+
const donePhase = terminalPhaseForDone(resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd }));
|
|
21420
|
+
if (donePhase === "dormant") {
|
|
21421
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "dormant", durable: true, completed_at: Date.now(), holds, ledger, verify_errors: 0 });
|
|
21422
|
+
sessionService.pushMessage({ type: "message", message: "\u{1F319} Backlog clear \u2014 loop is armed and watching for new issues." }, "event");
|
|
21423
|
+
} else {
|
|
21424
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: false, phase: "done", completed_at: Date.now(), holds, ledger, verify_errors: 0 });
|
|
21425
|
+
}
|
|
21176
21426
|
checkSvampConfig?.();
|
|
21177
21427
|
}
|
|
21178
21428
|
} catch (e) {
|
|
21179
|
-
logger.log(`[Session ${sessionId}] verifyAcpLoopCompletion error \u2014 failing
|
|
21429
|
+
logger.log(`[Session ${sessionId}] verifyAcpLoopCompletion error \u2014 failing closed (retry/park): ${e?.message || e}`);
|
|
21180
21430
|
try {
|
|
21181
|
-
|
|
21182
|
-
|
|
21431
|
+
const decision = decideVerifyErrorHandling({
|
|
21432
|
+
priorErrors: ls.verify_errors,
|
|
21433
|
+
maxRetries: resolveVerifyErrorRetries(process.env.SVAMP_LOOP_VERIFY_ERROR_RETRIES),
|
|
21434
|
+
reason: `verify threw: ${e?.message || e}`
|
|
21435
|
+
});
|
|
21436
|
+
if (decision.action === "retry") {
|
|
21437
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: true, phase: "continue", holds, started_at: startedAt, ledger, verify_errors: decision.attempt });
|
|
21438
|
+
dispatchAcpPrompt(`Continue toward the goal: ${ls.goal_task || ls.until || "the task"}. The completion check could not run; keep working \u2014 it will be re-verified. Not done.`, "\u26A0\uFE0F Re-verifying loop");
|
|
21439
|
+
} else {
|
|
21440
|
+
const parkPhase = terminalPhaseForDone(resolveLoopDurable({ durable: ls.durable, oracle: oracleCmd }));
|
|
21441
|
+
writeGoalLoopState(directory, sessionId, { ...ls, active: parkPhase === "dormant", phase: parkPhase, completed_at: Date.now(), ledger, verify_errors: 0 });
|
|
21442
|
+
checkSvampConfig?.();
|
|
21443
|
+
}
|
|
21183
21444
|
} catch {
|
|
21184
21445
|
}
|
|
21185
21446
|
} finally {
|
|
@@ -21743,7 +22004,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21743
22004
|
}
|
|
21744
22005
|
if (persistedSessions.length > 0) {
|
|
21745
22006
|
try {
|
|
21746
|
-
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-
|
|
22007
|
+
const { awaitClaudeVersionReady } = await import('./pinnedClaudeCode-mM9Q6Q2G.mjs');
|
|
21747
22008
|
await awaitClaudeVersionReady();
|
|
21748
22009
|
} catch {
|
|
21749
22010
|
}
|
|
@@ -21776,12 +22037,26 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21776
22037
|
break;
|
|
21777
22038
|
}
|
|
21778
22039
|
}
|
|
22040
|
+
const _loopArmed = !isOrphaned && isLoopArmedForSession(persisted.directory, persisted.sessionId);
|
|
21779
22041
|
const _restoreAction = classifyRestoreContinuation({
|
|
21780
22042
|
wasProcessing: !!persisted.wasProcessing,
|
|
21781
22043
|
hasResumeId: !!persisted.claudeResumeId,
|
|
21782
22044
|
isOrphaned: !!isOrphaned,
|
|
21783
|
-
loopActive: isLoopActiveForSession(persisted.directory, persisted.sessionId)
|
|
22045
|
+
loopActive: isLoopActiveForSession(persisted.directory, persisted.sessionId),
|
|
22046
|
+
loopArmed: _loopArmed,
|
|
22047
|
+
armedHasPendingWork: _loopArmed ? safeBacklogPendingCount(persisted.directory, persisted.sessionId) > 0 : false
|
|
21784
22048
|
});
|
|
22049
|
+
if (_loopArmed) {
|
|
22050
|
+
logger.log(`[loop] Restored DORMANT loop for ${persisted.sessionId} \u2192 ${_restoreAction === "loop-resume" ? "re-arming (pending work found)" : "staying dormant/watching (backlog empty)"}`);
|
|
22051
|
+
if (_restoreAction === "loop-resume") {
|
|
22052
|
+
try {
|
|
22053
|
+
const lp = join(getLoopDir(persisted.directory, persisted.sessionId), "loop-state.json");
|
|
22054
|
+
const cur = readLoopState(persisted.directory, persisted.sessionId);
|
|
22055
|
+
if (cur) atomicWriteLoopState(lp, { ...cur, active: true, phase: "continue", completed_at: void 0, verify_errors: 0, resumed_at: Date.now() });
|
|
22056
|
+
} catch {
|
|
22057
|
+
}
|
|
22058
|
+
}
|
|
22059
|
+
}
|
|
21785
22060
|
if (_restoreAction === "auto-continue") {
|
|
21786
22061
|
sessionsToAutoContinue.push(persisted.sessionId);
|
|
21787
22062
|
} else if (_restoreAction === "loop-resume") {
|
|
@@ -21899,18 +22174,44 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21899
22174
|
logger.log(`Session ${sessionId} RPC handlers not found for loop resume`);
|
|
21900
22175
|
continue;
|
|
21901
22176
|
}
|
|
22177
|
+
const loopTurnFingerprint = () => {
|
|
22178
|
+
try {
|
|
22179
|
+
const s = readLoopState(sessDir, sessionId);
|
|
22180
|
+
return `${s?.phase || ""}|${s?.holds || 0}|${s?.ledger?.turns || 0}`;
|
|
22181
|
+
} catch {
|
|
22182
|
+
return "";
|
|
22183
|
+
}
|
|
22184
|
+
};
|
|
22185
|
+
const sendResume = async (tag) => {
|
|
22186
|
+
const prompt = loopResumeMessage(sessDir, sessionId);
|
|
22187
|
+
await rpc.sendMessage(
|
|
22188
|
+
JSON.stringify({
|
|
22189
|
+
role: "user",
|
|
22190
|
+
content: { type: "text", text: prompt },
|
|
22191
|
+
meta: { sentFrom: "svamp-daemon-loop-resume" }
|
|
22192
|
+
})
|
|
22193
|
+
);
|
|
22194
|
+
logger.log(`Resumed loop for session ${sessionId}${tag}`);
|
|
22195
|
+
};
|
|
21902
22196
|
setTimeout(async () => {
|
|
21903
22197
|
try {
|
|
21904
22198
|
if (!isLoopActiveForSession(sessDir, sessionId)) return;
|
|
21905
|
-
const
|
|
21906
|
-
await
|
|
21907
|
-
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
22199
|
+
const fpBefore = loopTurnFingerprint();
|
|
22200
|
+
await sendResume("");
|
|
22201
|
+
const graceMs = Math.max(5e3, Number(process.env.SVAMP_LOOP_RESUME_WATCHDOG_MS) || 45e3);
|
|
22202
|
+
setTimeout(() => {
|
|
22203
|
+
try {
|
|
22204
|
+
if (!isLoopActiveForSession(sessDir, sessionId)) return;
|
|
22205
|
+
const live = Array.from(pidToTrackedSession.values()).find((s) => s.svampSessionId === sessionId && !s.stopped);
|
|
22206
|
+
const lifecycle = live?.hyphaService?.getMetadata()?.lifecycleState;
|
|
22207
|
+
if (lifecycle && lifecycle !== "idle") return;
|
|
22208
|
+
if (loopTurnFingerprint() !== fpBefore) return;
|
|
22209
|
+
logger.log(`[loop] resume watchdog: session ${sessionId} loop still active but never advanced after ${graceMs}ms \u2014 re-issuing /goal once`);
|
|
22210
|
+
void sendResume(" (watchdog re-issue)").catch(() => {
|
|
22211
|
+
});
|
|
22212
|
+
} catch {
|
|
22213
|
+
}
|
|
22214
|
+
}, graceMs);
|
|
21914
22215
|
} catch (err) {
|
|
21915
22216
|
logger.log(`Failed to resume loop for session ${sessionId}: ${err.message}`);
|
|
21916
22217
|
}
|
|
@@ -21948,7 +22249,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
21948
22249
|
const PING_TIMEOUT_MS = 15e3;
|
|
21949
22250
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
21950
22251
|
const RECONNECT_JITTER_MS = 2500;
|
|
21951
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
22252
|
+
const { WorkflowScheduler } = await import('./scheduler-B1xThM3A.mjs');
|
|
21952
22253
|
const workflowProjectRoots = () => {
|
|
21953
22254
|
const dirs = /* @__PURE__ */ new Set();
|
|
21954
22255
|
for (const s of pidToTrackedSession.values()) {
|