svamp-cli 0.2.342 → 0.2.346
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/bin/skills/artifact/SKILL.md +1 -1
- package/bin/skills/crew/SKILL.md +21 -3
- package/dist/DefaultTransport-vfp78fug.mjs +47 -0
- package/dist/GeminiTransport-3OkhtFRO.mjs +130 -0
- package/dist/KimiTransport-D8n009Tr.mjs +141 -0
- package/dist/acpAgentConfig-CObOPlA8.mjs +48 -0
- package/dist/acpBackend-BZ1MJhQV.mjs +922 -0
- package/dist/{adminCommands-CRTBDZan.mjs → adminCommands-BvsYnRDA.mjs} +2 -18
- package/dist/{agentCommands-BuO988tJ.mjs → agentCommands-B2Nl7Fof.mjs} +6 -23
- package/dist/api-BHjyC0Ph.mjs +157 -0
- package/dist/{auth--WS0wezs.mjs → auth-CRz8TsEl.mjs} +1 -22
- package/dist/bootStatus-DqtNgJch.mjs +143 -0
- package/dist/claudeAuth-iAerdaMr.mjs +247 -0
- package/dist/cli.mjs +2939 -5
- package/dist/codexAppServerBackend-VGsYtbqg.mjs +786 -0
- package/dist/codexProvider-BtxYc5Rj.mjs +68 -0
- package/dist/{commands-DXM17M6n.mjs → commands-BB1Kcyt-.mjs} +13 -19
- package/dist/{commands-DU4B-4Gh.mjs → commands-BOpoVBOM.mjs} +55 -25
- package/dist/{commands-C0HQiu-S.mjs → commands-C2aODeVc.mjs} +58 -33
- package/dist/{commands-Cwxy-C5N.mjs → commands-CiRCwjkJ.mjs} +10 -32
- package/dist/{commands-DDuGBSsz.mjs → commands-D1pZq3d4.mjs} +103 -41
- package/dist/{commands-BKb1g8Gq.mjs → commands-D69mnkYu.mjs} +20 -30
- package/dist/{commands-BsKgZ5sQ.mjs → commands-DW5RnHZw.mjs} +2 -19
- package/dist/{commands-DXLOKJOj.mjs → commands-H4PsTGn8.mjs} +58 -26
- package/dist/cron-hHGdb5kU.mjs +132 -0
- package/dist/detectIsolation-CzoDCEnh.mjs +319 -0
- package/dist/envFlag-COOuJ3AH.mjs +11 -0
- package/dist/fileLock-BMPwCrB6.mjs +80 -0
- package/dist/{fleet-BHzlAvnu.mjs → fleet-DM_Q7w69.mjs} +85 -24
- package/dist/flushExit-C-i6SGjj.mjs +37 -0
- package/dist/friendlyName-1UZcyDhu.mjs +586 -0
- package/dist/{headlessCli-C2gvOpOt.mjs → headlessCli-C8HzQvis.mjs} +3 -21
- package/dist/hookSettings-DYMTUgVA.mjs +10399 -0
- package/dist/{httpServer-Be1Hcddr.mjs → httpServer-BzEDbOGn.mjs} +14 -5
- package/dist/hyphaClient-ybivOjZo.mjs +49 -0
- package/dist/index.mjs +40 -8
- package/dist/instance-CeHz5C8v.mjs +203 -0
- package/dist/kimiProvider-DbxODebR.mjs +69 -0
- package/dist/kimiSetup-NGsoYB5h.mjs +48 -0
- package/dist/loopVerify-B47sSjxe.mjs +444 -0
- package/dist/machineTools-DygM1jXW.mjs +580 -0
- package/dist/model-CUHojegn.mjs +66 -0
- package/dist/{notifyCommands-0aZUwKQK.mjs → notifyCommands-D-IKIxiq.mjs} +3 -18
- package/dist/package-DtuINqf5.mjs +64 -0
- package/dist/pinnedCodex-XW4qvOAe.mjs +11 -0
- package/dist/pinnedNono-5ZHb6328.mjs +15 -0
- package/dist/{rpc-rAQQ1q8t.mjs → rpc-BB6RF4-F.mjs} +38 -6
- package/dist/{rpc-B2Lc2BAB.mjs → rpc-Cf0grcru.mjs} +28 -20
- package/dist/{run-C7QVfyw4.mjs → run-ChdsgaSD.mjs} +17 -9
- package/dist/run-DkjwF-zA.mjs +9695 -0
- package/dist/sandboxDetect-Cdzsj5TC.mjs +19 -0
- package/dist/{scheduler-CzF4lOe_.mjs → scheduler-BY30ZrB9.mjs} +38 -6
- package/dist/securityContext-Cm5UTCuD.mjs +218 -0
- package/dist/serveAuth-DbVLRCRR.mjs +418 -0
- package/dist/{serveCommands-EIEqdaVu.mjs → serveCommands-CUHUiYnZ.mjs} +13 -34
- package/dist/sharingNotificationSync-qGIQ-3yf.mjs +359 -0
- package/dist/{sideband-BoutiRJi.mjs → sideband-68HhGT0i.mjs} +1 -21
- package/dist/store-Bn0ByYev.mjs +347 -0
- package/dist/store-fi0f-ff1.mjs +184 -0
- package/dist/types-57J3Re0n.mjs +17 -0
- package/dist/wantsHelp-6bMOeNUz.mjs +5 -0
- package/dist/wrapCommand-CdNawSaa.mjs +117 -0
- package/package.json +3 -3
- package/dist/cli-B24CmIli.mjs +0 -2780
- package/dist/package-CTbGYaJG.mjs +0 -64
- package/dist/run-CF4kAVPW.mjs +0 -26508
- package/dist/sandboxDetect-DNTcbgWD.mjs +0 -12
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { n as normalizeAllowedUser } from './types-57J3Re0n.mjs';
|
|
2
|
+
import { c as getSvampWebBaseUrl } from './instance-CeHz5C8v.mjs';
|
|
3
|
+
import { l as listIssues, i as isPendingIssue, j as isVisibleTo } from './store-Bn0ByYev.mjs';
|
|
4
|
+
|
|
5
|
+
function getShareBaseUrl() {
|
|
6
|
+
return getSvampWebBaseUrl();
|
|
7
|
+
}
|
|
8
|
+
function buildSessionShareUrl(input) {
|
|
9
|
+
const params = new URLSearchParams();
|
|
10
|
+
if (input.workspace) params.set("ws", input.workspace);
|
|
11
|
+
if (input.machineServiceId) params.set("svc", input.machineServiceId);
|
|
12
|
+
const query = params.toString();
|
|
13
|
+
return `${getShareBaseUrl()}/share/${input.sessionId}${query ? `?${query}` : ""}`;
|
|
14
|
+
}
|
|
15
|
+
function buildMachineShareUrl(input) {
|
|
16
|
+
const params = new URLSearchParams();
|
|
17
|
+
if (input.workspace) params.set("ws", input.workspace);
|
|
18
|
+
const query = params.toString();
|
|
19
|
+
return `${getShareBaseUrl()}/share/machine/${input.machineId}${query ? `?${query}` : ""}`;
|
|
20
|
+
}
|
|
21
|
+
const DAEMON_SHARE_EMAILS_ENV = "SVAMP_SHARE_EMAILS";
|
|
22
|
+
function parseDaemonShareEmails(raw) {
|
|
23
|
+
if (!raw) return [];
|
|
24
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const part of raw.split(",")) {
|
|
27
|
+
const email = part.trim();
|
|
28
|
+
if (!email || !email.includes("@")) continue;
|
|
29
|
+
const k = email.toLowerCase();
|
|
30
|
+
if (seen.has(k)) continue;
|
|
31
|
+
seen.add(k);
|
|
32
|
+
out.push(email);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function applyDaemonShareSeed(sharing, seedEmails, ownerEmail) {
|
|
37
|
+
const base = sharing ? { ...sharing, allowedUsers: [...sharing.allowedUsers || []] } : { enabled: true, owner: ownerEmail || "", allowedUsers: [] };
|
|
38
|
+
base.enabled = true;
|
|
39
|
+
if (!base.owner && ownerEmail) base.owner = ownerEmail;
|
|
40
|
+
const existing = new Set(base.allowedUsers.map((u) => u.email.toLowerCase()));
|
|
41
|
+
const addedEmails = [];
|
|
42
|
+
for (const email of seedEmails) {
|
|
43
|
+
if (existing.has(email.toLowerCase())) continue;
|
|
44
|
+
base.allowedUsers.push(
|
|
45
|
+
normalizeAllowedUser({ email, addedBy: "daemon-share-flag" }, base.owner || "daemon-share-flag")
|
|
46
|
+
);
|
|
47
|
+
existing.add(email.toLowerCase());
|
|
48
|
+
addedEmails.push(email);
|
|
49
|
+
}
|
|
50
|
+
return { sharing: base, addedEmails };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const UNBOUNDED_LOOP_ITERATIONS = 1e6;
|
|
54
|
+
function loopCancelledDuringVerify(current) {
|
|
55
|
+
return !!current && (current.phase === "cancelled" || current.active === false);
|
|
56
|
+
}
|
|
57
|
+
function backlogOraclePending(projectRoot, sessionId) {
|
|
58
|
+
const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
|
|
59
|
+
return listIssues(projectRoot).filter(inScope).filter(isPendingIssue).map((i) => ({ id: i.id }));
|
|
60
|
+
}
|
|
61
|
+
function isBacklogDrainerOracle(oracle) {
|
|
62
|
+
if (!oracle || typeof oracle !== "string") return false;
|
|
63
|
+
return /(^|\s)svamp\s+issue\s+pending(\s|$)/.test(oracle.trim());
|
|
64
|
+
}
|
|
65
|
+
function oracleScopeSessionId(oracle, selfSessionId) {
|
|
66
|
+
if (!oracle || typeof oracle !== "string") return void 0;
|
|
67
|
+
const explicit = /(^|\s)--session[=\s]+(\S+)/.exec(oracle);
|
|
68
|
+
if (explicit) return explicit[2];
|
|
69
|
+
if (/(^|\s)--mine(\s|$)/.test(oracle)) return selfSessionId;
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
function resolveLoopDurable(cfg) {
|
|
73
|
+
if (typeof cfg.durable === "boolean") return cfg.durable;
|
|
74
|
+
return isBacklogDrainerOracle(cfg.oracle);
|
|
75
|
+
}
|
|
76
|
+
function shouldRearmDormantLoop(s, pendingCount, sessionId) {
|
|
77
|
+
if (!s || s.phase !== "dormant") return false;
|
|
78
|
+
if (typeof s.session_id === "string" && sessionId && s.session_id !== sessionId) return false;
|
|
79
|
+
if (!isBacklogDrainerOracle(typeof s.oracle === "string" ? s.oracle : void 0)) return false;
|
|
80
|
+
return pendingCount > 0;
|
|
81
|
+
}
|
|
82
|
+
function issuesClosedSince(projectRoot, sinceMs) {
|
|
83
|
+
const all = listIssues(projectRoot, { includeArchived: true, status: "archived" });
|
|
84
|
+
if (!sinceMs || !Number.isFinite(sinceMs)) return all;
|
|
85
|
+
return all.filter((i) => {
|
|
86
|
+
if (!i.closed) return false;
|
|
87
|
+
const t = Date.parse(i.closed);
|
|
88
|
+
return Number.isFinite(t) && t >= sinceMs;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function issuesPausedNow(projectRoot, sessionId) {
|
|
92
|
+
const inScope = (i) => !sessionId || isVisibleTo(i, sessionId);
|
|
93
|
+
return listIssues(projectRoot, { status: "paused" }).filter(inScope);
|
|
94
|
+
}
|
|
95
|
+
function buildEvaluatorPrompt(o) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push("You are an INDEPENDENT, skeptical reviewer. You did NOT write the code under review.");
|
|
98
|
+
lines.push('A self-verifying loop has just reported its work COMPLETE. Decide whether it is GENUINELY and COMPLETELY done. Default to "continue" on ANY doubt \u2014 a false "done" is far worse than one more iteration.');
|
|
99
|
+
lines.push("");
|
|
100
|
+
if (o.task) lines.push(`LOOP TASK / GOAL:
|
|
101
|
+
${o.task.trim()}`);
|
|
102
|
+
if (o.until) lines.push(`DONE-WHEN CRITERIA:
|
|
103
|
+
${o.until.trim()}`);
|
|
104
|
+
lines.push("");
|
|
105
|
+
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.");
|
|
106
|
+
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.');
|
|
107
|
+
if (o.pausedIssues && o.pausedIssues.length) {
|
|
108
|
+
lines.push("");
|
|
109
|
+
lines.push('ISSUES PAUSED (removed from the oracle \u2014 a pause is a BLOCK ONLY THE USER can clear). For EACH, decide if the pause is LEGITIMATE (a real question only the user can answer, or a clear delegation to another session) or GAMING (paused to dodge work: no reason, or a "question" the agent could and should have resolved itself). Reject "done" if ANY goal-relevant pause is gaming \u2014 the agent must resume and do the work, not park it:');
|
|
110
|
+
for (const i of o.pausedIssues.slice(0, 40)) {
|
|
111
|
+
const orig = (i.original || "").trim().replace(/\s+/g, " ").slice(0, 200);
|
|
112
|
+
const q = (i.question?.text || "").trim().replace(/\s+/g, " ").slice(0, 240);
|
|
113
|
+
lines.push(` - #${i.id} ${i.title}${orig ? ` \u2014 original: "${orig}"` : ""} \u2014 pause reason: ${q ? `"${q}"` : "(NONE GIVEN \u2014 strong sign of gaming)"}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (o.oracle) {
|
|
117
|
+
lines.push("");
|
|
118
|
+
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.`);
|
|
119
|
+
}
|
|
120
|
+
if (o.closedIssues.length) {
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push("ISSUES CLOSED DURING THIS LOOP \u2014 verify EACH was genuinely resolved by the real change (not closed by self-assertion). Re-read the ORIGINAL request behind each, not just its title:");
|
|
123
|
+
for (const i of o.closedIssues.slice(0, 40)) {
|
|
124
|
+
const orig = (i.original || "").trim().replace(/\s+/g, " ").slice(0, 200);
|
|
125
|
+
const vtype = i.verify?.type ? ` [verify:${i.verify.type}]` : "";
|
|
126
|
+
lines.push(` - #${i.id} ${i.title}${vtype}${orig ? ` \u2014 original: "${orig}"` : ""}`);
|
|
127
|
+
}
|
|
128
|
+
lines.push('Reject "done" if ANY closed issue was not genuinely resolved by the actual change.');
|
|
129
|
+
} else {
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push("No issues were closed during this loop. Judge whether the task/criteria are genuinely met from the USER's perspective, end-to-end.");
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push("Return ONLY a JSON object on the LAST line, nothing after it:");
|
|
135
|
+
lines.push('{"verdict":"done"|"continue","reason":"<concise>","guidance":"<what to fix if continue>"}');
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function buildGoalCondition(o) {
|
|
139
|
+
const parts = [];
|
|
140
|
+
if (o.task) parts.push(o.task.trim());
|
|
141
|
+
if (o.until) parts.push(`Done when: ${o.until.trim()}.`);
|
|
142
|
+
if (o.oracle) parts.push(`Verify by running \`${o.oracle.trim()}\` at the end of every turn and pasting its output; it must succeed (exit 0 / no pending work).`);
|
|
143
|
+
if (!o.until && !o.oracle && o.task) parts.push("Done when the task is fully complete and you have shown the evidence that proves it.");
|
|
144
|
+
parts.push(`Or stop after ${o.maxIterations && o.maxIterations > 0 ? o.maxIterations : 20} turns.`);
|
|
145
|
+
if (o.inboxDrain !== false) {
|
|
146
|
+
parts.push("(\u{1F4EC} Inbox: at each turn, BRIEFLY run `svamp session inbox list` and handle anything relevant (reply/triage) before continuing \u2014 but do NOT derail the task to over-service ambient chatter. This drains queued non-urgent peer mail at loop pace; urgent/VIP already interrupt separately. It is NOT a completion criterion.)");
|
|
147
|
+
}
|
|
148
|
+
return parts.join(" ");
|
|
149
|
+
}
|
|
150
|
+
const HOUR_MS = 36e5;
|
|
151
|
+
function sumUsageTokens(usage) {
|
|
152
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
153
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
154
|
+
return n(usage.input_tokens) + n(usage.output_tokens) + n(usage.cache_creation_input_tokens) + Math.round(n(usage.cache_read_input_tokens) * 0.1);
|
|
155
|
+
}
|
|
156
|
+
function accumulateLedger(prev, sample) {
|
|
157
|
+
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: [] };
|
|
158
|
+
const addTurns = sample.turns > 0 ? sample.turns : 0;
|
|
159
|
+
const addTokens = sample.tokens > 0 ? sample.tokens : 0;
|
|
160
|
+
const window = [...base.window, [sample.ts, addTokens]].filter(([ts]) => sample.ts - ts < HOUR_MS);
|
|
161
|
+
return { turns: base.turns + addTurns, tokens: base.tokens + addTokens, window };
|
|
162
|
+
}
|
|
163
|
+
function tokensInLastHour(ledger, now) {
|
|
164
|
+
if (!ledger || !Array.isArray(ledger.window)) return 0;
|
|
165
|
+
return ledger.window.filter(([ts]) => now - ts < HOUR_MS).reduce((s, [, t]) => s + (t > 0 ? t : 0), 0);
|
|
166
|
+
}
|
|
167
|
+
function isCostCeiling(kind) {
|
|
168
|
+
return !!kind && kind !== "iteration";
|
|
169
|
+
}
|
|
170
|
+
function isHardBudgetStop(kind) {
|
|
171
|
+
return isCostCeiling(kind) || kind === "iteration";
|
|
172
|
+
}
|
|
173
|
+
function rearmStartedAt(startedAt, completedAt, now) {
|
|
174
|
+
const fin = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
175
|
+
if (!fin(startedAt)) return startedAt;
|
|
176
|
+
if (!fin(completedAt)) return startedAt;
|
|
177
|
+
if (completedAt < startedAt) return startedAt;
|
|
178
|
+
const dormantMs = now - completedAt;
|
|
179
|
+
if (dormantMs <= 0) return startedAt;
|
|
180
|
+
return startedAt + dormantMs;
|
|
181
|
+
}
|
|
182
|
+
function checkLoopBudget(ledger, limits, now, startedAt) {
|
|
183
|
+
const L = limits || {};
|
|
184
|
+
const led = ledger || { turns: 0, tokens: 0, window: [] };
|
|
185
|
+
const pos = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
186
|
+
if (pos(L.max_iterations) && L.max_iterations < UNBOUNDED_LOOP_ITERATIONS && led.turns >= L.max_iterations) {
|
|
187
|
+
return { exceeded: true, reason: `iteration budget reached (${led.turns}/${L.max_iterations} turns)`, kind: "iteration" };
|
|
188
|
+
}
|
|
189
|
+
if (pos(L.max_runtime_sec) && pos(startedAt)) {
|
|
190
|
+
const elapsed = (now - startedAt) / 1e3;
|
|
191
|
+
if (elapsed >= L.max_runtime_sec) {
|
|
192
|
+
return { exceeded: true, reason: `runtime budget reached (${Math.round(elapsed)}s/${L.max_runtime_sec}s)`, kind: "runtime" };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (pos(L.max_tokens) && led.tokens >= L.max_tokens) {
|
|
196
|
+
return { exceeded: true, reason: `token budget reached (${led.tokens}/${L.max_tokens} tokens)`, kind: "tokens" };
|
|
197
|
+
}
|
|
198
|
+
if (pos(L.max_tokens_per_hour)) {
|
|
199
|
+
const rate = tokensInLastHour(led, now);
|
|
200
|
+
if (rate >= L.max_tokens_per_hour) {
|
|
201
|
+
return { exceeded: true, reason: `token-rate budget reached (${rate}/${L.max_tokens_per_hour} tokens in the last hour)`, kind: "tokens_per_hour" };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { exceeded: false };
|
|
205
|
+
}
|
|
206
|
+
function computeSoftTurnCap(hardMax, cadence) {
|
|
207
|
+
const c = Number.isFinite(cadence) && cadence >= 1 ? Math.floor(cadence) : 25;
|
|
208
|
+
if (!Number.isFinite(hardMax) || hardMax <= 0 || hardMax >= UNBOUNDED_LOOP_ITERATIONS) return c;
|
|
209
|
+
if (hardMax <= c) return Math.floor(hardMax);
|
|
210
|
+
const chunks = Math.ceil(hardMax / c);
|
|
211
|
+
return Math.ceil(hardMax / chunks);
|
|
212
|
+
}
|
|
213
|
+
const DEFAULT_LOOP_MAX_EXTENSIONS = 3;
|
|
214
|
+
const DEFAULT_LOOP_EXTENSION_TURNS = 25;
|
|
215
|
+
function resolveMaxExtensions(envValue) {
|
|
216
|
+
if (envValue == null || envValue.trim() === "") return DEFAULT_LOOP_MAX_EXTENSIONS;
|
|
217
|
+
const n = Number(envValue);
|
|
218
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
219
|
+
return DEFAULT_LOOP_MAX_EXTENSIONS;
|
|
220
|
+
}
|
|
221
|
+
function resolveExtensionTurns(envValue) {
|
|
222
|
+
const n = Number(envValue);
|
|
223
|
+
if (Number.isFinite(n) && n >= 1) return Math.floor(n);
|
|
224
|
+
return DEFAULT_LOOP_EXTENSION_TURNS;
|
|
225
|
+
}
|
|
226
|
+
function grantedExtensionCount(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENSIONS) {
|
|
227
|
+
if (!Array.isArray(extensions)) return 0;
|
|
228
|
+
const cap = Number.isFinite(maxExtensions) && maxExtensions >= 0 ? Math.floor(maxExtensions) : DEFAULT_LOOP_MAX_EXTENSIONS;
|
|
229
|
+
return Math.min(extensions.filter((e) => e && typeof e === "object").length, cap);
|
|
230
|
+
}
|
|
231
|
+
function extensionTurns(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENSIONS) {
|
|
232
|
+
if (!Array.isArray(extensions)) return 0;
|
|
233
|
+
const cap = Number.isFinite(maxExtensions) && maxExtensions >= 0 ? Math.floor(maxExtensions) : DEFAULT_LOOP_MAX_EXTENSIONS;
|
|
234
|
+
return extensions.slice(0, cap).reduce((s, e) => {
|
|
235
|
+
const g = e && Number.isFinite(e.grantedTurns) && e.grantedTurns > 0 ? Math.floor(e.grantedTurns) : 0;
|
|
236
|
+
return s + g;
|
|
237
|
+
}, 0);
|
|
238
|
+
}
|
|
239
|
+
function effectiveSoftCap(baseMax, extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENSIONS) {
|
|
240
|
+
if (typeof baseMax !== "number" || !Number.isFinite(baseMax) || baseMax <= 0 || baseMax >= UNBOUNDED_LOOP_ITERATIONS) return baseMax;
|
|
241
|
+
return baseMax + extensionTurns(extensions, maxExtensions);
|
|
242
|
+
}
|
|
243
|
+
function grantLoopExtension(prior, o) {
|
|
244
|
+
const max = Number.isFinite(o.maxExtensions) && o.maxExtensions >= 0 ? Math.floor(o.maxExtensions) : DEFAULT_LOOP_MAX_EXTENSIONS;
|
|
245
|
+
const base = Array.isArray(prior) ? prior.filter((e) => e && typeof e === "object") : [];
|
|
246
|
+
if (base.length >= max) return { granted: false, used: base.length, max };
|
|
247
|
+
const extension = {
|
|
248
|
+
reason: (o.reason || "").trim().slice(0, 500) || "(no reason given)",
|
|
249
|
+
ts: o.ts,
|
|
250
|
+
grantedTurns: Number.isFinite(o.grantedTurns) && o.grantedTurns > 0 ? Math.floor(o.grantedTurns) : DEFAULT_LOOP_EXTENSION_TURNS
|
|
251
|
+
};
|
|
252
|
+
return { granted: true, extension, extensions: [...base, extension], used: base.length + 1, max };
|
|
253
|
+
}
|
|
254
|
+
function summarizeExtensions(extensions, maxExtensions = DEFAULT_LOOP_MAX_EXTENSIONS) {
|
|
255
|
+
const n = grantedExtensionCount(extensions, maxExtensions);
|
|
256
|
+
if (n === 0) return "";
|
|
257
|
+
const turns = extensionTurns(extensions, maxExtensions);
|
|
258
|
+
const reasons = extensions.slice(0, maxExtensions).map((e) => (e?.reason || "").trim()).filter(Boolean).join("; ").slice(0, 300);
|
|
259
|
+
return `${n} extension${n === 1 ? "" : "s"} granted (+${turns} turns)${reasons ? `: ${reasons}` : ""}`;
|
|
260
|
+
}
|
|
261
|
+
const DEFAULT_LOOP_STUCK_CHECKPOINTS = 3;
|
|
262
|
+
const DEFAULT_LOOP_MAX_AUTORESUMES = 3;
|
|
263
|
+
const PROGRESS_HISTORY_CAP = 12;
|
|
264
|
+
function resolveStuckLimit(envValue) {
|
|
265
|
+
const n = Number(envValue);
|
|
266
|
+
if (Number.isFinite(n) && n >= 1) return Math.min(Math.floor(n), PROGRESS_HISTORY_CAP - 1);
|
|
267
|
+
return DEFAULT_LOOP_STUCK_CHECKPOINTS;
|
|
268
|
+
}
|
|
269
|
+
function resolveMaxAutoResumes(envValue) {
|
|
270
|
+
if (envValue == null || envValue.trim() === "") return DEFAULT_LOOP_MAX_AUTORESUMES;
|
|
271
|
+
const s = envValue.trim().toLowerCase();
|
|
272
|
+
if (s === "unbounded" || s === "inf" || s === "infinity" || s === "-1") return Number.POSITIVE_INFINITY;
|
|
273
|
+
const n = Number(envValue);
|
|
274
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
275
|
+
return DEFAULT_LOOP_MAX_AUTORESUMES;
|
|
276
|
+
}
|
|
277
|
+
function recordProgressMarker(history, marker, cap = PROGRESS_HISTORY_CAP) {
|
|
278
|
+
const base = Array.isArray(history) ? history : [];
|
|
279
|
+
const next = [...base, marker];
|
|
280
|
+
return next.length > cap ? next.slice(next.length - cap) : next;
|
|
281
|
+
}
|
|
282
|
+
function assessLoopProgress(history, opts) {
|
|
283
|
+
const stuckLimit = opts && typeof opts.stuckLimit === "number" && opts.stuckLimit >= 1 ? Math.floor(opts.stuckLimit) : DEFAULT_LOOP_STUCK_CHECKPOINTS;
|
|
284
|
+
const h = (Array.isArray(history) ? history : []).filter((m) => m && typeof m.pending === "number" && Number.isFinite(m.pending));
|
|
285
|
+
if (h.length === 0) return { progressing: true, stuckCheckpoints: 0 };
|
|
286
|
+
let best = Infinity;
|
|
287
|
+
let stuck = 0;
|
|
288
|
+
for (const m of h) {
|
|
289
|
+
if (m.pending < best) {
|
|
290
|
+
best = m.pending;
|
|
291
|
+
stuck = 0;
|
|
292
|
+
} else {
|
|
293
|
+
stuck++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { progressing: stuck < stuckLimit, stuckCheckpoints: stuck };
|
|
297
|
+
}
|
|
298
|
+
function decideLoopContinuation(input) {
|
|
299
|
+
if (input.costCeilingBreached) {
|
|
300
|
+
return { action: "stop", kind: "cost", reason: input.costReason || "cost budget exhausted" };
|
|
301
|
+
}
|
|
302
|
+
const stuckLimit = typeof input.stuckLimit === "number" && input.stuckLimit >= 1 ? Math.floor(input.stuckLimit) : DEFAULT_LOOP_STUCK_CHECKPOINTS;
|
|
303
|
+
const maxAutoResumes = typeof input.maxAutoResumes === "number" && input.maxAutoResumes >= 0 ? Math.floor(input.maxAutoResumes) : DEFAULT_LOOP_MAX_AUTORESUMES;
|
|
304
|
+
const autoResumes = typeof input.autoResumes === "number" && input.autoResumes > 0 ? Math.floor(input.autoResumes) : 0;
|
|
305
|
+
const { progressing, stuckCheckpoints } = assessLoopProgress(input.progressHistory, { stuckLimit });
|
|
306
|
+
if (progressing) {
|
|
307
|
+
return { action: "continue", kind: "progress", consumeAutoResume: false, reason: `still making progress (${stuckCheckpoints}/${stuckLimit} stuck checkpoints)` };
|
|
308
|
+
}
|
|
309
|
+
if (autoResumes < maxAutoResumes) {
|
|
310
|
+
const label = Number.isFinite(maxAutoResumes) ? `auto-resume ${autoResumes + 1}/${maxAutoResumes}` : `auto-resume ${autoResumes + 1} (unbounded \u2014 bounded only by the cost/iteration ceiling)`;
|
|
311
|
+
return { action: "continue", kind: "autoresume", consumeAutoResume: true, reason: `no progress across ${stuckCheckpoints} checkpoints \u2014 ${label}` };
|
|
312
|
+
}
|
|
313
|
+
const tail = `no progress across ${stuckCheckpoints} checkpoints; ${maxAutoResumes} auto-resume(s) exhausted`;
|
|
314
|
+
return { action: "stop", kind: "stuck", reason: input.stuckReason ? `${input.stuckReason} \u2014 ${tail}` : tail };
|
|
315
|
+
}
|
|
316
|
+
function terminalPhaseForDone(durable) {
|
|
317
|
+
return durable ? "dormant" : "done";
|
|
318
|
+
}
|
|
319
|
+
const DEFAULT_LOOP_VERIFY_ERROR_RETRIES = 3;
|
|
320
|
+
function resolveVerifyErrorRetries(envValue) {
|
|
321
|
+
if (envValue == null || envValue.trim() === "") return DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
322
|
+
const n = Number(envValue);
|
|
323
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
324
|
+
return DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
325
|
+
}
|
|
326
|
+
function decideVerifyErrorHandling(input) {
|
|
327
|
+
const maxRetries = typeof input.maxRetries === "number" && input.maxRetries >= 0 ? Math.floor(input.maxRetries) : DEFAULT_LOOP_VERIFY_ERROR_RETRIES;
|
|
328
|
+
const attempt = (typeof input.priorErrors === "number" && input.priorErrors > 0 ? Math.floor(input.priorErrors) : 0) + 1;
|
|
329
|
+
const why = input.reason ? String(input.reason).slice(0, 200) : "verification error";
|
|
330
|
+
if (attempt <= maxRetries) {
|
|
331
|
+
return { action: "retry", attempt, reason: `${why} \u2014 retry ${attempt}/${maxRetries} (keeping the loop running, not declaring done)` };
|
|
332
|
+
}
|
|
333
|
+
return { action: "park", attempt, reason: `${why} \u2014 ${maxRetries} verification retries exhausted; parking safely instead of a false "done"` };
|
|
334
|
+
}
|
|
335
|
+
const DEFAULT_LOOP_EVALUATOR_MODEL = "claude-haiku-4-5-20251001";
|
|
336
|
+
function resolveEvaluatorModel(envValue) {
|
|
337
|
+
if (envValue === void 0) return DEFAULT_LOOP_EVALUATOR_MODEL;
|
|
338
|
+
const v = envValue.trim();
|
|
339
|
+
if (!v || v.toLowerCase() === "inherit" || v.toLowerCase() === "default") return void 0;
|
|
340
|
+
return v;
|
|
341
|
+
}
|
|
342
|
+
function parseEvaluatorVerdict(text) {
|
|
343
|
+
if (!text) return null;
|
|
344
|
+
const candidates = [];
|
|
345
|
+
const openStack = [];
|
|
346
|
+
let inStr = false, esc = false;
|
|
347
|
+
for (let i = 0; i < text.length; i++) {
|
|
348
|
+
const c = text[i];
|
|
349
|
+
if (inStr) {
|
|
350
|
+
if (esc) {
|
|
351
|
+
esc = false;
|
|
352
|
+
} else if (c === "\\") {
|
|
353
|
+
esc = true;
|
|
354
|
+
} else if (c === '"') {
|
|
355
|
+
inStr = false;
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (c === '"') {
|
|
360
|
+
inStr = true;
|
|
361
|
+
} else if (c === "{") {
|
|
362
|
+
openStack.push(i);
|
|
363
|
+
} else if (c === "}") {
|
|
364
|
+
const open = openStack.pop();
|
|
365
|
+
if (open !== void 0) {
|
|
366
|
+
candidates.push(text.slice(open, i + 1));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (let i = candidates.length - 1; i >= 0; i--) {
|
|
371
|
+
try {
|
|
372
|
+
const o = JSON.parse(candidates[i]);
|
|
373
|
+
if (o && (o.verdict === "done" || o.verdict === "continue")) {
|
|
374
|
+
return { verdict: o.verdict, reason: typeof o.reason === "string" ? o.reason : void 0, guidance: typeof o.guidance === "string" ? o.guidance : void 0 };
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
function buildStaleSafeOracleGuidance(oracleOutput, now = /* @__PURE__ */ new Date()) {
|
|
382
|
+
const reading = (oracleOutput || "").trim().slice(0, 500);
|
|
383
|
+
return [
|
|
384
|
+
`The loop oracle reported unfinished work as of ${now.toISOString()}:`,
|
|
385
|
+
reading || "(no detail)",
|
|
386
|
+
"",
|
|
387
|
+
"That is a POINT-IN-TIME reading, not a fact about right now \u2014 the backlog may have changed",
|
|
388
|
+
"since (an item can be closed or paused out-of-band while this message was queued). RE-RUN",
|
|
389
|
+
"the oracle yourself and treat ITS CURRENT OUTPUT as the truth. If it is now empty, the goal",
|
|
390
|
+
"is complete: say so rather than chasing the items listed above. Otherwise keep working until",
|
|
391
|
+
"it is genuinely empty."
|
|
392
|
+
].join("\n");
|
|
393
|
+
}
|
|
394
|
+
async function runLoopVerification(cfg, deps) {
|
|
395
|
+
const log = deps.log || (() => {
|
|
396
|
+
});
|
|
397
|
+
const maxHolds = typeof cfg.maxHolds === "number" && cfg.maxHolds > 0 ? cfg.maxHolds : 3;
|
|
398
|
+
const holds = typeof cfg.holds === "number" && cfg.holds > 0 ? cfg.holds : 0;
|
|
399
|
+
let oracleOutput = "";
|
|
400
|
+
try {
|
|
401
|
+
const o = await deps.runOracle(cfg.oracle);
|
|
402
|
+
oracleOutput = o.output || "";
|
|
403
|
+
if (!o.ok) {
|
|
404
|
+
if (holds + 1 > maxHolds) {
|
|
405
|
+
return { action: "gave_up", reason: `oracle still reports pending work after ${holds} verification hold(s): ${oracleOutput.trim().slice(0, 200)}` };
|
|
406
|
+
}
|
|
407
|
+
log(`[loopVerify] oracle reports pending work \u2014 re-kicking instead of marking done`);
|
|
408
|
+
return {
|
|
409
|
+
action: "rekick",
|
|
410
|
+
reason: "oracle reports pending work",
|
|
411
|
+
guidance: buildStaleSafeOracleGuidance(oracleOutput)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
} catch (e) {
|
|
415
|
+
log(`[loopVerify] oracle runner error (continuing to evaluator): ${e?.message || e}`);
|
|
416
|
+
}
|
|
417
|
+
const closed = issuesClosedSince(deps.projectRoot, cfg.startedAt);
|
|
418
|
+
const paused = issuesPausedNow(deps.projectRoot, cfg.sessionId);
|
|
419
|
+
const prompt = buildEvaluatorPrompt({ task: cfg.task, until: cfg.until, oracle: cfg.oracle, oracleOutput, closedIssues: closed, pausedIssues: paused });
|
|
420
|
+
let raw = "";
|
|
421
|
+
try {
|
|
422
|
+
raw = await deps.runEvaluator(prompt);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
log(`[loopVerify] evaluator spawn failed \u2014 signalling verify_error (fail-closed, will retry): ${e?.message || e}`);
|
|
425
|
+
return { action: "verify_error", reason: `evaluator spawn failed: ${e?.message || e}` };
|
|
426
|
+
}
|
|
427
|
+
const verdict = parseEvaluatorVerdict(raw);
|
|
428
|
+
if (!verdict) {
|
|
429
|
+
log(`[loopVerify] could not parse evaluator verdict \u2014 signalling verify_error (fail-closed, will retry)`);
|
|
430
|
+
return { action: "verify_error", reason: "could not parse evaluator verdict" };
|
|
431
|
+
}
|
|
432
|
+
if (verdict.verdict === "done") {
|
|
433
|
+
log(`[loopVerify] evaluator confirms done${verdict.reason ? `: ${verdict.reason}` : ""}`);
|
|
434
|
+
return { action: "done" };
|
|
435
|
+
}
|
|
436
|
+
if (holds + 1 > maxHolds) {
|
|
437
|
+
return { action: "gave_up", reason: `evaluator held "continue" past the limit (${maxHolds})${verdict.reason ? `: ${verdict.reason}` : ""}` };
|
|
438
|
+
}
|
|
439
|
+
const guidance = [verdict.reason ? `Independent reviewer says NOT done: ${verdict.reason}` : "Independent reviewer says NOT done.", verdict.guidance ? `Fix: ${verdict.guidance}` : ""].filter(Boolean).join("\n");
|
|
440
|
+
log(`[loopVerify] evaluator says continue \u2014 re-kicking (hold ${holds + 1}/${maxHolds})`);
|
|
441
|
+
return { action: "rekick", reason: verdict.reason || "evaluator says continue", guidance };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export { applyDaemonShareSeed as A, recordProgressMarker as B, assessLoopProgress as C, DAEMON_SHARE_EMAILS_ENV as D, resolveExtensionTurns as E, grantLoopExtension as F, buildGoalCondition as G, computeSoftTurnCap as H, UNBOUNDED_LOOP_ITERATIONS as U, buildMachineShareUrl as a, buildSessionShareUrl as b, backlogOraclePending as c, runLoopVerification as d, resolveLoopDurable as e, decideLoopContinuation as f, resolveMaxAutoResumes as g, grantedExtensionCount as h, resolveMaxExtensions as i, resolveStuckLimit as j, decideVerifyErrorHandling as k, loopCancelledDuringVerify as l, resolveVerifyErrorRetries as m, accumulateLedger as n, effectiveSoftCap as o, checkLoopBudget as p, isHardBudgetStop as q, resolveEvaluatorModel as r, sumUsageTokens as s, terminalPhaseForDone as t, isCostCeiling as u, summarizeExtensions as v, rearmStartedAt as w, oracleScopeSessionId as x, shouldRearmDormantLoop as y, parseDaemonShareEmails as z };
|