@kynver-app/runtime 0.1.123 → 0.1.129
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/chat/chat-claim-loop.d.ts +11 -0
- package/dist/chat/command-allowlist.d.ts +8 -0
- package/dist/chat/command-executor.d.ts +13 -0
- package/dist/cli.js +62 -17782
- package/dist/config.d.ts +2 -0
- package/dist/cron/cron-cli-bin.d.ts +7 -0
- package/dist/cron/cron-readiness.d.ts +35 -0
- package/dist/daemon-heartbeat.d.ts +3 -0
- package/dist/disk-gate.d.ts +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +79 -19281
- package/dist/instruction-bundle/cache.d.ts +6 -0
- package/dist/instruction-bundle/client.d.ts +25 -0
- package/dist/instruction-bundle/contract.d.ts +4 -0
- package/dist/instruction-bundle/contract.js +5 -0
- package/dist/instruction-bundle/embedded-bundle.d.ts +3 -0
- package/dist/instruction-bundle/index.d.ts +7 -0
- package/dist/instruction-bundle/keys.d.ts +14 -0
- package/dist/instruction-bundle/state.d.ts +24 -0
- package/dist/instruction-bundle/types.d.ts +42 -0
- package/dist/instruction-bundle/verify.d.ts +15 -0
- package/dist/mesh-liveness/mesh-cron-lease-store.d.ts +9 -0
- package/dist/retry-limits.d.ts +4 -0
- package/dist/server/cleanup.js +16 -4054
- package/dist/server/default-repo.js +1 -458
- package/dist/server/harness-notice.js +15 -287
- package/dist/server/heavy-verification.js +1 -223
- package/dist/server/landing.js +1 -44
- package/dist/server/memory-cost-enforce.js +2 -480
- package/dist/server/memory-cost.js +2 -184
- package/dist/server/monitor.js +8 -1805
- package/dist/server/orchestration.js +1 -444
- package/dist/server/pr-evidence.js +1 -163
- package/dist/server/repo-search.js +1 -224
- package/dist/server/worker-policy.js +1 -432
- package/dist/worker-persona-catalog.d.ts +5 -0
- package/dist/worker-persona-catalog.js +5 -138
- package/package.json +8 -2
- package/dist/cli.js.map +0 -7
- package/dist/index.js.map +0 -7
- package/dist/server/cleanup.js.map +0 -7
- package/dist/server/default-repo.js.map +0 -7
- package/dist/server/harness-notice.js.map +0 -7
- package/dist/server/heavy-verification.js.map +0 -7
- package/dist/server/landing.js.map +0 -7
- package/dist/server/memory-cost-enforce.js.map +0 -7
- package/dist/server/memory-cost.js.map +0 -7
- package/dist/server/monitor.js.map +0 -7
- package/dist/server/orchestration.js.map +0 -7
- package/dist/server/pr-evidence.js.map +0 -7
- package/dist/server/repo-search.js.map +0 -7
- package/dist/server/worker-policy.js.map +0 -7
- package/dist/worker-persona-catalog.js.map +0 -7
package/dist/server/cleanup.js
CHANGED
|
@@ -1,4054 +1,16 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function fail(message) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
function hiddenSpawnOptions(opts) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
function safeJson(line) {
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(line);
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function readJson(file, fallback) {
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
27
|
-
} catch (error) {
|
|
28
|
-
if (arguments.length > 1) return fallback;
|
|
29
|
-
fail(`failed to read ${file}: ${error.message}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function writeJson(file, value) {
|
|
33
|
-
mkdirSync(path.dirname(file), { recursive: true });
|
|
34
|
-
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
35
|
-
`);
|
|
36
|
-
}
|
|
37
|
-
function safeSlug(value) {
|
|
38
|
-
return String(value || "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "worker";
|
|
39
|
-
}
|
|
40
|
-
function fileSize(file) {
|
|
41
|
-
try {
|
|
42
|
-
return statSync(file).size;
|
|
43
|
-
} catch {
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function fileMtime(file) {
|
|
48
|
-
try {
|
|
49
|
-
return statSync(file).mtime.toISOString();
|
|
50
|
-
} catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function tailFile(file, lines) {
|
|
55
|
-
if (!existsSync(file)) return "";
|
|
56
|
-
const data = readFileSync(file, "utf8");
|
|
57
|
-
return data.split("\n").slice(-lines).join("\n");
|
|
58
|
-
}
|
|
59
|
-
function isPidAlive(pid) {
|
|
60
|
-
if (!pid) return false;
|
|
61
|
-
try {
|
|
62
|
-
process.kill(pid, 0);
|
|
63
|
-
return true;
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function latestIso(values) {
|
|
69
|
-
let best = null;
|
|
70
|
-
let bestMs = -Infinity;
|
|
71
|
-
for (const value of values) {
|
|
72
|
-
if (!value) continue;
|
|
73
|
-
const ms = Date.parse(value);
|
|
74
|
-
if (Number.isFinite(ms) && ms > bestMs) {
|
|
75
|
-
bestMs = ms;
|
|
76
|
-
best = value;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return best;
|
|
80
|
-
}
|
|
81
|
-
function secsAgo(ms) {
|
|
82
|
-
return Math.max(0, Math.round((Date.now() - ms) / 1e3));
|
|
83
|
-
}
|
|
84
|
-
var init_util = __esm({
|
|
85
|
-
"src/util.ts"() {
|
|
86
|
-
"use strict";
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// src/worker-env.ts
|
|
91
|
-
var FORBIDDEN_WORKER_ENV_KEYS, FORBIDDEN_KEY_SET;
|
|
92
|
-
var init_worker_env = __esm({
|
|
93
|
-
"src/worker-env.ts"() {
|
|
94
|
-
"use strict";
|
|
95
|
-
FORBIDDEN_WORKER_ENV_KEYS = [
|
|
96
|
-
"ANTHROPIC_API_KEY",
|
|
97
|
-
"ANALYST_API_KEY",
|
|
98
|
-
"RECRUITER_API_KEY",
|
|
99
|
-
"AUTH_SECRET",
|
|
100
|
-
"NEXTAUTH_SECRET",
|
|
101
|
-
"DATABASE_URL",
|
|
102
|
-
"PRODUCTION_DATABASE_URL",
|
|
103
|
-
"KYNVER_PRODUCTION_DATABASE_URL",
|
|
104
|
-
"REDIS_URL",
|
|
105
|
-
"GOOGLE_CLIENT_SECRET",
|
|
106
|
-
"GITHUB_CLIENT_SECRET",
|
|
107
|
-
"KYNVER_API_KEY",
|
|
108
|
-
"KYNVER_SERVICE_SECRET",
|
|
109
|
-
"KYNVER_RUNTIME_SECRET",
|
|
110
|
-
"KYNVER_CRON_SECRET",
|
|
111
|
-
"OPENCLAW_CRON_SECRET",
|
|
112
|
-
"QSTASH_TOKEN",
|
|
113
|
-
"QSTASH_CURRENT_SIGNING_KEY",
|
|
114
|
-
"QSTASH_NEXT_SIGNING_KEY",
|
|
115
|
-
"TOOL_SECRETS_KEK",
|
|
116
|
-
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
117
|
-
"CLOUDFLARE_API_TOKEN",
|
|
118
|
-
"STRIPE_SECRET_KEY",
|
|
119
|
-
"STRIPE_WEBHOOK_SECRET",
|
|
120
|
-
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
121
|
-
"VOYAGE_API_KEY",
|
|
122
|
-
"PERPLEXITY_API_KEY",
|
|
123
|
-
"FRED_API_KEY",
|
|
124
|
-
"FMP_API_KEY",
|
|
125
|
-
"CURSOR_API_KEY"
|
|
126
|
-
];
|
|
127
|
-
FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// src/git.ts
|
|
132
|
-
import { spawnSync } from "node:child_process";
|
|
133
|
-
function git(cwd, args, options = {}) {
|
|
134
|
-
const res = spawnSync(
|
|
135
|
-
"git",
|
|
136
|
-
args,
|
|
137
|
-
hiddenSpawnOptions({ cwd, encoding: "utf8" })
|
|
138
|
-
);
|
|
139
|
-
if (res.status !== 0 && !options.allowFailure) {
|
|
140
|
-
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
141
|
-
if (options.throwError) throw new Error(message);
|
|
142
|
-
fail(message);
|
|
143
|
-
}
|
|
144
|
-
return res.stdout || "";
|
|
145
|
-
}
|
|
146
|
-
function gitStatusShort(worktreePath) {
|
|
147
|
-
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
148
|
-
}
|
|
149
|
-
function gitCapture(cwd, args) {
|
|
150
|
-
try {
|
|
151
|
-
const res = spawnSync(
|
|
152
|
-
"git",
|
|
153
|
-
args,
|
|
154
|
-
hiddenSpawnOptions({ cwd, encoding: "utf8" })
|
|
155
|
-
);
|
|
156
|
-
return {
|
|
157
|
-
status: res.status,
|
|
158
|
-
stdout: res.stdout || "",
|
|
159
|
-
stderr: res.stderr || "",
|
|
160
|
-
error: res.error ? res.error.message : null
|
|
161
|
-
};
|
|
162
|
-
} catch (error) {
|
|
163
|
-
return {
|
|
164
|
-
status: null,
|
|
165
|
-
stdout: "",
|
|
166
|
-
stderr: "",
|
|
167
|
-
error: error.message
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function gitIsAncestor(cwd, ancestor, descendant) {
|
|
172
|
-
const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
173
|
-
if (res.status === 0) return { isAncestor: true, error: null };
|
|
174
|
-
if (res.status === 1) return { isAncestor: false, error: null };
|
|
175
|
-
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
176
|
-
}
|
|
177
|
-
function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
|
|
178
|
-
const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
|
|
179
|
-
const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
|
|
180
|
-
const pinnedBaseCommit = options.baseCommit?.trim() || null;
|
|
181
|
-
if (!worktreePath) {
|
|
182
|
-
return unknownAncestry(baseLabel, "missing worktree path");
|
|
183
|
-
}
|
|
184
|
-
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
185
|
-
if (head.status !== 0) {
|
|
186
|
-
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
187
|
-
}
|
|
188
|
-
let baseSha;
|
|
189
|
-
if (pinnedBaseCommit) {
|
|
190
|
-
baseSha = pinnedBaseCommit;
|
|
191
|
-
} else {
|
|
192
|
-
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
193
|
-
if (baseHead.status !== 0) {
|
|
194
|
-
return unknownAncestry(
|
|
195
|
-
baseLabel,
|
|
196
|
-
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
197
|
-
head.stdout.trim()
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
baseSha = baseHead.stdout.trim();
|
|
201
|
-
}
|
|
202
|
-
const headSha = head.stdout.trim();
|
|
203
|
-
if (headSha === baseSha) {
|
|
204
|
-
return {
|
|
205
|
-
checked: true,
|
|
206
|
-
base: baseLabel,
|
|
207
|
-
head: headSha,
|
|
208
|
-
baseHead: baseSha,
|
|
209
|
-
baseIsAncestorOfHead: true,
|
|
210
|
-
headIsAncestorOfBase: true,
|
|
211
|
-
relation: "synced"
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
|
|
215
|
-
const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
|
|
216
|
-
const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
|
|
217
|
-
if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
|
|
218
|
-
return {
|
|
219
|
-
checked: false,
|
|
220
|
-
base: baseLabel,
|
|
221
|
-
head: headSha,
|
|
222
|
-
baseHead: baseSha,
|
|
223
|
-
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
224
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
225
|
-
relation: "unknown",
|
|
226
|
-
...error ? { error } : {}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
|
|
230
|
-
return {
|
|
231
|
-
checked: true,
|
|
232
|
-
base: baseLabel,
|
|
233
|
-
head: headSha,
|
|
234
|
-
baseHead: baseSha,
|
|
235
|
-
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
236
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
237
|
-
relation,
|
|
238
|
-
...error ? { error } : {}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
function unknownAncestry(base, error, head = null) {
|
|
242
|
-
return {
|
|
243
|
-
checked: false,
|
|
244
|
-
base,
|
|
245
|
-
head,
|
|
246
|
-
baseHead: null,
|
|
247
|
-
baseIsAncestorOfHead: null,
|
|
248
|
-
headIsAncestorOfBase: null,
|
|
249
|
-
relation: "unknown",
|
|
250
|
-
error
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
var init_git = __esm({
|
|
254
|
-
"src/git.ts"() {
|
|
255
|
-
"use strict";
|
|
256
|
-
init_util();
|
|
257
|
-
init_worker_env();
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// src/path-values.ts
|
|
262
|
-
import { homedir } from "node:os";
|
|
263
|
-
import path2 from "node:path";
|
|
264
|
-
function expandHomePath(value) {
|
|
265
|
-
if (value === "~") return homedir();
|
|
266
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
267
|
-
return path2.join(homedir(), value.slice(2));
|
|
268
|
-
}
|
|
269
|
-
return value;
|
|
270
|
-
}
|
|
271
|
-
function resolveUserPath(value) {
|
|
272
|
-
return path2.resolve(expandHomePath(value));
|
|
273
|
-
}
|
|
274
|
-
var init_path_values = __esm({
|
|
275
|
-
"src/path-values.ts"() {
|
|
276
|
-
"use strict";
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// src/default-repo-discovery.ts
|
|
281
|
-
var init_default_repo_discovery = __esm({
|
|
282
|
-
"src/default-repo-discovery.ts"() {
|
|
283
|
-
"use strict";
|
|
284
|
-
init_git();
|
|
285
|
-
init_path_values();
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// src/box-identity.ts
|
|
290
|
-
var init_box_identity = __esm({
|
|
291
|
-
"src/box-identity.ts"() {
|
|
292
|
-
"use strict";
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// src/bounded-build/meminfo.ts
|
|
297
|
-
var init_meminfo = __esm({
|
|
298
|
-
"src/bounded-build/meminfo.ts"() {
|
|
299
|
-
"use strict";
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// src/wsl-host.ts
|
|
304
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, statfsSync } from "node:fs";
|
|
305
|
-
function isWslHost() {
|
|
306
|
-
if (process.platform !== "linux") return false;
|
|
307
|
-
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
308
|
-
try {
|
|
309
|
-
if (!existsSync2(probe)) continue;
|
|
310
|
-
const text = readFileSync2(probe, "utf8");
|
|
311
|
-
if (/microsoft|wsl/i.test(text)) return true;
|
|
312
|
-
} catch {
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
function observeWslHostDisk(options = {}) {
|
|
318
|
-
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
319
|
-
if (!wsl) return null;
|
|
320
|
-
const path26 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
321
|
-
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
322
|
-
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
323
|
-
const statfs = options.statfs ?? statfsSync;
|
|
324
|
-
let stats;
|
|
325
|
-
try {
|
|
326
|
-
stats = statfs(path26);
|
|
327
|
-
} catch (error) {
|
|
328
|
-
return {
|
|
329
|
-
ok: false,
|
|
330
|
-
path: path26,
|
|
331
|
-
freeBytes: 0,
|
|
332
|
-
totalBytes: 0,
|
|
333
|
-
usedPercent: 100,
|
|
334
|
-
warnBelowBytes,
|
|
335
|
-
criticalBelowBytes,
|
|
336
|
-
reason: `Windows host disk probe failed at ${path26}: ${error.message}`,
|
|
337
|
-
probeError: error.message
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
341
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
342
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
343
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
344
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
345
|
-
const ok = !lowFree && !criticalFree;
|
|
346
|
-
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
347
|
-
let reason = null;
|
|
348
|
-
if (!ok) {
|
|
349
|
-
const tag = criticalFree ? "critical" : "warning";
|
|
350
|
-
reason = `Windows host disk ${path26} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
351
|
-
}
|
|
352
|
-
return {
|
|
353
|
-
ok,
|
|
354
|
-
path: path26,
|
|
355
|
-
freeBytes,
|
|
356
|
-
totalBytes,
|
|
357
|
-
usedPercent,
|
|
358
|
-
warnBelowBytes,
|
|
359
|
-
criticalBelowBytes,
|
|
360
|
-
reason,
|
|
361
|
-
probeError: null
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function summarizeWslRecoverySteps() {
|
|
365
|
-
return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
|
|
366
|
-
}
|
|
367
|
-
var DEFAULT_WSL_HOST_WARN_FREE_BYTES, DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES, DEFAULT_WSL_HOST_MOUNT;
|
|
368
|
-
var init_wsl_host = __esm({
|
|
369
|
-
"src/wsl-host.ts"() {
|
|
370
|
-
"use strict";
|
|
371
|
-
DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
372
|
-
DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
373
|
-
DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// src/disk-gate.ts
|
|
378
|
-
import { statfsSync as statfsSync2 } from "node:fs";
|
|
379
|
-
function observeRunnerDiskGate(input = {}) {
|
|
380
|
-
const path26 = input.diskPath?.trim() || "/";
|
|
381
|
-
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
382
|
-
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
383
|
-
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
384
|
-
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
385
|
-
const stats = statfsSync2(path26);
|
|
386
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
387
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
388
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
389
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
390
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
391
|
-
const highUse = usedPercent > maxUsedPercent;
|
|
392
|
-
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
393
|
-
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
394
|
-
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
395
|
-
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
396
|
-
let reason = null;
|
|
397
|
-
if (!ok) {
|
|
398
|
-
reason = [
|
|
399
|
-
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
400
|
-
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
401
|
-
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
402
|
-
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
403
|
-
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
404
|
-
].filter(Boolean).join("; ");
|
|
405
|
-
}
|
|
406
|
-
return {
|
|
407
|
-
ok,
|
|
408
|
-
path: path26,
|
|
409
|
-
freeBytes,
|
|
410
|
-
totalBytes,
|
|
411
|
-
usedPercent,
|
|
412
|
-
warnBelowBytes,
|
|
413
|
-
criticalBelowBytes,
|
|
414
|
-
maxUsedPercent,
|
|
415
|
-
hardMaxUsedPercent,
|
|
416
|
-
reason,
|
|
417
|
-
wslHost
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
var DEFAULT_WARN_FREE_BYTES, DEFAULT_CRITICAL_FREE_BYTES, DEFAULT_MAX_USED_PERCENT, DEFAULT_HARD_MAX_USED_PERCENT;
|
|
421
|
-
var init_disk_gate = __esm({
|
|
422
|
-
"src/disk-gate.ts"() {
|
|
423
|
-
"use strict";
|
|
424
|
-
init_wsl_host();
|
|
425
|
-
DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
|
|
426
|
-
DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
427
|
-
DEFAULT_MAX_USED_PERCENT = 80;
|
|
428
|
-
DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// src/run-store.ts
|
|
433
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
434
|
-
import path3 from "node:path";
|
|
435
|
-
function getPaths() {
|
|
436
|
-
return getHarnessPaths();
|
|
437
|
-
}
|
|
438
|
-
function listRunRecords() {
|
|
439
|
-
const { runsDir } = getPaths();
|
|
440
|
-
return listRunRecordsAt(runsDir);
|
|
441
|
-
}
|
|
442
|
-
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
443
|
-
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
444
|
-
}
|
|
445
|
-
function isRunDirectoryEntry(runDirPath) {
|
|
446
|
-
try {
|
|
447
|
-
return statSync2(runDirPath).isDirectory();
|
|
448
|
-
} catch {
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
function listRunRecordsAt(runsDir) {
|
|
453
|
-
if (!existsSync3(runsDir)) return [];
|
|
454
|
-
const runs = [];
|
|
455
|
-
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
456
|
-
if (entry.name === "runs") continue;
|
|
457
|
-
const runDir2 = path3.join(runsDir, entry.name);
|
|
458
|
-
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
459
|
-
const run = readJson(
|
|
460
|
-
path3.join(runDir2, "run.json"),
|
|
461
|
-
void 0
|
|
462
|
-
);
|
|
463
|
-
if (run?.id) runs.push(run);
|
|
464
|
-
}
|
|
465
|
-
return runs;
|
|
466
|
-
}
|
|
467
|
-
function saveRun(run) {
|
|
468
|
-
const { runsDir } = getPaths();
|
|
469
|
-
writeJson(path3.join(runDir(runsDir, run.id), "run.json"), run);
|
|
470
|
-
}
|
|
471
|
-
function runDirectory(id) {
|
|
472
|
-
const { harnessRoot } = getPaths();
|
|
473
|
-
return runDirectoryAt(harnessRoot, id);
|
|
474
|
-
}
|
|
475
|
-
function runDirectoryAt(harnessRoot, id) {
|
|
476
|
-
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
477
|
-
}
|
|
478
|
-
var init_run_store = __esm({
|
|
479
|
-
"src/run-store.ts"() {
|
|
480
|
-
"use strict";
|
|
481
|
-
init_paths();
|
|
482
|
-
init_util();
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// src/run-worker-index.ts
|
|
487
|
-
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
488
|
-
import path4 from "node:path";
|
|
489
|
-
function listRunWorkerNames(run) {
|
|
490
|
-
const names = /* @__PURE__ */ new Set();
|
|
491
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
492
|
-
names.add(safeSlug(name));
|
|
493
|
-
}
|
|
494
|
-
const workersDir = path4.join(runDirectory(run.id), "workers");
|
|
495
|
-
if (!existsSync4(workersDir)) return [...names];
|
|
496
|
-
for (const entry of readdirSync3(workersDir, { withFileTypes: true })) {
|
|
497
|
-
if (!entry.isDirectory()) continue;
|
|
498
|
-
names.add(safeSlug(entry.name));
|
|
499
|
-
}
|
|
500
|
-
return [...names];
|
|
501
|
-
}
|
|
502
|
-
var init_run_worker_index = __esm({
|
|
503
|
-
"src/run-worker-index.ts"() {
|
|
504
|
-
"use strict";
|
|
505
|
-
init_run_store();
|
|
506
|
-
init_util();
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// src/heartbeat-final-result.ts
|
|
511
|
-
function tryParseJsonObject(text) {
|
|
512
|
-
const trimmed = text.trim();
|
|
513
|
-
if (!trimmed.startsWith("{")) return null;
|
|
514
|
-
try {
|
|
515
|
-
const parsed = JSON.parse(trimmed);
|
|
516
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
517
|
-
return parsed;
|
|
518
|
-
}
|
|
519
|
-
} catch {
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
function embeddedRecordFromProse(text) {
|
|
525
|
-
const trimmed = text.trim();
|
|
526
|
-
if (!trimmed) return null;
|
|
527
|
-
const direct = tryParseJsonObject(trimmed);
|
|
528
|
-
if (direct) return direct;
|
|
529
|
-
const candidates = [];
|
|
530
|
-
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
531
|
-
let fenceMatch;
|
|
532
|
-
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
533
|
-
const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
|
|
534
|
-
if (fromFence) candidates.push(fromFence);
|
|
535
|
-
}
|
|
536
|
-
const firstBrace = trimmed.indexOf("{");
|
|
537
|
-
const lastBrace = trimmed.lastIndexOf("}");
|
|
538
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
539
|
-
const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
|
|
540
|
-
if (slice) candidates.push(slice);
|
|
541
|
-
}
|
|
542
|
-
return candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
543
|
-
}
|
|
544
|
-
function terminalFinalResultFromHeartbeatRow(row) {
|
|
545
|
-
const explicit = row.finalResult ?? row.final_result;
|
|
546
|
-
if (explicit !== void 0 && explicit !== null) {
|
|
547
|
-
if (typeof explicit === "string") {
|
|
548
|
-
const embedded2 = embeddedRecordFromProse(explicit);
|
|
549
|
-
return embedded2 ?? (explicit.trim() || null);
|
|
550
|
-
}
|
|
551
|
-
return explicit;
|
|
552
|
-
}
|
|
553
|
-
const summary = typeof row.summary === "string" ? row.summary.trim() : "";
|
|
554
|
-
if (!summary) return null;
|
|
555
|
-
const embedded = embeddedRecordFromProse(summary);
|
|
556
|
-
if (embedded) return embedded;
|
|
557
|
-
return summary;
|
|
558
|
-
}
|
|
559
|
-
var init_heartbeat_final_result = __esm({
|
|
560
|
-
"src/heartbeat-final-result.ts"() {
|
|
561
|
-
"use strict";
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// src/heartbeat.ts
|
|
566
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
567
|
-
function isTerminalHeartbeatPhase(phase) {
|
|
568
|
-
return phase === "complete";
|
|
569
|
-
}
|
|
570
|
-
function terminalFinalResultFromHeartbeat(heartbeat) {
|
|
571
|
-
if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
|
|
572
|
-
if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
|
|
573
|
-
return heartbeat.terminalFinalResult;
|
|
574
|
-
}
|
|
575
|
-
const summary = heartbeat.lastHeartbeatSummary?.trim();
|
|
576
|
-
return summary || "completed";
|
|
577
|
-
}
|
|
578
|
-
function parseHeartbeat(file) {
|
|
579
|
-
const result = {
|
|
580
|
-
heartbeatCount: 0,
|
|
581
|
-
lastHeartbeatAt: null,
|
|
582
|
-
lastHeartbeatPhase: null,
|
|
583
|
-
lastHeartbeatSummary: null,
|
|
584
|
-
terminalFinalResult: null,
|
|
585
|
-
heartbeatBlocker: null,
|
|
586
|
-
timestampAnomalies: [],
|
|
587
|
-
lastBoxResourceSnapshot: null,
|
|
588
|
-
lastPrEvidence: []
|
|
589
|
-
};
|
|
590
|
-
if (!existsSync5(file)) return result;
|
|
591
|
-
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
592
|
-
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
593
|
-
const lines = readFileSync3(file, "utf8").split("\n").filter(Boolean);
|
|
594
|
-
for (const line of lines) {
|
|
595
|
-
const entry = safeJson(line);
|
|
596
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
597
|
-
const row = entry;
|
|
598
|
-
result.heartbeatCount++;
|
|
599
|
-
if (row.ts) {
|
|
600
|
-
const ts = String(row.ts);
|
|
601
|
-
const tsMs = Date.parse(ts);
|
|
602
|
-
if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
|
|
603
|
-
result.timestampAnomalies.push({
|
|
604
|
-
kind: "future_heartbeat_timestamp",
|
|
605
|
-
observedAt: ts,
|
|
606
|
-
clampedTo
|
|
607
|
-
});
|
|
608
|
-
} else {
|
|
609
|
-
result.lastHeartbeatAt = ts;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
|
|
613
|
-
if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
|
|
614
|
-
if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
|
|
615
|
-
result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
|
|
616
|
-
}
|
|
617
|
-
result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
|
|
618
|
-
if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
|
|
619
|
-
result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
|
|
620
|
-
}
|
|
621
|
-
if (Array.isArray(row.prEvidence)) {
|
|
622
|
-
result.lastPrEvidence = row.prEvidence.filter(
|
|
623
|
-
(entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return result;
|
|
628
|
-
}
|
|
629
|
-
var HEARTBEAT_FUTURE_SKEW_MS;
|
|
630
|
-
var init_heartbeat = __esm({
|
|
631
|
-
"src/heartbeat.ts"() {
|
|
632
|
-
"use strict";
|
|
633
|
-
init_heartbeat_final_result();
|
|
634
|
-
init_util();
|
|
635
|
-
HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
636
|
-
}
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
// src/repo-search.ts
|
|
640
|
-
function binaryName(token) {
|
|
641
|
-
if (!token) return null;
|
|
642
|
-
const base = token.split("/").pop() ?? token;
|
|
643
|
-
return base.replace(/\.exe$/i, "").toLowerCase();
|
|
644
|
-
}
|
|
645
|
-
function splitShellWords(input) {
|
|
646
|
-
if (!input) return [];
|
|
647
|
-
const words = [];
|
|
648
|
-
let current = "";
|
|
649
|
-
let quote;
|
|
650
|
-
let escaped = false;
|
|
651
|
-
for (const char of input) {
|
|
652
|
-
if (escaped) {
|
|
653
|
-
current += char;
|
|
654
|
-
escaped = false;
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
if (char === "\\") {
|
|
658
|
-
escaped = true;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (quote) {
|
|
662
|
-
if (char === quote) quote = void 0;
|
|
663
|
-
else current += char;
|
|
664
|
-
continue;
|
|
665
|
-
}
|
|
666
|
-
if (char === '"' || char === "'") {
|
|
667
|
-
quote = char;
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
if (/\s/.test(char)) {
|
|
671
|
-
if (current) {
|
|
672
|
-
words.push(current);
|
|
673
|
-
current = "";
|
|
674
|
-
}
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
current += char;
|
|
678
|
-
}
|
|
679
|
-
if (current) words.push(current);
|
|
680
|
-
return words;
|
|
681
|
-
}
|
|
682
|
-
function isRgArgv(argv) {
|
|
683
|
-
const bin = binaryName(argv[0]);
|
|
684
|
-
return Boolean(bin && RG_BINARIES.has(bin));
|
|
685
|
-
}
|
|
686
|
-
function isSingleFileSearchTarget(target) {
|
|
687
|
-
if (!target || target.includes("/") || target.includes("*")) return false;
|
|
688
|
-
return /\.(?:json|md|mjs|cjs|js|ts|tsx|yaml|yml)$/iu.test(target);
|
|
689
|
-
}
|
|
690
|
-
function isRgExcludeScopeTarget(target) {
|
|
691
|
-
if (!target) return false;
|
|
692
|
-
const t = target.trim();
|
|
693
|
-
return t.startsWith("!") && !t.includes("/") && !t.endsWith("/**");
|
|
694
|
-
}
|
|
695
|
-
function fixRgGlobToken(token) {
|
|
696
|
-
if (!token.startsWith("--glob=")) return token;
|
|
697
|
-
const value = token.slice("--glob=".length);
|
|
698
|
-
if (value.startsWith("!") && !value.includes("/") && !value.endsWith("/**")) {
|
|
699
|
-
return `--glob=${value}/**`;
|
|
700
|
-
}
|
|
701
|
-
return token;
|
|
702
|
-
}
|
|
703
|
-
function collectRgPositional(argv) {
|
|
704
|
-
const positional = [];
|
|
705
|
-
for (let i = 1; i < argv.length; i += 1) {
|
|
706
|
-
const token = argv[i];
|
|
707
|
-
if (!token) continue;
|
|
708
|
-
if (token === "--") {
|
|
709
|
-
positional.push(...argv.slice(i + 1));
|
|
710
|
-
break;
|
|
711
|
-
}
|
|
712
|
-
if (token.startsWith("-")) {
|
|
713
|
-
if (token.includes("=")) continue;
|
|
714
|
-
if (RG_OPTS_WITH_VALUE.has(token)) i += 1;
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
positional.push(token);
|
|
718
|
-
}
|
|
719
|
-
return positional;
|
|
720
|
-
}
|
|
721
|
-
function normalizeRgArgv(argv) {
|
|
722
|
-
let changed = false;
|
|
723
|
-
const out = argv.map((token) => {
|
|
724
|
-
const fixed = fixRgGlobToken(token);
|
|
725
|
-
if (fixed !== token) changed = true;
|
|
726
|
-
return fixed;
|
|
727
|
-
});
|
|
728
|
-
const positional = collectRgPositional(out);
|
|
729
|
-
if (positional.length === 2) {
|
|
730
|
-
const [pattern, target] = positional;
|
|
731
|
-
if (isSingleFileSearchTarget(target)) {
|
|
732
|
-
return { argv: [out[0] ?? "rg", "-g", target, pattern, "."], changed: true };
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return { argv: out, changed };
|
|
736
|
-
}
|
|
737
|
-
function normalizeRepoSearchCommand(command) {
|
|
738
|
-
const trimmed = command.trim();
|
|
739
|
-
if (!trimmed) return { command: trimmed, changed: false };
|
|
740
|
-
const joiner = trimmed.includes("&&") ? " && " : trimmed.includes("||") ? " || " : "; ";
|
|
741
|
-
const stages = trimmed.split(/\s*(?:&&|\|\||;)\s*/u);
|
|
742
|
-
let changed = false;
|
|
743
|
-
const normalizedStages = stages.map((stage) => {
|
|
744
|
-
const argv = splitShellWords(stage.trim());
|
|
745
|
-
if (!argv.length || !isRgArgv(argv)) return stage;
|
|
746
|
-
const normalized = normalizeRgArgv(argv);
|
|
747
|
-
if (normalized.changed) {
|
|
748
|
-
changed = true;
|
|
749
|
-
return normalized.argv.join(" ");
|
|
750
|
-
}
|
|
751
|
-
return stage;
|
|
752
|
-
});
|
|
753
|
-
if (!changed) return { command: trimmed, changed: false };
|
|
754
|
-
return { command: normalizedStages.join(joiner), changed: true };
|
|
755
|
-
}
|
|
756
|
-
function extractSearchMeta(meta) {
|
|
757
|
-
if (!meta) return {};
|
|
758
|
-
const pipelineMatch = meta.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(|$)/iu);
|
|
759
|
-
if (pipelineMatch) {
|
|
760
|
-
return { pattern: pipelineMatch[1], target: pipelineMatch[2]?.trim() };
|
|
761
|
-
}
|
|
762
|
-
const match = meta.match(/^search\s+"(.+)"\s+in\s+(.+)$/iu);
|
|
763
|
-
if (!match) return {};
|
|
764
|
-
return { pattern: match[1], target: match[2]?.trim() };
|
|
765
|
-
}
|
|
766
|
-
function classifyRepoSearchMeta(meta) {
|
|
767
|
-
const { pattern, target } = extractSearchMeta(meta);
|
|
768
|
-
if (!pattern) return { kind: "not_repo_search" };
|
|
769
|
-
if (isRgExcludeScopeTarget(target)) {
|
|
770
|
-
return { kind: "rg_exclude_syntax", pattern, target };
|
|
771
|
-
}
|
|
772
|
-
if (isSingleFileSearchTarget(target)) {
|
|
773
|
-
return { kind: "bad_scope", pattern, target };
|
|
774
|
-
}
|
|
775
|
-
return { kind: "not_repo_search", pattern, target };
|
|
776
|
-
}
|
|
777
|
-
function metaToNormalizedRgCommand(meta) {
|
|
778
|
-
const { pattern, target } = extractSearchMeta(meta);
|
|
779
|
-
if (!pattern) return null;
|
|
780
|
-
if (isRgExcludeScopeTarget(target)) {
|
|
781
|
-
const glob = `${target.trim()}/**`;
|
|
782
|
-
return {
|
|
783
|
-
command: `rg "${pattern}" -g '${glob}' .`,
|
|
784
|
-
changed: true
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
if (target && isSingleFileSearchTarget(target)) {
|
|
788
|
-
return {
|
|
789
|
-
command: `rg -g ${target} "${pattern}" .`,
|
|
790
|
-
changed: true
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
function formatRepoSearchGuidance(ctx) {
|
|
796
|
-
if (ctx.kind === "bad_scope" && ctx.pattern?.includes("agent-os-land-pr") && ctx.target === "package.json") {
|
|
797
|
-
return "Search package.json with a glob from the repo root: `rg -g package.json agent-os-land-pr .` \u2014 or run `node scripts/agent-os-land-pr.mjs <pr-url>` directly.";
|
|
798
|
-
}
|
|
799
|
-
if (ctx.kind === "bad_scope" && ctx.pattern && ctx.target) {
|
|
800
|
-
return `Use \`rg -g '${ctx.target}' ${ctx.pattern} .\` from the repo root instead of treating ${ctx.target} as a folder.`;
|
|
801
|
-
}
|
|
802
|
-
if (ctx.kind === "rg_exclude_syntax" && ctx.pattern) {
|
|
803
|
-
const glob = ctx.target ? `${ctx.target.trim()}/**` : "!node_modules/**";
|
|
804
|
-
return `Repo search scope \`${ctx.target ?? "!node_modules"}\` is not a valid ripgrep path. Use \`rg "${ctx.pattern}" -g '${glob}' .\` from the repo root (exclude globs need a \`/**\` suffix).`;
|
|
805
|
-
}
|
|
806
|
-
if (ctx.kind === "no_matches" && ctx.pattern) {
|
|
807
|
-
return `No matches for "${ctx.pattern}". Try a broader pattern, drop overly short tokens, or search from the repo root with \`rg "${ctx.pattern}" .\`.`;
|
|
808
|
-
}
|
|
809
|
-
return null;
|
|
810
|
-
}
|
|
811
|
-
function extractSearchMetaFromToolLine(line) {
|
|
812
|
-
const match = line.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(agent\)|\s*failed|$)/iu);
|
|
813
|
-
if (!match) return null;
|
|
814
|
-
return `search "${match[1]}" in ${match[2]?.trim()}`;
|
|
815
|
-
}
|
|
816
|
-
function diagnoseRepoSearchFailure(input) {
|
|
817
|
-
const meta = input.meta?.trim() || (input.command ? extractSearchMetaFromToolLine(input.command) : null) || null;
|
|
818
|
-
if (meta) {
|
|
819
|
-
const ctx = classifyRepoSearchMeta(meta);
|
|
820
|
-
const guidance = formatRepoSearchGuidance(ctx);
|
|
821
|
-
if (guidance) return guidance;
|
|
822
|
-
const normalized = metaToNormalizedRgCommand(meta);
|
|
823
|
-
if (normalized?.changed) {
|
|
824
|
-
return `Repo search used an invalid scope. Retry with: \`${normalized.command}\`.`;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
if (input.command && /\b(rg|ripgrep)\b/i.test(input.command)) {
|
|
828
|
-
const normalized = normalizeRepoSearchCommand(input.command);
|
|
829
|
-
if (normalized.changed) {
|
|
830
|
-
return `Ripgrep scope may be invalid. Retry with: \`${normalized.command}\`.`;
|
|
831
|
-
}
|
|
832
|
-
if (input.exitCode === 1) {
|
|
833
|
-
return "Ripgrep returned no matches (exit 1). Try a broader pattern or search from the repo root.";
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
var RG_BINARIES, RG_OPTS_WITH_VALUE;
|
|
839
|
-
var init_repo_search = __esm({
|
|
840
|
-
"src/repo-search.ts"() {
|
|
841
|
-
"use strict";
|
|
842
|
-
RG_BINARIES = /* @__PURE__ */ new Set(["rg", "ripgrep", "grep"]);
|
|
843
|
-
RG_OPTS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
844
|
-
"-e",
|
|
845
|
-
"--regexp",
|
|
846
|
-
"-f",
|
|
847
|
-
"--file",
|
|
848
|
-
"-m",
|
|
849
|
-
"--max-count",
|
|
850
|
-
"-A",
|
|
851
|
-
"--after-context",
|
|
852
|
-
"-B",
|
|
853
|
-
"--before-context",
|
|
854
|
-
"-C",
|
|
855
|
-
"--context",
|
|
856
|
-
"-g",
|
|
857
|
-
"--glob",
|
|
858
|
-
"--iglob"
|
|
859
|
-
]);
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
// src/shell-command-outcome.ts
|
|
864
|
-
function tidy(text, max = 200) {
|
|
865
|
-
const one = text.replace(/\s+/g, " ").trim();
|
|
866
|
-
return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
|
|
867
|
-
}
|
|
868
|
-
function extractJsonObject(text) {
|
|
869
|
-
const trimmed = text.trim();
|
|
870
|
-
if (!trimmed) return null;
|
|
871
|
-
if (trimmed.startsWith("{")) {
|
|
872
|
-
try {
|
|
873
|
-
return JSON.parse(trimmed);
|
|
874
|
-
} catch {
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
const start = trimmed.indexOf("{");
|
|
878
|
-
const end = trimmed.lastIndexOf("}");
|
|
879
|
-
if (start >= 0 && end > start) {
|
|
880
|
-
try {
|
|
881
|
-
return JSON.parse(trimmed.slice(start, end + 1));
|
|
882
|
-
} catch {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return null;
|
|
887
|
-
}
|
|
888
|
-
function isRecord(value) {
|
|
889
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
890
|
-
}
|
|
891
|
-
function summarizeNpmAuditReport(report) {
|
|
892
|
-
const meta = report.metadata;
|
|
893
|
-
if (!isRecord(meta)) return null;
|
|
894
|
-
const vuln = meta.vulnerabilities;
|
|
895
|
-
if (!isRecord(vuln)) return null;
|
|
896
|
-
const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
|
|
897
|
-
const summary = {
|
|
898
|
-
info: num("info"),
|
|
899
|
-
low: num("low"),
|
|
900
|
-
moderate: num("moderate"),
|
|
901
|
-
high: num("high"),
|
|
902
|
-
critical: num("critical"),
|
|
903
|
-
total: num("total")
|
|
904
|
-
};
|
|
905
|
-
if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
|
|
906
|
-
return null;
|
|
907
|
-
}
|
|
908
|
-
return summary;
|
|
909
|
-
}
|
|
910
|
-
function formatAuditSummaryLine(audit) {
|
|
911
|
-
const parts = [];
|
|
912
|
-
if (audit.critical) parts.push(`${audit.critical} critical`);
|
|
913
|
-
if (audit.high) parts.push(`${audit.high} high`);
|
|
914
|
-
if (audit.moderate) parts.push(`${audit.moderate} moderate`);
|
|
915
|
-
if (audit.low) parts.push(`${audit.low} low`);
|
|
916
|
-
if (audit.info) parts.push(`${audit.info} info`);
|
|
917
|
-
const breakdown = parts.length ? parts.join(", ") : "see report";
|
|
918
|
-
return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
|
|
919
|
-
}
|
|
920
|
-
function npmAuditFailureReason(report, stderr) {
|
|
921
|
-
const err = report.error;
|
|
922
|
-
if (isRecord(err)) {
|
|
923
|
-
const summary = typeof err.summary === "string" ? err.summary.trim() : "";
|
|
924
|
-
const code = typeof err.code === "string" ? err.code.trim() : "";
|
|
925
|
-
if (summary) return code ? `${code}: ${summary}` : summary;
|
|
926
|
-
if (code) return code;
|
|
927
|
-
}
|
|
928
|
-
const detail = typeof report.message === "string" ? report.message.trim() : "";
|
|
929
|
-
if (detail) return detail;
|
|
930
|
-
const errTail = stderr.trim();
|
|
931
|
-
if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
|
|
932
|
-
return "npm audit failed";
|
|
933
|
-
}
|
|
934
|
-
function classifyNpmAuditOutcome(input) {
|
|
935
|
-
const combined = `${input.stdout}
|
|
936
|
-
${input.stderr}`.trim();
|
|
937
|
-
const parsed = extractJsonObject(combined);
|
|
938
|
-
if (!parsed || !isRecord(parsed)) {
|
|
939
|
-
const tail = tidy(combined || `exit ${input.exitCode}`, 180);
|
|
940
|
-
return {
|
|
941
|
-
kind: "command_failure",
|
|
942
|
-
exitCode: input.exitCode,
|
|
943
|
-
summary: `npm audit failed (invalid or missing JSON): ${tail}`,
|
|
944
|
-
parseError: "invalid_json"
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
if (isRecord(parsed.error)) {
|
|
948
|
-
return {
|
|
949
|
-
kind: "command_failure",
|
|
950
|
-
exitCode: input.exitCode,
|
|
951
|
-
summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
const audit = summarizeNpmAuditReport(parsed);
|
|
955
|
-
if (!audit) {
|
|
956
|
-
return {
|
|
957
|
-
kind: "command_failure",
|
|
958
|
-
exitCode: input.exitCode,
|
|
959
|
-
summary: "npm audit failed: JSON response missing vulnerability metadata",
|
|
960
|
-
parseError: "missing_metadata"
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
if (input.exitCode === 0 && audit.total === 0) {
|
|
964
|
-
return {
|
|
965
|
-
kind: "success",
|
|
966
|
-
exitCode: 0,
|
|
967
|
-
summary: "npm audit: no vulnerabilities reported",
|
|
968
|
-
audit
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
return {
|
|
972
|
-
kind: "audit_findings",
|
|
973
|
-
exitCode: input.exitCode,
|
|
974
|
-
summary: formatAuditSummaryLine(audit),
|
|
975
|
-
audit
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
function isNpmAuditCommand(command) {
|
|
979
|
-
return NPM_AUDIT_RE.test(command);
|
|
980
|
-
}
|
|
981
|
-
function isRgCommand(command) {
|
|
982
|
-
return RG_CMD_RE.test(command);
|
|
983
|
-
}
|
|
984
|
-
function classifyRgOutcome(input) {
|
|
985
|
-
const diagnosis = diagnoseRepoSearchFailure({
|
|
986
|
-
command: input.command,
|
|
987
|
-
exitCode: input.exitCode
|
|
988
|
-
});
|
|
989
|
-
if (input.exitCode === 0) {
|
|
990
|
-
return {
|
|
991
|
-
kind: "success",
|
|
992
|
-
exitCode: 0,
|
|
993
|
-
summary: "ripgrep finished (exit 0)"
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
if (input.exitCode === 1) {
|
|
997
|
-
const errText = (input.stderr || input.interleaved).trim();
|
|
998
|
-
if (errText && RG_REAL_ERROR_RE.test(errText)) {
|
|
999
|
-
const tail2 = tidy(errText, 160);
|
|
1000
|
-
return {
|
|
1001
|
-
kind: "command_failure",
|
|
1002
|
-
exitCode: 1,
|
|
1003
|
-
summary: diagnosis ?? `ripgrep failed (exit 1): ${tail2}`
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
const normalized = normalizeRepoSearchCommand(input.command);
|
|
1007
|
-
const retry = normalized.changed && !diagnosis ? ` Retry with: \`${normalized.command}\`.` : "";
|
|
1008
|
-
return {
|
|
1009
|
-
kind: "search_no_matches",
|
|
1010
|
-
exitCode: 1,
|
|
1011
|
-
summary: diagnosis ?? `ripgrep: no matches (exit 1).${retry} Try a broader pattern or search from the repo root.`
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
const tail = tidy(input.interleaved || input.stdout || input.stderr || `exit ${input.exitCode}`, 180);
|
|
1015
|
-
return {
|
|
1016
|
-
kind: "command_failure",
|
|
1017
|
-
exitCode: input.exitCode,
|
|
1018
|
-
summary: diagnosis ?? `ripgrep failed (exit ${input.exitCode}): ${tail}`
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
function classifyShellCommandOutcome(input) {
|
|
1022
|
-
const stdout = input.stdout ?? "";
|
|
1023
|
-
const stderr = input.stderr ?? "";
|
|
1024
|
-
const interleaved = input.interleavedOutput ?? "";
|
|
1025
|
-
if (isNpmAuditCommand(input.command)) {
|
|
1026
|
-
const body = stdout.trim() || interleaved.trim() || stderr.trim();
|
|
1027
|
-
return classifyNpmAuditOutcome({
|
|
1028
|
-
exitCode: input.exitCode,
|
|
1029
|
-
stdout: body,
|
|
1030
|
-
stderr
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
if (isRgCommand(input.command)) {
|
|
1034
|
-
return classifyRgOutcome({
|
|
1035
|
-
command: input.command,
|
|
1036
|
-
exitCode: input.exitCode,
|
|
1037
|
-
stdout,
|
|
1038
|
-
stderr,
|
|
1039
|
-
interleaved
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
const searchDiagnosis = diagnoseRepoSearchFailure({
|
|
1043
|
-
command: input.command,
|
|
1044
|
-
exitCode: input.exitCode
|
|
1045
|
-
});
|
|
1046
|
-
if (searchDiagnosis && input.exitCode !== 0) {
|
|
1047
|
-
return {
|
|
1048
|
-
kind: "command_failure",
|
|
1049
|
-
exitCode: input.exitCode,
|
|
1050
|
-
summary: searchDiagnosis
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
if (input.exitCode === 0) {
|
|
1054
|
-
return {
|
|
1055
|
-
kind: "success",
|
|
1056
|
-
exitCode: 0,
|
|
1057
|
-
summary: `command succeeded (exit 0)`
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
|
|
1061
|
-
return {
|
|
1062
|
-
kind: "command_failure",
|
|
1063
|
-
exitCode: input.exitCode,
|
|
1064
|
-
summary: `command failed (exit ${input.exitCode}): ${tail}`
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
var NPM_AUDIT_RE, RG_CMD_RE, RG_REAL_ERROR_RE;
|
|
1068
|
-
var init_shell_command_outcome = __esm({
|
|
1069
|
-
"src/shell-command-outcome.ts"() {
|
|
1070
|
-
"use strict";
|
|
1071
|
-
init_repo_search();
|
|
1072
|
-
NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
1073
|
-
RG_CMD_RE = /\b(rg|ripgrep)\b/i;
|
|
1074
|
-
RG_REAL_ERROR_RE = /\b(error|invalid|unknown|panic|not found)\b/i;
|
|
1075
|
-
}
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
// src/stream.ts
|
|
1079
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
1080
|
-
function eventTimestampIso(event) {
|
|
1081
|
-
const tsMs = event.timestamp_ms;
|
|
1082
|
-
return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
|
|
1083
|
-
}
|
|
1084
|
-
function cursorToolNameFromCall(toolCall) {
|
|
1085
|
-
if (!toolCall) return null;
|
|
1086
|
-
for (const key of Object.keys(toolCall)) {
|
|
1087
|
-
if (key.endsWith("ToolCall")) {
|
|
1088
|
-
const stem = key.slice(0, -"ToolCall".length);
|
|
1089
|
-
return stem.length ? stem : key;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
return null;
|
|
1093
|
-
}
|
|
1094
|
-
function recordStreamResult(result, event) {
|
|
1095
|
-
result.finalResult = event.result || event.subtype || event.terminal_reason || "completed";
|
|
1096
|
-
if (event.is_error) {
|
|
1097
|
-
result.error = String(event.result || event.api_error_status || "stream result error");
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
function shellPayloadFromCursorEvent(event) {
|
|
1101
|
-
if (event.type !== "tool_call" || event.subtype !== "completed") return null;
|
|
1102
|
-
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
|
|
1103
|
-
const shell = toolCall?.shellToolCall;
|
|
1104
|
-
if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
|
|
1105
|
-
const shellObj = shell;
|
|
1106
|
-
const args = shellObj.args;
|
|
1107
|
-
const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
|
|
1108
|
-
const result = shellObj.result;
|
|
1109
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) return null;
|
|
1110
|
-
const body = result.success ?? result.failure;
|
|
1111
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
1112
|
-
const row = body;
|
|
1113
|
-
const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
|
|
1114
|
-
return {
|
|
1115
|
-
command,
|
|
1116
|
-
exitCode,
|
|
1117
|
-
stdout: typeof row.stdout === "string" ? row.stdout : "",
|
|
1118
|
-
stderr: typeof row.stderr === "string" ? row.stderr : "",
|
|
1119
|
-
interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
function applyShellOutcome(parsed, outcome) {
|
|
1123
|
-
if (outcome.kind === "success" || outcome.kind === "search_no_matches") return;
|
|
1124
|
-
parsed.lastShellOutcome = outcome;
|
|
1125
|
-
}
|
|
1126
|
-
function parseHarnessStream(file) {
|
|
1127
|
-
const result = {
|
|
1128
|
-
firstEventAt: null,
|
|
1129
|
-
lastEventAt: null,
|
|
1130
|
-
currentTool: null,
|
|
1131
|
-
finalResult: null,
|
|
1132
|
-
error: null,
|
|
1133
|
-
lastShellOutcome: null
|
|
1134
|
-
};
|
|
1135
|
-
if (!existsSync6(file)) return result;
|
|
1136
|
-
const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
|
|
1137
|
-
for (const line of lines) {
|
|
1138
|
-
const event = safeJson(line);
|
|
1139
|
-
if (!event) continue;
|
|
1140
|
-
const ts = eventTimestampIso(event);
|
|
1141
|
-
if (ts) {
|
|
1142
|
-
result.firstEventAt ||= ts;
|
|
1143
|
-
result.lastEventAt = ts;
|
|
1144
|
-
}
|
|
1145
|
-
if (event.type === "stream_event" && event.event && typeof event.event === "object" && event.event.type === "content_block_start") {
|
|
1146
|
-
const block = event.event.content_block;
|
|
1147
|
-
if (block?.type === "tool_use") result.currentTool = String(block.name || "tool");
|
|
1148
|
-
}
|
|
1149
|
-
if (event.type === "assistant" && event.message && typeof event.message === "object") {
|
|
1150
|
-
const content = event.message.content;
|
|
1151
|
-
if (Array.isArray(content)) {
|
|
1152
|
-
const tool = content.find((item) => item?.type === "tool_use");
|
|
1153
|
-
if (tool) result.currentTool = String(tool.name || result.currentTool);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
if (event.type === "tool_call" && event.subtype === "started") {
|
|
1157
|
-
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
|
|
1158
|
-
const name = cursorToolNameFromCall(toolCall);
|
|
1159
|
-
if (name) result.currentTool = name;
|
|
1160
|
-
}
|
|
1161
|
-
const shell = shellPayloadFromCursorEvent(event);
|
|
1162
|
-
if (shell) {
|
|
1163
|
-
applyShellOutcome(
|
|
1164
|
-
result,
|
|
1165
|
-
classifyShellCommandOutcome({
|
|
1166
|
-
command: shell.command,
|
|
1167
|
-
exitCode: shell.exitCode,
|
|
1168
|
-
stdout: shell.stdout,
|
|
1169
|
-
stderr: shell.stderr,
|
|
1170
|
-
interleavedOutput: shell.interleaved
|
|
1171
|
-
})
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
if (event.type === "result") {
|
|
1175
|
-
recordStreamResult(result, event);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
return result;
|
|
1179
|
-
}
|
|
1180
|
-
var init_stream = __esm({
|
|
1181
|
-
"src/stream.ts"() {
|
|
1182
|
-
"use strict";
|
|
1183
|
-
init_shell_command_outcome();
|
|
1184
|
-
init_util();
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
|
|
1188
|
-
// src/exit-classify.ts
|
|
1189
|
-
function tidy2(errorText, max = 240) {
|
|
1190
|
-
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
1191
|
-
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
1192
|
-
}
|
|
1193
|
-
function classifyExitFailure(errorText) {
|
|
1194
|
-
const text = (errorText ?? "").trim();
|
|
1195
|
-
if (!text) return null;
|
|
1196
|
-
for (const pattern of FAILURE_PATTERNS) {
|
|
1197
|
-
if (pattern.test.test(text)) {
|
|
1198
|
-
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
return null;
|
|
1202
|
-
}
|
|
1203
|
-
var FAILURE_PATTERNS;
|
|
1204
|
-
var init_exit_classify = __esm({
|
|
1205
|
-
"src/exit-classify.ts"() {
|
|
1206
|
-
"use strict";
|
|
1207
|
-
FAILURE_PATTERNS = [
|
|
1208
|
-
{
|
|
1209
|
-
test: /\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,
|
|
1210
|
-
label: "provider rejected the requested model"
|
|
1211
|
-
},
|
|
1212
|
-
{
|
|
1213
|
-
test: /\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,
|
|
1214
|
-
label: "provider rejected the requested model"
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
test: /\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,
|
|
1218
|
-
label: "provider rejected the requested model"
|
|
1219
|
-
},
|
|
1220
|
-
{
|
|
1221
|
-
test: /model preflight failed/i,
|
|
1222
|
-
label: "model/provider preflight failed"
|
|
1223
|
-
},
|
|
1224
|
-
{
|
|
1225
|
-
test: /\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,
|
|
1226
|
-
label: "provider CLI is missing or not on PATH"
|
|
1227
|
-
},
|
|
1228
|
-
{
|
|
1229
|
-
test: /\bfailed to spawn\b/i,
|
|
1230
|
-
label: "provider failed to spawn the worker process"
|
|
1231
|
-
},
|
|
1232
|
-
{
|
|
1233
|
-
test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
|
|
1234
|
-
label: "provider authentication failed"
|
|
1235
|
-
}
|
|
1236
|
-
];
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
// src/exited-salvage.ts
|
|
1241
|
-
function trimOrNull(value) {
|
|
1242
|
-
if (typeof value !== "string") return null;
|
|
1243
|
-
const trimmed = value.trim();
|
|
1244
|
-
return trimmed.length ? trimmed : null;
|
|
1245
|
-
}
|
|
1246
|
-
function hasFinalResult(value) {
|
|
1247
|
-
if (value === void 0 || value === null) return false;
|
|
1248
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1249
|
-
if (typeof value === "boolean") return value;
|
|
1250
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1251
|
-
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1252
|
-
return true;
|
|
1253
|
-
}
|
|
1254
|
-
function committedHeadFromAncestry(ancestry) {
|
|
1255
|
-
if (!ancestry?.checked) return null;
|
|
1256
|
-
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1257
|
-
return trimOrNull(ancestry.head);
|
|
1258
|
-
}
|
|
1259
|
-
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1260
|
-
const parts = ["exited_with_changes_salvage"];
|
|
1261
|
-
if (kind === "uncommitted" || kind === "both") {
|
|
1262
|
-
parts.push(
|
|
1263
|
-
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1267
|
-
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1268
|
-
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1269
|
-
}
|
|
1270
|
-
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1271
|
-
return parts.join(": ");
|
|
1272
|
-
}
|
|
1273
|
-
function assessExitedWorkerSalvage(input) {
|
|
1274
|
-
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1275
|
-
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1276
|
-
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1277
|
-
const hasUncommitted = uncommittedCount > 0;
|
|
1278
|
-
const hasCommittedAhead = Boolean(headCommit);
|
|
1279
|
-
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1280
|
-
return {
|
|
1281
|
-
kind: "none",
|
|
1282
|
-
salvageable: false,
|
|
1283
|
-
uncommittedCount: 0,
|
|
1284
|
-
headCommit: null,
|
|
1285
|
-
attentionReason: "process exited without a final result"
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1289
|
-
return {
|
|
1290
|
-
kind,
|
|
1291
|
-
salvageable: true,
|
|
1292
|
-
uncommittedCount,
|
|
1293
|
-
headCommit,
|
|
1294
|
-
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
var init_exited_salvage = __esm({
|
|
1298
|
-
"src/exited-salvage.ts"() {
|
|
1299
|
-
"use strict";
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1302
|
-
|
|
1303
|
-
// src/landing-gate.ts
|
|
1304
|
-
function trimOrNull2(value) {
|
|
1305
|
-
if (typeof value !== "string") return null;
|
|
1306
|
-
const trimmed = value.trim();
|
|
1307
|
-
return trimmed.length ? trimmed : null;
|
|
1308
|
-
}
|
|
1309
|
-
function hasFinalResult2(value) {
|
|
1310
|
-
if (value === void 0 || value === null) return false;
|
|
1311
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1312
|
-
if (typeof value === "boolean") return value;
|
|
1313
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1314
|
-
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1315
|
-
return true;
|
|
1316
|
-
}
|
|
1317
|
-
function hasCommittedLandingRef(snapshot) {
|
|
1318
|
-
if (trimOrNull2(snapshot.headCommit)) return true;
|
|
1319
|
-
if (trimOrNull2(snapshot.prUrl)) return true;
|
|
1320
|
-
if (trimOrNull2(snapshot.artifactBundlePath)) return true;
|
|
1321
|
-
if (trimOrNull2(snapshot.patchPath)) return true;
|
|
1322
|
-
const ancestry = snapshot.gitAncestry;
|
|
1323
|
-
if (ancestry?.checked && ancestry.headIsAncestorOfBase === false && trimOrNull2(ancestry.head)) {
|
|
1324
|
-
return true;
|
|
1325
|
-
}
|
|
1326
|
-
return false;
|
|
1327
|
-
}
|
|
1328
|
-
function assessWorkerLanding(snapshot) {
|
|
1329
|
-
if (!hasFinalResult2(snapshot.finalResult)) return { blocked: false };
|
|
1330
|
-
if (snapshot.changedFiles.length === 0) return { blocked: false };
|
|
1331
|
-
if (!hasCommittedLandingRef(snapshot)) {
|
|
1332
|
-
return {
|
|
1333
|
-
blocked: true,
|
|
1334
|
-
reason: "dirty_worktree_no_pr",
|
|
1335
|
-
detail: `Worktree has ${snapshot.changedFiles.length} uncommitted change(s) with no commit or PR; commit, open a PR, or discard before landing`
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
return {
|
|
1339
|
-
blocked: true,
|
|
1340
|
-
detail: `Worktree has ${snapshot.changedFiles.length} uncommitted change(s); commit or discard before landing`
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
function landingAttentionReason(verdict) {
|
|
1344
|
-
if (!verdict.blocked) return void 0;
|
|
1345
|
-
return verdict.detail ?? verdict.reason ?? "dirty_worktree_no_pr";
|
|
1346
|
-
}
|
|
1347
|
-
var init_landing_gate = __esm({
|
|
1348
|
-
"src/landing-gate.ts"() {
|
|
1349
|
-
"use strict";
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
// src/worker-final-result-embed.ts
|
|
1354
|
-
function tryParseJsonObject2(text) {
|
|
1355
|
-
const trimmed = text.trim();
|
|
1356
|
-
if (!trimmed.startsWith("{")) return null;
|
|
1357
|
-
try {
|
|
1358
|
-
const parsed = JSON.parse(trimmed);
|
|
1359
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1360
|
-
return parsed;
|
|
1361
|
-
}
|
|
1362
|
-
} catch {
|
|
1363
|
-
return null;
|
|
1364
|
-
}
|
|
1365
|
-
return null;
|
|
1366
|
-
}
|
|
1367
|
-
function reconciliationEntryCount(record) {
|
|
1368
|
-
const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation ?? record.targetPrs ?? record.target_prs;
|
|
1369
|
-
return Array.isArray(raw) ? raw.length : 0;
|
|
1370
|
-
}
|
|
1371
|
-
function extractEmbeddedWorkerFinalResultRecord(value) {
|
|
1372
|
-
const trimmed = value.trim();
|
|
1373
|
-
if (!trimmed) return null;
|
|
1374
|
-
const direct = tryParseJsonObject2(trimmed);
|
|
1375
|
-
if (direct) return direct;
|
|
1376
|
-
const candidates = [];
|
|
1377
|
-
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
1378
|
-
let fenceMatch;
|
|
1379
|
-
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
1380
|
-
const fromFence = tryParseJsonObject2(fenceMatch[1] ?? "");
|
|
1381
|
-
if (fromFence) candidates.push(fromFence);
|
|
1382
|
-
}
|
|
1383
|
-
const firstBrace = trimmed.indexOf("{");
|
|
1384
|
-
const lastBrace = trimmed.lastIndexOf("}");
|
|
1385
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
1386
|
-
const slice = tryParseJsonObject2(trimmed.slice(firstBrace, lastBrace + 1));
|
|
1387
|
-
if (slice) candidates.push(slice);
|
|
1388
|
-
}
|
|
1389
|
-
if (candidates.length === 0) return null;
|
|
1390
|
-
let best = candidates[candidates.length - 1];
|
|
1391
|
-
let bestScore = reconciliationEntryCount(best);
|
|
1392
|
-
for (const candidate of candidates) {
|
|
1393
|
-
const score = reconciliationEntryCount(candidate);
|
|
1394
|
-
if (score > bestScore) {
|
|
1395
|
-
best = candidate;
|
|
1396
|
-
bestScore = score;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
return best;
|
|
1400
|
-
}
|
|
1401
|
-
var init_worker_final_result_embed = __esm({
|
|
1402
|
-
"src/worker-final-result-embed.ts"() {
|
|
1403
|
-
"use strict";
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
|
|
1407
|
-
// src/landing-contract-gate.ts
|
|
1408
|
-
function trimOrNull3(value) {
|
|
1409
|
-
if (typeof value !== "string") return null;
|
|
1410
|
-
const t = value.trim();
|
|
1411
|
-
return t.length ? t : null;
|
|
1412
|
-
}
|
|
1413
|
-
function hasFinalResult3(value) {
|
|
1414
|
-
if (value === void 0 || value === null) return false;
|
|
1415
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1416
|
-
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1417
|
-
return true;
|
|
1418
|
-
}
|
|
1419
|
-
function normalizePrUrl(url) {
|
|
1420
|
-
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
1421
|
-
if (!m) return trimOrNull3(url);
|
|
1422
|
-
return `https://github.com/${m[1]}/pull/${m[2]}`;
|
|
1423
|
-
}
|
|
1424
|
-
function prUrlSetKey(url) {
|
|
1425
|
-
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
1426
|
-
if (!m) return url.trim().toLowerCase();
|
|
1427
|
-
return `${m[1].toLowerCase()}/pull/${m[2]}`;
|
|
1428
|
-
}
|
|
1429
|
-
function parseReconciliation(finalResult) {
|
|
1430
|
-
let record = null;
|
|
1431
|
-
if (typeof finalResult === "string") {
|
|
1432
|
-
const embedded = extractEmbeddedWorkerFinalResultRecord(finalResult);
|
|
1433
|
-
if (embedded) record = embedded;
|
|
1434
|
-
} else if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
|
|
1435
|
-
record = finalResult;
|
|
1436
|
-
}
|
|
1437
|
-
if (!record) return [];
|
|
1438
|
-
const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
|
|
1439
|
-
if (!Array.isArray(raw)) return [];
|
|
1440
|
-
const out = [];
|
|
1441
|
-
for (const item of raw) {
|
|
1442
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
1443
|
-
const row = item;
|
|
1444
|
-
const prUrl = normalizePrUrl(String(row.prUrl ?? row.pr_url ?? ""));
|
|
1445
|
-
const outcome = trimOrNull3(row.outcome);
|
|
1446
|
-
if (!prUrl || outcome !== "merged" && outcome !== "skipped" && outcome !== "blocked") continue;
|
|
1447
|
-
out.push({
|
|
1448
|
-
prUrl,
|
|
1449
|
-
outcome,
|
|
1450
|
-
mergeCommit: trimOrNull3(row.mergeCommit ?? row.merge_commit),
|
|
1451
|
-
reason: trimOrNull3(row.reason)
|
|
1452
|
-
});
|
|
1453
|
-
}
|
|
1454
|
-
return out;
|
|
1455
|
-
}
|
|
1456
|
-
function workerAttachedPrUrls(snapshot, finalResult) {
|
|
1457
|
-
const urls = [];
|
|
1458
|
-
const fromSnapshot = normalizePrUrl(trimOrNull3(snapshot.prUrl) ?? "");
|
|
1459
|
-
if (fromSnapshot) urls.push(fromSnapshot);
|
|
1460
|
-
if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
|
|
1461
|
-
const record = finalResult;
|
|
1462
|
-
const pr = normalizePrUrl(String(record.prUrl ?? record.pr_url ?? ""));
|
|
1463
|
-
if (pr) urls.push(pr);
|
|
1464
|
-
}
|
|
1465
|
-
return [...new Set(urls)];
|
|
1466
|
-
}
|
|
1467
|
-
function assessWorkerLandingContract(input) {
|
|
1468
|
-
const { contract, snapshot } = input;
|
|
1469
|
-
const finalResult = input.finalResult ?? snapshot.finalResult;
|
|
1470
|
-
if (!contract.landingOnly && contract.targetPrUrls.length === 0 && !contract.repairEnforceOriginalPr) {
|
|
1471
|
-
return { blocked: false };
|
|
1472
|
-
}
|
|
1473
|
-
if (!hasFinalResult3(finalResult)) {
|
|
1474
|
-
const requiresEarly = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
|
|
1475
|
-
if (requiresEarly && contract.targetPrUrls.length > 0) {
|
|
1476
|
-
return {
|
|
1477
|
-
blocked: true,
|
|
1478
|
-
reason: "missing_target_pr_reconciliation",
|
|
1479
|
-
detail: `Final result required to reconcile target PR(s): ${contract.targetPrUrls.join(", ")}`
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
return { blocked: false };
|
|
1483
|
-
}
|
|
1484
|
-
const requiresTargetPrReconciliation = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
|
|
1485
|
-
if (!requiresTargetPrReconciliation && !contract.repairEnforceOriginalPr) {
|
|
1486
|
-
return { blocked: false };
|
|
1487
|
-
}
|
|
1488
|
-
const repairTarget = contract.repairEnforceOriginalPr ? normalizePrUrl(trimOrNull3(contract.targetPrUrl) ?? "") ?? (contract.targetPrUrls.length === 1 ? normalizePrUrl(contract.targetPrUrls[0]) : null) : null;
|
|
1489
|
-
if (repairTarget) {
|
|
1490
|
-
const workerPrs = workerAttachedPrUrls(snapshot, finalResult);
|
|
1491
|
-
const supersedes = finalResult && typeof finalResult === "object" && !Array.isArray(finalResult) && finalResult.supersedesOriginalTargetPr === true;
|
|
1492
|
-
if (!supersedes) {
|
|
1493
|
-
for (const pr of workerPrs) {
|
|
1494
|
-
if (pr !== repairTarget) {
|
|
1495
|
-
return {
|
|
1496
|
-
blocked: true,
|
|
1497
|
-
reason: "duplicate_repair_pr",
|
|
1498
|
-
detail: `Repair worker opened or attached PR ${pr} instead of canonical target ${repairTarget}`
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
const reconciliation2 = parseReconciliation(finalResult);
|
|
1504
|
-
const entry = reconciliation2.find((r) => r.prUrl === repairTarget);
|
|
1505
|
-
if (!entry || entry.outcome !== "merged" && !(entry.reason?.trim() && (entry.outcome === "skipped" || entry.outcome === "blocked"))) {
|
|
1506
|
-
return {
|
|
1507
|
-
blocked: true,
|
|
1508
|
-
reason: "missing_repair_target_reconciliation",
|
|
1509
|
-
detail: `Repair worker must reconcile target PR ${repairTarget}`
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
const reconciliation = parseReconciliation(finalResult);
|
|
1514
|
-
const byUrl = new Map(
|
|
1515
|
-
reconciliation.map((r) => [prUrlSetKey(r.prUrl), r])
|
|
1516
|
-
);
|
|
1517
|
-
const targetSet = new Set(
|
|
1518
|
-
contract.targetPrUrls.map((u) => prUrlSetKey(normalizePrUrl(u) ?? u)).filter(Boolean)
|
|
1519
|
-
);
|
|
1520
|
-
const attachedPrs = workerAttachedPrUrls(snapshot, finalResult);
|
|
1521
|
-
if (contract.landingOnly) {
|
|
1522
|
-
for (const pr of attachedPrs) {
|
|
1523
|
-
if (targetSet.size > 0 && !targetSet.has(prUrlSetKey(pr))) {
|
|
1524
|
-
return {
|
|
1525
|
-
blocked: true,
|
|
1526
|
-
reason: "unrelated_implementation_pr",
|
|
1527
|
-
detail: `Landing-only worker attached unrelated PR ${pr}`
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
if (targetSet.size === 0) {
|
|
1531
|
-
return {
|
|
1532
|
-
blocked: true,
|
|
1533
|
-
reason: "unrelated_implementation_pr",
|
|
1534
|
-
detail: "Landing-only worker must not open new implementation PRs"
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
if (contract.targetPrUrls.length === 0) return { blocked: false };
|
|
1540
|
-
const missing = [];
|
|
1541
|
-
for (const target of contract.targetPrUrls) {
|
|
1542
|
-
const key = prUrlSetKey(normalizePrUrl(target) ?? target);
|
|
1543
|
-
const entry = byUrl.get(key);
|
|
1544
|
-
if (!entry) {
|
|
1545
|
-
missing.push(key);
|
|
1546
|
-
continue;
|
|
1547
|
-
}
|
|
1548
|
-
if (entry.outcome !== "merged" && !entry.reason?.trim()) {
|
|
1549
|
-
missing.push(key);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
if (missing.length > 0) {
|
|
1553
|
-
return {
|
|
1554
|
-
blocked: true,
|
|
1555
|
-
reason: missing.every((u) => byUrl.has(u)) ? "incomplete_target_pr_landing" : "missing_target_pr_reconciliation",
|
|
1556
|
-
detail: `Target PR reconciliation incomplete: ${missing.join(", ")}`
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
return { blocked: false };
|
|
1560
|
-
}
|
|
1561
|
-
function landingContractAttentionReason(verdict) {
|
|
1562
|
-
if (!verdict.blocked) return void 0;
|
|
1563
|
-
return verdict.detail ?? verdict.reason;
|
|
1564
|
-
}
|
|
1565
|
-
var init_landing_contract_gate = __esm({
|
|
1566
|
-
"src/landing-contract-gate.ts"() {
|
|
1567
|
-
"use strict";
|
|
1568
|
-
init_worker_final_result_embed();
|
|
1569
|
-
}
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
// src/status.ts
|
|
1573
|
-
function computeAttention(input) {
|
|
1574
|
-
const now = Date.now();
|
|
1575
|
-
if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
|
|
1576
|
-
return { state: "blocked", reason: input.completionBlocker };
|
|
1577
|
-
}
|
|
1578
|
-
if (input.finalResult) {
|
|
1579
|
-
if (input.localOnly && hasMergedTargetPrReconciliation(input.finalResult)) {
|
|
1580
|
-
return { state: "done", reason: "local-only worker superseded by merged PR" };
|
|
1581
|
-
}
|
|
1582
|
-
const landingSnapshot = {
|
|
1583
|
-
finalResult: input.finalResult,
|
|
1584
|
-
changedFiles: input.changedFiles ?? [],
|
|
1585
|
-
gitAncestry: input.gitAncestry ?? null,
|
|
1586
|
-
prUrl: input.prUrl ?? null
|
|
1587
|
-
};
|
|
1588
|
-
const landing = assessWorkerLanding(landingSnapshot);
|
|
1589
|
-
if (landing.blocked) {
|
|
1590
|
-
const detail = landingAttentionReason(landing);
|
|
1591
|
-
return {
|
|
1592
|
-
state: "needs_attention",
|
|
1593
|
-
reason: landing.reason ? `landing blocked (${landing.reason}): ${detail}` : `landing blocked: ${detail}`
|
|
1594
|
-
};
|
|
1595
|
-
}
|
|
1596
|
-
if (input.landingContract) {
|
|
1597
|
-
const contractVerdict = assessWorkerLandingContract({
|
|
1598
|
-
contract: input.landingContract,
|
|
1599
|
-
snapshot: landingSnapshot,
|
|
1600
|
-
finalResult: input.finalResult
|
|
1601
|
-
});
|
|
1602
|
-
const contractDetail = landingContractAttentionReason(contractVerdict);
|
|
1603
|
-
if (contractDetail) {
|
|
1604
|
-
return {
|
|
1605
|
-
state: "needs_attention",
|
|
1606
|
-
reason: contractVerdict.reason ? `landing contract (${contractVerdict.reason}): ${contractDetail}` : `landing contract: ${contractDetail}`
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
return { state: "done", reason: "final result recorded" };
|
|
1611
|
-
}
|
|
1612
|
-
if (!input.alive) {
|
|
1613
|
-
if (isAbandonedEmptyWorker(input)) {
|
|
1614
|
-
return { state: "done", reason: "empty abandoned worker record" };
|
|
1615
|
-
}
|
|
1616
|
-
const classified = classifyExitFailure(input.error);
|
|
1617
|
-
if (classified) return { state: "blocked", reason: classified.reason };
|
|
1618
|
-
const salvage = assessExitedWorkerSalvage({
|
|
1619
|
-
alive: false,
|
|
1620
|
-
finalResult: null,
|
|
1621
|
-
changedFiles: input.changedFiles,
|
|
1622
|
-
gitAncestry: input.gitAncestry
|
|
1623
|
-
});
|
|
1624
|
-
if (salvage?.salvageable) {
|
|
1625
|
-
const tail2 = input.error?.trim();
|
|
1626
|
-
return {
|
|
1627
|
-
state: "needs_attention",
|
|
1628
|
-
reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
const tail = input.error?.trim();
|
|
1632
|
-
return {
|
|
1633
|
-
state: "needs_attention",
|
|
1634
|
-
reason: tail ? `process exited without a final result: ${tail}` : salvage?.attentionReason ?? "process exited without a final result"
|
|
1635
|
-
};
|
|
1636
|
-
}
|
|
1637
|
-
if (input.heartbeatBlocker) {
|
|
1638
|
-
return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
|
|
1639
|
-
}
|
|
1640
|
-
const startMs = input.startedAt ? Date.parse(input.startedAt) : NaN;
|
|
1641
|
-
if (!input.firstEventAt && input.stdoutBytes === 0 && input.heartbeatBytes === 0 && Number.isFinite(startMs) && now - startMs > NO_START_MS) {
|
|
1642
|
-
return { state: "needs_attention", reason: `no first stream event ${secsAgo(startMs)}s after start` };
|
|
1643
|
-
}
|
|
1644
|
-
const actMs = input.lastActivityAt ? Date.parse(input.lastActivityAt) : NaN;
|
|
1645
|
-
if (Number.isFinite(actMs) && now - actMs > STALE_MS) {
|
|
1646
|
-
return { state: "stale", reason: `no log/event/heartbeat activity for ${secsAgo(actMs)}s` };
|
|
1647
|
-
}
|
|
1648
|
-
return { state: "ok", reason: "recent activity" };
|
|
1649
|
-
}
|
|
1650
|
-
function isSkippedTerminalCompletionBlocker(reason) {
|
|
1651
|
-
const text = reason?.trim();
|
|
1652
|
-
if (!text) return false;
|
|
1653
|
-
return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
|
|
1654
|
-
}
|
|
1655
|
-
function isAbandonedEmptyWorker(input) {
|
|
1656
|
-
if (input.finalResult) return false;
|
|
1657
|
-
if (input.taskId || input.agentOsId) return false;
|
|
1658
|
-
if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
|
|
1659
|
-
if (input.error?.trim()) return false;
|
|
1660
|
-
if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
|
|
1661
|
-
return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
|
|
1662
|
-
}
|
|
1663
|
-
function hasMergedTargetPrReconciliation(value) {
|
|
1664
|
-
let record = null;
|
|
1665
|
-
if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
|
|
1666
|
-
else if (value && typeof value === "object" && !Array.isArray(value)) record = value;
|
|
1667
|
-
if (!record) return false;
|
|
1668
|
-
const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
|
|
1669
|
-
if (!Array.isArray(raw)) return false;
|
|
1670
|
-
return raw.some((item) => {
|
|
1671
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
1672
|
-
return String(item.outcome ?? "").trim() === "merged";
|
|
1673
|
-
});
|
|
1674
|
-
}
|
|
1675
|
-
function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
|
|
1676
|
-
const ackSnapshot = worker.completionSnapshot?.finalResult;
|
|
1677
|
-
if (worker.completionAckSource === "local-pr-merged-reconcile" && ackSnapshot !== void 0 && ackSnapshot !== null) {
|
|
1678
|
-
return ackSnapshot;
|
|
1679
|
-
}
|
|
1680
|
-
if (parsedFinalResult) return parsedFinalResult;
|
|
1681
|
-
if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
|
|
1682
|
-
return terminalFinalResultFromHeartbeat(heartbeat);
|
|
1683
|
-
}
|
|
1684
|
-
function computeWorkerStatus(worker, options = {}) {
|
|
1685
|
-
const parsed = parseHarnessStream(worker.stdoutPath);
|
|
1686
|
-
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
1687
|
-
const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
|
|
1688
|
-
const finalResult = resolveFinalResult(worker, parsed.finalResult, heartbeat);
|
|
1689
|
-
const alive = completionAcknowledged ? false : isPidAlive(worker.pid);
|
|
1690
|
-
const stdoutBytes = fileSize(worker.stdoutPath);
|
|
1691
|
-
const stderrBytes = fileSize(worker.stderrPath);
|
|
1692
|
-
const heartbeatBytes = fileSize(worker.heartbeatPath);
|
|
1693
|
-
const changedFiles = gitStatusShort(worker.worktreePath);
|
|
1694
|
-
const gitAncestry = computeGitAncestry(worker.worktreePath, {
|
|
1695
|
-
base: options.base,
|
|
1696
|
-
baseCommit: options.baseCommit
|
|
1697
|
-
});
|
|
1698
|
-
const lastActivityAt = latestIso([
|
|
1699
|
-
parsed.lastEventAt,
|
|
1700
|
-
heartbeat.lastHeartbeatAt,
|
|
1701
|
-
fileMtime(worker.stdoutPath),
|
|
1702
|
-
fileMtime(worker.stderrPath),
|
|
1703
|
-
fileMtime(worker.heartbeatPath)
|
|
1704
|
-
]);
|
|
1705
|
-
const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
1706
|
-
const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
|
|
1707
|
-
const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
|
|
1708
|
-
const landingContract = worker.repairTargetPrUrl ? {
|
|
1709
|
-
landingOnly: false,
|
|
1710
|
-
targetPrUrls: [worker.repairTargetPrUrl],
|
|
1711
|
-
targetPrUrl: worker.repairTargetPrUrl,
|
|
1712
|
-
repairEnforceOriginalPr: true
|
|
1713
|
-
} : null;
|
|
1714
|
-
const attention = computeAttention({
|
|
1715
|
-
alive,
|
|
1716
|
-
finalResult,
|
|
1717
|
-
firstEventAt: parsed.firstEventAt,
|
|
1718
|
-
stdoutBytes,
|
|
1719
|
-
stderrBytes,
|
|
1720
|
-
heartbeatBytes,
|
|
1721
|
-
lastActivityAt,
|
|
1722
|
-
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
1723
|
-
startedAt: worker.startedAt,
|
|
1724
|
-
error,
|
|
1725
|
-
changedFiles,
|
|
1726
|
-
gitAncestry,
|
|
1727
|
-
completionBlocker: effectiveCompletionBlocker,
|
|
1728
|
-
landingContract,
|
|
1729
|
-
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
|
|
1730
|
-
localOnly: worker.localOnly === true,
|
|
1731
|
-
taskId: worker.taskId ?? null,
|
|
1732
|
-
agentOsId: worker.agentOsId ?? null,
|
|
1733
|
-
reconcileReason: worker.reconcileReason ?? null
|
|
1734
|
-
});
|
|
1735
|
-
const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
1736
|
-
return {
|
|
1737
|
-
runId: worker.runId,
|
|
1738
|
-
worker: worker.name,
|
|
1739
|
-
pid: worker.pid,
|
|
1740
|
-
alive,
|
|
1741
|
-
status: workerStatusLabel,
|
|
1742
|
-
attention,
|
|
1743
|
-
branch: worker.branch,
|
|
1744
|
-
worktreePath: worker.worktreePath,
|
|
1745
|
-
ownedPaths: worker.ownedPaths,
|
|
1746
|
-
stdoutBytes,
|
|
1747
|
-
stderrBytes,
|
|
1748
|
-
heartbeatBytes,
|
|
1749
|
-
firstEventAt: parsed.firstEventAt,
|
|
1750
|
-
lastEventAt: parsed.lastEventAt,
|
|
1751
|
-
lastActivityAt,
|
|
1752
|
-
currentTool: completionAcknowledged ? null : parsed.currentTool,
|
|
1753
|
-
heartbeatCount: heartbeat.heartbeatCount,
|
|
1754
|
-
lastHeartbeatAt: heartbeat.lastHeartbeatAt,
|
|
1755
|
-
lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
|
|
1756
|
-
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
1757
|
-
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
1758
|
-
timestampAnomalies: heartbeat.timestampAnomalies,
|
|
1759
|
-
finalResult,
|
|
1760
|
-
error,
|
|
1761
|
-
changedFiles,
|
|
1762
|
-
gitAncestry,
|
|
1763
|
-
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
1764
|
-
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null,
|
|
1765
|
-
model: worker.model ?? worker.orchestrationAudit?.model ?? null,
|
|
1766
|
-
provider: worker.orchestrationAudit?.provider ?? null,
|
|
1767
|
-
boxKind: worker.boxKind ?? null,
|
|
1768
|
-
boxId: worker.boxId ?? null,
|
|
1769
|
-
runtimeId: worker.runtimeId ?? null,
|
|
1770
|
-
personaSlug: worker.personaSlug ?? null,
|
|
1771
|
-
dispatched: worker.dispatched ?? null,
|
|
1772
|
-
localOnly: worker.localOnly ?? null
|
|
1773
|
-
};
|
|
1774
|
-
}
|
|
1775
|
-
function isFinishedWorkerStatus(status) {
|
|
1776
|
-
if (status.finalResult) return true;
|
|
1777
|
-
if (status.alive === false) return true;
|
|
1778
|
-
if (status.status === "exited" || status.status === "done") return true;
|
|
1779
|
-
return false;
|
|
1780
|
-
}
|
|
1781
|
-
function isLandingBlockedWorkerStatus(status) {
|
|
1782
|
-
if (!status.finalResult) return false;
|
|
1783
|
-
return status.attention.state === "needs_attention" || status.attention.state === "blocked";
|
|
1784
|
-
}
|
|
1785
|
-
var NO_START_MS, STALE_MS;
|
|
1786
|
-
var init_status = __esm({
|
|
1787
|
-
"src/status.ts"() {
|
|
1788
|
-
"use strict";
|
|
1789
|
-
init_heartbeat();
|
|
1790
|
-
init_stream();
|
|
1791
|
-
init_exit_classify();
|
|
1792
|
-
init_exited_salvage();
|
|
1793
|
-
init_git();
|
|
1794
|
-
init_landing_gate();
|
|
1795
|
-
init_landing_contract_gate();
|
|
1796
|
-
init_worker_final_result_embed();
|
|
1797
|
-
init_util();
|
|
1798
|
-
NO_START_MS = 18e4;
|
|
1799
|
-
STALE_MS = 6e5;
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
|
|
1803
|
-
// src/harness-worker-active.ts
|
|
1804
|
-
var init_harness_worker_active = __esm({
|
|
1805
|
-
"src/harness-worker-active.ts"() {
|
|
1806
|
-
"use strict";
|
|
1807
|
-
init_status();
|
|
1808
|
-
}
|
|
1809
|
-
});
|
|
1810
|
-
|
|
1811
|
-
// src/resource-gate.ts
|
|
1812
|
-
var DEFAULT_PER_WORKER_MEM_BYTES, DEFAULT_MEM_RESERVE_BYTES;
|
|
1813
|
-
var init_resource_gate = __esm({
|
|
1814
|
-
"src/resource-gate.ts"() {
|
|
1815
|
-
"use strict";
|
|
1816
|
-
init_meminfo();
|
|
1817
|
-
init_config();
|
|
1818
|
-
init_box_identity();
|
|
1819
|
-
init_worker_cap_source();
|
|
1820
|
-
init_disk_gate();
|
|
1821
|
-
init_run_store();
|
|
1822
|
-
init_run_worker_index();
|
|
1823
|
-
init_harness_worker_active();
|
|
1824
|
-
init_util();
|
|
1825
|
-
DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
1826
|
-
DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
1827
|
-
}
|
|
1828
|
-
});
|
|
1829
|
-
|
|
1830
|
-
// src/worker-cap-source.ts
|
|
1831
|
-
var init_worker_cap_source = __esm({
|
|
1832
|
-
"src/worker-cap-source.ts"() {
|
|
1833
|
-
"use strict";
|
|
1834
|
-
init_resource_gate();
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
|
|
1838
|
-
// src/config.ts
|
|
1839
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1840
|
-
import { homedir as homedir2, totalmem } from "node:os";
|
|
1841
|
-
import path5 from "node:path";
|
|
1842
|
-
function loadUserConfig() {
|
|
1843
|
-
if (!existsSync7(CONFIG_FILE)) return {};
|
|
1844
|
-
try {
|
|
1845
|
-
return JSON.parse(readFileSync5(CONFIG_FILE, "utf8"));
|
|
1846
|
-
} catch {
|
|
1847
|
-
return {};
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
var CONFIG_DIR, CONFIG_FILE, CREDENTIALS_FILE, SETUP_PER_WORKER_MEM_BYTES, SETUP_MEM_RESERVE_BYTES;
|
|
1851
|
-
var init_config = __esm({
|
|
1852
|
-
"src/config.ts"() {
|
|
1853
|
-
"use strict";
|
|
1854
|
-
init_default_repo_discovery();
|
|
1855
|
-
init_path_values();
|
|
1856
|
-
init_util();
|
|
1857
|
-
init_box_identity();
|
|
1858
|
-
init_worker_cap_source();
|
|
1859
|
-
init_disk_gate();
|
|
1860
|
-
CONFIG_DIR = path5.join(homedir2(), ".kynver");
|
|
1861
|
-
CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
|
|
1862
|
-
CREDENTIALS_FILE = path5.join(CONFIG_DIR, "credentials");
|
|
1863
|
-
SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
1864
|
-
SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
1865
|
-
}
|
|
1866
|
-
});
|
|
1867
|
-
|
|
1868
|
-
// src/paths.ts
|
|
1869
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
1870
|
-
import { homedir as homedir3 } from "node:os";
|
|
1871
|
-
import path6 from "node:path";
|
|
1872
|
-
function normalizeHarnessRoot(root) {
|
|
1873
|
-
let resolved = path6.resolve(resolveUserPath(root.trim()));
|
|
1874
|
-
while (HARNESS_LAYOUT_DIR_NAMES.has(path6.basename(resolved))) {
|
|
1875
|
-
resolved = path6.dirname(resolved);
|
|
1876
|
-
}
|
|
1877
|
-
return resolved;
|
|
1878
|
-
}
|
|
1879
|
-
function resolveHarnessRoot() {
|
|
1880
|
-
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
1881
|
-
if (env) return normalizeHarnessRoot(env);
|
|
1882
|
-
const configured = loadUserConfig().harnessRoot?.trim();
|
|
1883
|
-
if (configured) return normalizeHarnessRoot(configured);
|
|
1884
|
-
const kynverRoot = path6.join(homedir3(), ".kynver", "harness");
|
|
1885
|
-
if (existsSync8(kynverRoot)) return kynverRoot;
|
|
1886
|
-
if (existsSync8(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
1887
|
-
return kynverRoot;
|
|
1888
|
-
}
|
|
1889
|
-
function harnessRunsDir(harnessRoot) {
|
|
1890
|
-
return path6.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
1891
|
-
}
|
|
1892
|
-
function harnessWorktreesDir(harnessRoot) {
|
|
1893
|
-
return path6.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
1894
|
-
}
|
|
1895
|
-
function getHarnessPaths() {
|
|
1896
|
-
const harnessRoot = resolveHarnessRoot();
|
|
1897
|
-
return {
|
|
1898
|
-
harnessRoot,
|
|
1899
|
-
runsDir: harnessRunsDir(harnessRoot),
|
|
1900
|
-
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
1901
|
-
};
|
|
1902
|
-
}
|
|
1903
|
-
function runDir(runsDir, id) {
|
|
1904
|
-
return path6.join(runsDir, safeSlug(id));
|
|
1905
|
-
}
|
|
1906
|
-
var LEGACY_ROOT, HARNESS_LAYOUT_DIR_NAMES;
|
|
1907
|
-
var init_paths = __esm({
|
|
1908
|
-
"src/paths.ts"() {
|
|
1909
|
-
"use strict";
|
|
1910
|
-
init_config();
|
|
1911
|
-
init_path_values();
|
|
1912
|
-
init_util();
|
|
1913
|
-
LEGACY_ROOT = path6.join(homedir3(), ".openclaw", "harness");
|
|
1914
|
-
HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
1915
|
-
}
|
|
1916
|
-
});
|
|
1917
|
-
|
|
1918
|
-
// src/cleanup.ts
|
|
1919
|
-
init_paths();
|
|
1920
|
-
import path25 from "node:path";
|
|
1921
|
-
|
|
1922
|
-
// src/cleanup-guards.ts
|
|
1923
|
-
init_landing_gate();
|
|
1924
|
-
import path11 from "node:path";
|
|
1925
|
-
|
|
1926
|
-
// src/cleanup-index-status.ts
|
|
1927
|
-
init_git();
|
|
1928
|
-
init_status();
|
|
1929
|
-
|
|
1930
|
-
// src/cleanup-build-cache-paths.ts
|
|
1931
|
-
var HARNESS_BUILD_CACHE_RELATIVE_PATHS = [
|
|
1932
|
-
".next",
|
|
1933
|
-
".turbo",
|
|
1934
|
-
"dist",
|
|
1935
|
-
"build",
|
|
1936
|
-
".cache",
|
|
1937
|
-
"node_modules/.cache"
|
|
1938
|
-
];
|
|
1939
|
-
function isGeneratedHarnessPath(pathPart) {
|
|
1940
|
-
const normalized = pathPart.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1941
|
-
if (normalized === "node_modules" || normalized.startsWith("node_modules/")) return true;
|
|
1942
|
-
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
1943
|
-
if (normalized === rel || normalized.startsWith(`${rel}/`)) return true;
|
|
1944
|
-
}
|
|
1945
|
-
return false;
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
// src/cleanup-guards-helpers.ts
|
|
1949
|
-
function materialWorktreeChanges(changedFiles) {
|
|
1950
|
-
return changedFiles.filter((line) => {
|
|
1951
|
-
const trimmed = line.trim();
|
|
1952
|
-
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
1953
|
-
return !isGeneratedHarnessPath(pathPart);
|
|
1954
|
-
});
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
// src/cleanup-worktree-salvage.ts
|
|
1958
|
-
function prUrlFromFinalResult(finalResult) {
|
|
1959
|
-
if (typeof finalResult === "string") {
|
|
1960
|
-
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
1961
|
-
return match?.[0] ?? null;
|
|
1962
|
-
}
|
|
1963
|
-
if (finalResult && typeof finalResult === "object") {
|
|
1964
|
-
const obj = finalResult;
|
|
1965
|
-
for (const key of ["prUrl", "pr_url", "pullRequestUrl"]) {
|
|
1966
|
-
const value = obj[key];
|
|
1967
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
return null;
|
|
1971
|
-
}
|
|
1972
|
-
function isPrOrUnmergedWork(status) {
|
|
1973
|
-
const relation = status.gitAncestry?.relation;
|
|
1974
|
-
if (relation === "merged" || relation === "synced") {
|
|
1975
|
-
return materialWorktreeChanges(status.changedFiles).length > 0;
|
|
1976
|
-
}
|
|
1977
|
-
if (prUrlFromFinalResult(status.finalResult)) return true;
|
|
1978
|
-
if (relation === "ahead" || relation === "diverged") return true;
|
|
1979
|
-
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
1980
|
-
return false;
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
// src/cleanup-index-status.ts
|
|
1984
|
-
init_util();
|
|
1985
|
-
function indexedWorktreeStatus(entry) {
|
|
1986
|
-
if (!entry.status) {
|
|
1987
|
-
entry.status = computeWorkerStatus(entry.worker, {
|
|
1988
|
-
base: entry.run.base,
|
|
1989
|
-
baseCommit: entry.run.baseCommit
|
|
1990
|
-
});
|
|
1991
|
-
}
|
|
1992
|
-
return entry.status;
|
|
1993
|
-
}
|
|
1994
|
-
function indexedWorktreeHasMaterialChanges(entry, gitStatusCache) {
|
|
1995
|
-
if (entry.status) {
|
|
1996
|
-
return materialWorktreeChanges(entry.status.changedFiles).length > 0;
|
|
1997
|
-
}
|
|
1998
|
-
const porcelain = gitStatusCache ? gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
|
|
1999
|
-
return materialWorktreeChanges(porcelain).length > 0;
|
|
2000
|
-
}
|
|
2001
|
-
function finalResultFromWorkerJson(entry) {
|
|
2002
|
-
const snapshot = entry.worker.completionSnapshot?.finalResult;
|
|
2003
|
-
if (snapshot !== void 0 && snapshot !== null) return snapshot;
|
|
2004
|
-
if (entry.worker.taskPrUrl) {
|
|
2005
|
-
return { prUrl: entry.worker.taskPrUrl };
|
|
2006
|
-
}
|
|
2007
|
-
return null;
|
|
2008
|
-
}
|
|
2009
|
-
function resolveWorktreeGuardStatus(entry, ctx) {
|
|
2010
|
-
if (entry.status) return entry.status;
|
|
2011
|
-
const worker = entry.worker;
|
|
2012
|
-
const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
|
|
2013
|
-
const workerJsonTerminal = Boolean(worker.status && ["done", "exited", "blocked", "failed", "abandoned"].includes(worker.status)) || completionAcknowledged;
|
|
2014
|
-
const finalResult = finalResultFromWorkerJson(entry);
|
|
2015
|
-
if (workerJsonTerminal && !isPidAlive(worker.pid)) {
|
|
2016
|
-
const changedFiles = ctx?.gitStatusCache ? ctx.gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
|
|
2017
|
-
const baseLabel = entry.run.baseCommit?.trim() || entry.run.base?.trim() || "origin/main";
|
|
2018
|
-
const ahead = ctx?.gitRevCache?.countAheadOfMain(entry.worktreePath, baseLabel);
|
|
2019
|
-
const gitAncestry = ahead === 0 ? {
|
|
2020
|
-
checked: true,
|
|
2021
|
-
base: baseLabel,
|
|
2022
|
-
relation: "synced"
|
|
2023
|
-
} : computeGitAncestry(entry.worktreePath, {
|
|
2024
|
-
base: entry.run.base,
|
|
2025
|
-
baseCommit: entry.run.baseCommit
|
|
2026
|
-
});
|
|
2027
|
-
const status = {
|
|
2028
|
-
runId: entry.runId,
|
|
2029
|
-
worker: entry.workerName,
|
|
2030
|
-
pid: worker.pid,
|
|
2031
|
-
alive: false,
|
|
2032
|
-
status: worker.status ?? (completionAcknowledged ? "done" : "exited"),
|
|
2033
|
-
attention: { state: completionAcknowledged ? "done" : "stale" },
|
|
2034
|
-
branch: worker.branch,
|
|
2035
|
-
worktreePath: entry.worktreePath,
|
|
2036
|
-
ownedPaths: worker.ownedPaths,
|
|
2037
|
-
stdoutBytes: 0,
|
|
2038
|
-
stderrBytes: 0,
|
|
2039
|
-
heartbeatBytes: 0,
|
|
2040
|
-
firstEventAt: null,
|
|
2041
|
-
lastEventAt: null,
|
|
2042
|
-
lastActivityAt: worker.completionReportedAt ?? null,
|
|
2043
|
-
currentTool: null,
|
|
2044
|
-
heartbeatCount: 0,
|
|
2045
|
-
lastHeartbeatAt: null,
|
|
2046
|
-
lastHeartbeatPhase: null,
|
|
2047
|
-
lastHeartbeatSummary: null,
|
|
2048
|
-
heartbeatBlocker: null,
|
|
2049
|
-
changedFiles,
|
|
2050
|
-
gitAncestry,
|
|
2051
|
-
finalResult,
|
|
2052
|
-
completionBlocker: typeof worker.completionBlocker === "string" ? worker.completionBlocker.trim() || null : null,
|
|
2053
|
-
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? prUrlFromFinalResult(finalResult)
|
|
2054
|
-
};
|
|
2055
|
-
entry.status = status;
|
|
2056
|
-
return status;
|
|
2057
|
-
}
|
|
2058
|
-
return indexedWorktreeStatus(entry);
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
// src/cleanup-guards.ts
|
|
2062
|
-
init_status();
|
|
2063
|
-
|
|
2064
|
-
// src/finalize.ts
|
|
2065
|
-
init_run_store();
|
|
2066
|
-
init_run_worker_index();
|
|
2067
|
-
init_status();
|
|
2068
|
-
init_util();
|
|
2069
|
-
import path7 from "node:path";
|
|
2070
|
-
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
2071
|
-
"running",
|
|
2072
|
-
"dispatching",
|
|
2073
|
-
"pending",
|
|
2074
|
-
"queued",
|
|
2075
|
-
"needs_attention"
|
|
2076
|
-
]);
|
|
2077
|
-
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "done"]);
|
|
2078
|
-
function deriveTerminalRunStatus(run) {
|
|
2079
|
-
const names = listRunWorkerNames(run);
|
|
2080
|
-
if (names.length === 0) return "failed";
|
|
2081
|
-
let anyAlive = false;
|
|
2082
|
-
let anyResult = false;
|
|
2083
|
-
let anyCompletionBlocked = false;
|
|
2084
|
-
let anyLandingBlocked = false;
|
|
2085
|
-
for (const name of names) {
|
|
2086
|
-
const worker = readJson(
|
|
2087
|
-
path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2088
|
-
void 0
|
|
2089
|
-
);
|
|
2090
|
-
if (!worker) continue;
|
|
2091
|
-
const status = computeWorkerStatus(worker, {
|
|
2092
|
-
base: run.base,
|
|
2093
|
-
baseCommit: run.baseCommit
|
|
2094
|
-
});
|
|
2095
|
-
if (status.alive && !status.finalResult) {
|
|
2096
|
-
anyAlive = true;
|
|
2097
|
-
break;
|
|
2098
|
-
}
|
|
2099
|
-
if (typeof worker.completionBlocker === "string" && worker.completionBlocker) {
|
|
2100
|
-
anyCompletionBlocked = true;
|
|
2101
|
-
}
|
|
2102
|
-
if (isLandingBlockedWorkerStatus(status)) {
|
|
2103
|
-
anyLandingBlocked = true;
|
|
2104
|
-
}
|
|
2105
|
-
if (status.finalResult && status.attention.state === "done") anyResult = true;
|
|
2106
|
-
}
|
|
2107
|
-
if (anyAlive) return null;
|
|
2108
|
-
if (anyCompletionBlocked) return null;
|
|
2109
|
-
if (anyLandingBlocked) return null;
|
|
2110
|
-
return anyResult ? "completed" : "failed";
|
|
2111
|
-
}
|
|
2112
|
-
function finalizeStaleRuns() {
|
|
2113
|
-
const finalized = [];
|
|
2114
|
-
for (const run of listRunRecords()) {
|
|
2115
|
-
if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
|
|
2116
|
-
const next = deriveTerminalRunStatus(run);
|
|
2117
|
-
if (!next || next === run.status) continue;
|
|
2118
|
-
const from = run.status;
|
|
2119
|
-
run.status = next;
|
|
2120
|
-
saveRun(run);
|
|
2121
|
-
finalized.push({ runId: run.id, from, to: next });
|
|
2122
|
-
}
|
|
2123
|
-
return finalized;
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
// src/cleanup-run-liveness.ts
|
|
2127
|
-
init_status();
|
|
2128
|
-
|
|
2129
|
-
// src/cleanup-worker-harness-live.ts
|
|
2130
|
-
init_heartbeat();
|
|
2131
|
-
|
|
2132
|
-
// src/run-metadata-retention.ts
|
|
2133
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync4, renameSync, statSync as statSync3 } from "node:fs";
|
|
2134
|
-
import path9 from "node:path";
|
|
2135
|
-
|
|
2136
|
-
// src/default-repo.ts
|
|
2137
|
-
init_config();
|
|
2138
|
-
init_default_repo_discovery();
|
|
2139
|
-
init_path_values();
|
|
2140
|
-
|
|
2141
|
-
// src/run-metadata-retention.ts
|
|
2142
|
-
init_heartbeat();
|
|
2143
|
-
init_paths();
|
|
2144
|
-
init_run_store();
|
|
2145
|
-
|
|
2146
|
-
// src/worker-metadata-paths.ts
|
|
2147
|
-
init_paths();
|
|
2148
|
-
init_util();
|
|
2149
|
-
import path8 from "node:path";
|
|
2150
|
-
var NESTED_RUNS = `${path8.sep}runs${path8.sep}runs${path8.sep}`;
|
|
2151
|
-
function workerArtifactPaths(workerDir) {
|
|
2152
|
-
return {
|
|
2153
|
-
workerJsonPath: path8.join(workerDir, "worker.json"),
|
|
2154
|
-
stdoutPath: path8.join(workerDir, "stdout.jsonl"),
|
|
2155
|
-
stderrPath: path8.join(workerDir, "stderr.log"),
|
|
2156
|
-
heartbeatPath: path8.join(workerDir, "heartbeat.jsonl"),
|
|
2157
|
-
lastStatusPath: path8.join(workerDir, "last-status.json")
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
// src/run-metadata-retention.ts
|
|
2162
|
-
init_util();
|
|
2163
|
-
var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
|
|
2164
|
-
var TERMINAL_WORKER_ARCHIVE_AGE_MS = 60 * 60 * 1e3;
|
|
2165
|
-
function isHarnessRunMetadataPath(targetPath, harnessRoot) {
|
|
2166
|
-
const resolved = path9.resolve(targetPath);
|
|
2167
|
-
const runsDir = path9.resolve(harnessRunsDir(harnessRoot));
|
|
2168
|
-
const rel = path9.relative(runsDir, resolved);
|
|
2169
|
-
return rel !== ".." && !rel.startsWith("..") && !path9.isAbsolute(rel);
|
|
2170
|
-
}
|
|
2171
|
-
function listRunDirIds(runsDir) {
|
|
2172
|
-
if (!existsSync9(runsDir)) return [];
|
|
2173
|
-
try {
|
|
2174
|
-
return readdirSync4(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
|
|
2175
|
-
} catch {
|
|
2176
|
-
return [];
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
function listWorkerNamesOnDisk(runDir2) {
|
|
2180
|
-
const workersDir = path9.join(runDir2, "workers");
|
|
2181
|
-
if (!existsSync9(workersDir)) return [];
|
|
2182
|
-
try {
|
|
2183
|
-
return readdirSync4(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2184
|
-
} catch {
|
|
2185
|
-
return [];
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
function pathRecentlyTouched(target, now, windowMs) {
|
|
2189
|
-
if (!existsSync9(target)) return false;
|
|
2190
|
-
try {
|
|
2191
|
-
const age = now - statSync3(target).mtimeMs;
|
|
2192
|
-
return Number.isFinite(age) && age >= 0 && age < windowMs;
|
|
2193
|
-
} catch {
|
|
2194
|
-
return false;
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
2198
|
-
if (!existsSync9(workerDir)) return false;
|
|
2199
|
-
const artifacts = workerArtifactPaths(workerDir);
|
|
2200
|
-
const worker = readJson(
|
|
2201
|
-
artifacts.workerJsonPath,
|
|
2202
|
-
void 0
|
|
2203
|
-
);
|
|
2204
|
-
if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
|
|
2205
|
-
const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
|
|
2206
|
-
if (heartbeat.lastHeartbeatAt) {
|
|
2207
|
-
const age = now - Date.parse(heartbeat.lastHeartbeatAt);
|
|
2208
|
-
if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
|
|
2209
|
-
}
|
|
2210
|
-
if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
|
|
2211
|
-
if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
|
|
2212
|
-
return false;
|
|
2213
|
-
}
|
|
2214
|
-
function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
2215
|
-
for (const name of listWorkerNamesOnDisk(runDir2)) {
|
|
2216
|
-
if (workerDirHasActiveRetentionSignals(path9.join(runDir2, "workers", name), now, windowMs)) {
|
|
2217
|
-
return true;
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
return false;
|
|
2221
|
-
}
|
|
2222
|
-
function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
|
|
2223
|
-
const keys = /* @__PURE__ */ new Set();
|
|
2224
|
-
const runsDir = harnessRunsDir(harnessRoot);
|
|
2225
|
-
for (const runId of listRunDirIds(runsDir)) {
|
|
2226
|
-
const runDir2 = path9.join(runsDir, runId);
|
|
2227
|
-
if (runDirHasActiveRetentionSignals(runDir2, now)) {
|
|
2228
|
-
keys.add(`${harnessRoot}\0${runId}`);
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
return keys;
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
// src/cleanup-worker-harness-live.ts
|
|
2235
|
-
init_util();
|
|
2236
|
-
function heartbeatContentIsFresh(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
2237
|
-
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
2238
|
-
if (!heartbeat.lastHeartbeatAt) return false;
|
|
2239
|
-
const age = now - Date.parse(heartbeat.lastHeartbeatAt);
|
|
2240
|
-
return Number.isFinite(age) && age >= 0 && age < windowMs;
|
|
2241
|
-
}
|
|
2242
|
-
function isHarnessWorkerHarnessLive(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
2243
|
-
if (isPidAlive(worker.pid)) return true;
|
|
2244
|
-
return heartbeatContentIsFresh(worker, now, windowMs);
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
// src/cleanup-run-liveness.ts
|
|
2248
|
-
function deriveRunTerminal(indexed, ctx) {
|
|
2249
|
-
if (ctx) return ctx.runTerminalCache.derive(indexed.run);
|
|
2250
|
-
return deriveTerminalRunStatus(indexed.run);
|
|
2251
|
-
}
|
|
2252
|
-
function isWorkerProcessLive(indexed, now = Date.now()) {
|
|
2253
|
-
return isHarnessWorkerHarnessLive(indexed.worker, now);
|
|
2254
|
-
}
|
|
2255
|
-
function isRunStaleActive(indexed, ctx) {
|
|
2256
|
-
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
2257
|
-
return deriveRunTerminal(indexed, ctx) !== null;
|
|
2258
|
-
}
|
|
2259
|
-
function runBlocksWorktreeRemoval(indexed, ctx, now = Date.now()) {
|
|
2260
|
-
if (isWorkerProcessLive(indexed, now)) return true;
|
|
2261
|
-
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
2262
|
-
if (isRunStaleActive(indexed, ctx)) return false;
|
|
2263
|
-
const status = indexedWorktreeStatus(indexed);
|
|
2264
|
-
if (!isFinishedWorkerStatus(status)) {
|
|
2265
|
-
if (!isWorkerProcessLive(indexed, now)) return false;
|
|
2266
|
-
return true;
|
|
2267
|
-
}
|
|
2268
|
-
return false;
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
// src/cleanup-completion-blocker.ts
|
|
2272
|
-
init_landing_gate();
|
|
2273
|
-
init_status();
|
|
2274
|
-
function completionBlockerBlocksWorktreeRemoval(indexed, status) {
|
|
2275
|
-
const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
|
|
2276
|
-
if (!blocker) return false;
|
|
2277
|
-
if (isWorkerProcessLive(indexed)) return true;
|
|
2278
|
-
const resolved = status ?? indexedWorktreeStatus(indexed);
|
|
2279
|
-
if (!isFinishedWorkerStatus(resolved)) return true;
|
|
2280
|
-
if (isPrOrUnmergedWork(resolved)) return true;
|
|
2281
|
-
if (materialWorktreeChanges(resolved.changedFiles).length > 0) return true;
|
|
2282
|
-
const landing = assessWorkerLanding({
|
|
2283
|
-
finalResult: resolved.finalResult,
|
|
2284
|
-
changedFiles: resolved.changedFiles,
|
|
2285
|
-
gitAncestry: resolved.gitAncestry,
|
|
2286
|
-
prUrl: prUrlFromFinalResult(resolved.finalResult)
|
|
2287
|
-
});
|
|
2288
|
-
if (landing.blocked) return true;
|
|
2289
|
-
return false;
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
// src/cleanup-salvage-evidence.ts
|
|
2293
|
-
init_git();
|
|
2294
|
-
init_util();
|
|
2295
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2296
|
-
import path10 from "node:path";
|
|
2297
|
-
function salvageEvidenceDir(harnessRoot, runId, workerName) {
|
|
2298
|
-
return path10.join(harnessRoot, "salvage", safeSlug(runId), safeSlug(workerName));
|
|
2299
|
-
}
|
|
2300
|
-
function hasSalvageEvidence(input) {
|
|
2301
|
-
return existsSync10(path10.join(salvageEvidenceDir(input.harnessRoot, input.runId, input.workerName), "evidence.json"));
|
|
2302
|
-
}
|
|
2303
|
-
function writeWorktreeSalvageEvidence(input) {
|
|
2304
|
-
const dir = salvageEvidenceDir(input.harnessRoot, input.indexed.runId, input.indexed.workerName);
|
|
2305
|
-
mkdirSync4(dir, { recursive: true });
|
|
2306
|
-
const patchPath = path10.join(dir, "salvage.patch");
|
|
2307
|
-
let wrotePatch = false;
|
|
2308
|
-
if (existsSync10(input.indexed.worktreePath)) {
|
|
2309
|
-
const diff = gitCapture(input.indexed.worktreePath, ["diff", "HEAD"]);
|
|
2310
|
-
const staged = gitCapture(input.indexed.worktreePath, ["diff", "--cached"]);
|
|
2311
|
-
const patch = [diff.stdout, staged.stdout].filter((chunk) => chunk.trim()).join("\n");
|
|
2312
|
-
if (patch.trim()) {
|
|
2313
|
-
writeFileSync3(patchPath, patch.endsWith("\n") ? patch : `${patch}
|
|
2314
|
-
`);
|
|
2315
|
-
wrotePatch = true;
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
const record = {
|
|
2319
|
-
capturedAt: new Date(input.now ?? Date.now()).toISOString(),
|
|
2320
|
-
skipReason: input.skipReason,
|
|
2321
|
-
runId: input.indexed.runId,
|
|
2322
|
-
workerName: input.indexed.workerName,
|
|
2323
|
-
worktreePath: input.indexed.worktreePath,
|
|
2324
|
-
taskId: input.indexed.worker.taskId,
|
|
2325
|
-
agentOsId: input.indexed.worker.agentOsId,
|
|
2326
|
-
branch: input.indexed.worker.branch,
|
|
2327
|
-
completionBlocker: input.indexed.worker.completionBlocker,
|
|
2328
|
-
finalResult: input.status.finalResult,
|
|
2329
|
-
changedFiles: input.status.changedFiles,
|
|
2330
|
-
gitAncestry: input.status.gitAncestry,
|
|
2331
|
-
prUrl: input.status.prUrl ?? null,
|
|
2332
|
-
...wrotePatch ? { patchPath } : {}
|
|
2333
|
-
};
|
|
2334
|
-
writeFileSync3(path10.join(dir, "evidence.json"), `${JSON.stringify(record, null, 2)}
|
|
2335
|
-
`);
|
|
2336
|
-
return record;
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
// src/cleanup-guards.ts
|
|
2340
|
-
var SALVAGE_ELIGIBLE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
2341
|
-
"completion_blocked",
|
|
2342
|
-
"dirty_worktree",
|
|
2343
|
-
"pr_or_unmerged_commits",
|
|
2344
|
-
"landing_blocked"
|
|
2345
|
-
]);
|
|
2346
|
-
function maybeSalvageBlockedWorktree(input) {
|
|
2347
|
-
if (!SALVAGE_ELIGIBLE_SKIP_REASONS.has(input.skipReason)) return false;
|
|
2348
|
-
if (isWorkerProcessLive(input.indexed, input.now)) return false;
|
|
2349
|
-
if (hasSalvageEvidence({
|
|
2350
|
-
harnessRoot: input.harnessRoot,
|
|
2351
|
-
runId: input.indexed.runId,
|
|
2352
|
-
workerName: input.indexed.workerName
|
|
2353
|
-
})) {
|
|
2354
|
-
return true;
|
|
2355
|
-
}
|
|
2356
|
-
if (!input.writeSalvageEvidence) return false;
|
|
2357
|
-
writeWorktreeSalvageEvidence({
|
|
2358
|
-
harnessRoot: input.harnessRoot,
|
|
2359
|
-
indexed: input.indexed,
|
|
2360
|
-
skipReason: input.skipReason,
|
|
2361
|
-
status: input.status,
|
|
2362
|
-
now: input.now
|
|
2363
|
-
});
|
|
2364
|
-
return true;
|
|
2365
|
-
}
|
|
2366
|
-
function effectiveWorktreeAgeMs(input) {
|
|
2367
|
-
const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
|
|
2368
|
-
if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
|
|
2369
|
-
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
|
|
2370
|
-
return terminalWorktreesAgeMs;
|
|
2371
|
-
}
|
|
2372
|
-
if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
|
|
2373
|
-
return terminalWorktreesAgeMs;
|
|
2374
|
-
}
|
|
2375
|
-
if (input.liveness && isFinishedWorkerStatus(resolveWorktreeGuardStatus(indexed, input.liveness)) && !isWorkerProcessLive(indexed)) {
|
|
2376
|
-
return terminalWorktreesAgeMs;
|
|
2377
|
-
}
|
|
2378
|
-
return worktreesAgeMs;
|
|
2379
|
-
}
|
|
2380
|
-
function resolveHarnessRoot2(input, indexed) {
|
|
2381
|
-
if (input.harnessRoot?.trim()) return path11.resolve(input.harnessRoot);
|
|
2382
|
-
const workerDir = indexed.worker.workerDir;
|
|
2383
|
-
if (!workerDir) return null;
|
|
2384
|
-
const runsMarker = `${path11.sep}runs${path11.sep}`;
|
|
2385
|
-
const idx = workerDir.indexOf(runsMarker);
|
|
2386
|
-
if (idx < 0) return null;
|
|
2387
|
-
return workerDir.slice(0, idx + runsMarker.length - 1);
|
|
2388
|
-
}
|
|
2389
|
-
function skipWorktreeRemoval(input) {
|
|
2390
|
-
const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
|
|
2391
|
-
const now = input.now ?? Date.now();
|
|
2392
|
-
if (!indexed) {
|
|
2393
|
-
if (!includeOrphans) return "orphan_without_flag";
|
|
2394
|
-
return orphanSafety ?? null;
|
|
2395
|
-
}
|
|
2396
|
-
const ageThresholdMs = effectiveWorktreeAgeMs(input);
|
|
2397
|
-
if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
|
|
2398
|
-
if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
|
|
2399
|
-
if (isWorkerProcessLive(indexed, now)) return "active_worker";
|
|
2400
|
-
const status = resolveWorktreeGuardStatus(indexed, input.liveness);
|
|
2401
|
-
const salvageHarnessRoot = resolveHarnessRoot2(input, indexed);
|
|
2402
|
-
const salvageOrBlock = (skipReason) => {
|
|
2403
|
-
if (salvageHarnessRoot && maybeSalvageBlockedWorktree({
|
|
2404
|
-
indexed,
|
|
2405
|
-
harnessRoot: salvageHarnessRoot,
|
|
2406
|
-
skipReason,
|
|
2407
|
-
status,
|
|
2408
|
-
now,
|
|
2409
|
-
writeSalvageEvidence: input.writeSalvageEvidence === true
|
|
2410
|
-
})) {
|
|
2411
|
-
return null;
|
|
2412
|
-
}
|
|
2413
|
-
return skipReason;
|
|
2414
|
-
};
|
|
2415
|
-
if (completionBlockerBlocksWorktreeRemoval(indexed, status)) {
|
|
2416
|
-
const blocked = salvageOrBlock("completion_blocked");
|
|
2417
|
-
if (blocked) return blocked;
|
|
2418
|
-
}
|
|
2419
|
-
if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
|
|
2420
|
-
const blocked = salvageOrBlock("dirty_worktree");
|
|
2421
|
-
if (blocked) return blocked;
|
|
2422
|
-
}
|
|
2423
|
-
const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
|
|
2424
|
-
if (ahead !== null && ahead !== void 0 && ahead > 0) {
|
|
2425
|
-
const blocked = salvageOrBlock("pr_or_unmerged_commits");
|
|
2426
|
-
if (blocked) return blocked;
|
|
2427
|
-
}
|
|
2428
|
-
if (runBlocksWorktreeRemoval(indexed, input.liveness, now)) return "run_still_active";
|
|
2429
|
-
if (!isFinishedWorkerStatus(status)) return "run_still_active";
|
|
2430
|
-
if (isPrOrUnmergedWork(status)) {
|
|
2431
|
-
const blocked = salvageOrBlock("pr_or_unmerged_commits");
|
|
2432
|
-
if (blocked) return blocked;
|
|
2433
|
-
}
|
|
2434
|
-
if (materialWorktreeChanges(status.changedFiles).length > 0) {
|
|
2435
|
-
const blocked = salvageOrBlock("dirty_worktree");
|
|
2436
|
-
if (blocked) return blocked;
|
|
2437
|
-
}
|
|
2438
|
-
const landing = assessWorkerLanding({
|
|
2439
|
-
finalResult: status.finalResult,
|
|
2440
|
-
changedFiles: status.changedFiles,
|
|
2441
|
-
gitAncestry: status.gitAncestry,
|
|
2442
|
-
prUrl: prUrlFromFinalResult(status.finalResult)
|
|
2443
|
-
});
|
|
2444
|
-
if (landing.blocked) {
|
|
2445
|
-
const blocked = salvageOrBlock("landing_blocked");
|
|
2446
|
-
if (blocked) return blocked;
|
|
2447
|
-
}
|
|
2448
|
-
if (worktreeRemovalGuard && input.worktreePath) {
|
|
2449
|
-
const overlay = worktreeRemovalGuard({
|
|
2450
|
-
worktreePath: input.worktreePath,
|
|
2451
|
-
indexed: Boolean(indexed),
|
|
2452
|
-
runId: indexed?.runId,
|
|
2453
|
-
worker: indexed?.workerName
|
|
2454
|
-
});
|
|
2455
|
-
if (overlay) {
|
|
2456
|
-
return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
return null;
|
|
2460
|
-
}
|
|
2461
|
-
function skipDependencyCacheRemoval(input) {
|
|
2462
|
-
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
2463
|
-
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
2464
|
-
if (activeWorktreePaths.has(path11.resolve(worktreePath))) return "active_worker";
|
|
2465
|
-
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
2466
|
-
if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
|
|
2467
|
-
return "dirty_worktree";
|
|
2468
|
-
}
|
|
2469
|
-
return null;
|
|
2470
|
-
}
|
|
2471
|
-
function skipBuildCacheRemoval(input) {
|
|
2472
|
-
return skipDependencyCacheRemoval(input);
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
// src/cleanup-types.ts
|
|
2476
|
-
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
2477
|
-
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2478
|
-
var DEFAULT_TERMINAL_WORKTREES_AGE_MS = DEFAULT_NODE_MODULES_AGE_MS;
|
|
2479
|
-
var DEFAULT_RUN_DIRECTORIES_AGE_MS = 60 * 60 * 1e3;
|
|
2480
|
-
var DEFAULT_MAX_ACTIONS_PER_SWEEP = 120;
|
|
2481
|
-
var MAX_PRESERVED_LIVE_PATH_SAMPLES = 24;
|
|
2482
|
-
|
|
2483
|
-
// src/cleanup-evidence.ts
|
|
2484
|
-
var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
2485
|
-
"active_worker",
|
|
2486
|
-
"run_still_active",
|
|
2487
|
-
"completion_blocked",
|
|
2488
|
-
"landing_blocked"
|
|
2489
|
-
]);
|
|
2490
|
-
function collectPreservedLivePaths(actions, skips) {
|
|
2491
|
-
const out = [];
|
|
2492
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2493
|
-
const push = (path26, reason, detail) => {
|
|
2494
|
-
const key = `${path26}\0${reason}`;
|
|
2495
|
-
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
2496
|
-
seen.add(key);
|
|
2497
|
-
out.push({ path: path26, reason, ...detail ? { detail } : {} });
|
|
2498
|
-
};
|
|
2499
|
-
for (const skip of skips) {
|
|
2500
|
-
if (!LIVE_SKIP_REASONS.has(skip.reason)) continue;
|
|
2501
|
-
push(skip.path, skip.reason, skip.detail);
|
|
2502
|
-
}
|
|
2503
|
-
for (const action of actions) {
|
|
2504
|
-
if (!action.skipped || !action.skipReason) continue;
|
|
2505
|
-
if (!LIVE_SKIP_REASONS.has(action.skipReason)) continue;
|
|
2506
|
-
push(action.path, action.skipReason);
|
|
2507
|
-
}
|
|
2508
|
-
return out;
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
// src/cleanup-run-directory.ts
|
|
2512
|
-
import { existsSync as existsSync11, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
|
|
2513
|
-
import path13 from "node:path";
|
|
2514
|
-
|
|
2515
|
-
// src/cleanup-active-worktrees.ts
|
|
2516
|
-
init_run_store();
|
|
2517
|
-
init_paths();
|
|
2518
|
-
import path12 from "node:path";
|
|
2519
|
-
init_util();
|
|
2520
|
-
function isActiveHarnessWorker2(worker, now) {
|
|
2521
|
-
return isHarnessWorkerHarnessLive(worker, now);
|
|
2522
|
-
}
|
|
2523
|
-
function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
|
|
2524
|
-
const activeWorktreePaths = /* @__PURE__ */ new Set();
|
|
2525
|
-
const liveRunKeys = /* @__PURE__ */ new Set();
|
|
2526
|
-
for (const harnessRoot of harnessRoots) {
|
|
2527
|
-
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
2528
|
-
let runHasLive = false;
|
|
2529
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
2530
|
-
const worker = readJson(
|
|
2531
|
-
path12.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
2532
|
-
void 0
|
|
2533
|
-
);
|
|
2534
|
-
if (!worker?.worktreePath) continue;
|
|
2535
|
-
const worktreePath = path12.resolve(worker.worktreePath);
|
|
2536
|
-
if (!isActiveHarnessWorker2(worker, now)) continue;
|
|
2537
|
-
runHasLive = true;
|
|
2538
|
-
activeWorktreePaths.add(worktreePath);
|
|
2539
|
-
}
|
|
2540
|
-
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
2541
|
-
}
|
|
2542
|
-
for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
|
|
2543
|
-
liveRunKeys.add(key);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
return { activeWorktreePaths, liveRunKeys };
|
|
2547
|
-
}
|
|
2548
|
-
function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
|
|
2549
|
-
if (!runId) return false;
|
|
2550
|
-
return liveRunKeys.has(`${harnessRoot}\0${runId}`);
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
// src/cleanup-run-directory.ts
|
|
2554
|
-
init_util();
|
|
2555
|
-
function pathAgeMs(target, now) {
|
|
2556
|
-
try {
|
|
2557
|
-
const mtime = statSync4(target).mtimeMs;
|
|
2558
|
-
return Math.max(0, now - mtime);
|
|
2559
|
-
} catch {
|
|
2560
|
-
return 0;
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
function loadRunStatus(harnessRoot, runId) {
|
|
2564
|
-
const runPath = path13.join(harnessRoot, "runs", runId, "run.json");
|
|
2565
|
-
if (!existsSync11(runPath)) return null;
|
|
2566
|
-
return readJson(runPath, null);
|
|
2567
|
-
}
|
|
2568
|
-
function runDirectoryIsEmpty(runPath) {
|
|
2569
|
-
try {
|
|
2570
|
-
const entries = readdirSync5(runPath);
|
|
2571
|
-
return entries.length === 0;
|
|
2572
|
-
} catch {
|
|
2573
|
-
return false;
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
function skipRunDirectoryRemoval(input) {
|
|
2577
|
-
const { harnessRoot, runId, runPath, ageMs, runDirectoriesAgeMs, activeGuards } = input;
|
|
2578
|
-
if (isWorktreeOnLiveRun(runPath, harnessRoot, runId, activeGuards.liveRunKeys)) {
|
|
2579
|
-
return "run_still_active";
|
|
2580
|
-
}
|
|
2581
|
-
if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
|
|
2582
|
-
const run = loadRunStatus(harnessRoot, runId);
|
|
2583
|
-
if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
|
|
2584
|
-
if (!deriveTerminalRunStatus(run)) return "run_still_active";
|
|
2585
|
-
}
|
|
2586
|
-
if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
|
|
2587
|
-
return null;
|
|
2588
|
-
}
|
|
2589
|
-
function scanStaleRunDirectoryCandidates(opts) {
|
|
2590
|
-
if (!existsSync11(opts.worktreesDir)) return [];
|
|
2591
|
-
const candidates = [];
|
|
2592
|
-
let entries;
|
|
2593
|
-
try {
|
|
2594
|
-
entries = readdirSync5(opts.worktreesDir, { withFileTypes: true });
|
|
2595
|
-
} catch {
|
|
2596
|
-
return [];
|
|
2597
|
-
}
|
|
2598
|
-
for (const runEntry of entries) {
|
|
2599
|
-
if (!runEntry.isDirectory()) continue;
|
|
2600
|
-
const runId = runEntry.name;
|
|
2601
|
-
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
2602
|
-
const runPath = path13.join(opts.worktreesDir, runId);
|
|
2603
|
-
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
2604
|
-
candidates.push({
|
|
2605
|
-
kind: "remove_run_directory",
|
|
2606
|
-
path: runPath,
|
|
2607
|
-
bytes: null,
|
|
2608
|
-
harnessRoot: opts.harnessRoot,
|
|
2609
|
-
runId,
|
|
2610
|
-
ageMs: pathAgeMs(runPath, opts.now)
|
|
2611
|
-
});
|
|
2612
|
-
}
|
|
2613
|
-
return candidates;
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
// src/cleanup-execute.ts
|
|
2617
|
-
init_git();
|
|
2618
|
-
import { existsSync as existsSync14, rmSync as rmSync2 } from "node:fs";
|
|
2619
|
-
|
|
2620
|
-
// src/cleanup-dir-size.ts
|
|
2621
|
-
import { execFileSync } from "node:child_process";
|
|
2622
|
-
import { existsSync as existsSync12, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
|
|
2623
|
-
import path14 from "node:path";
|
|
2624
|
-
var DEFAULT_DU_TIMEOUT_MS = 2500;
|
|
2625
|
-
function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
|
|
2626
|
-
if (!existsSync12(root)) return 0;
|
|
2627
|
-
try {
|
|
2628
|
-
const out = execFileSync("du", ["-sb", root], {
|
|
2629
|
-
encoding: "utf8",
|
|
2630
|
-
timeout: timeoutMs,
|
|
2631
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2632
|
-
});
|
|
2633
|
-
const first = out.trim().split(/\s+/)[0];
|
|
2634
|
-
const bytes = Number(first);
|
|
2635
|
-
return Number.isFinite(bytes) && bytes >= 0 ? bytes : null;
|
|
2636
|
-
} catch {
|
|
2637
|
-
return null;
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
2641
|
-
if (!existsSync12(root)) return 0;
|
|
2642
|
-
const duBytes = directorySizeBytesDu(root);
|
|
2643
|
-
if (duBytes !== null) return duBytes;
|
|
2644
|
-
let total = 0;
|
|
2645
|
-
let seen = 0;
|
|
2646
|
-
const stack = [root];
|
|
2647
|
-
while (stack.length > 0) {
|
|
2648
|
-
const current = stack.pop();
|
|
2649
|
-
let entries;
|
|
2650
|
-
try {
|
|
2651
|
-
entries = readdirSync6(current);
|
|
2652
|
-
} catch {
|
|
2653
|
-
continue;
|
|
2654
|
-
}
|
|
2655
|
-
for (const name of entries) {
|
|
2656
|
-
if (seen++ > maxEntries) return null;
|
|
2657
|
-
const full = path14.join(current, name);
|
|
2658
|
-
let st;
|
|
2659
|
-
try {
|
|
2660
|
-
st = statSync5(full);
|
|
2661
|
-
} catch {
|
|
2662
|
-
continue;
|
|
2663
|
-
}
|
|
2664
|
-
if (st.isDirectory()) stack.push(full);
|
|
2665
|
-
else total += st.size;
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
return total;
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
// src/cleanup-remove-path.ts
|
|
2672
|
-
import { existsSync as existsSync13, rmSync } from "node:fs";
|
|
2673
|
-
init_paths();
|
|
2674
|
-
|
|
2675
|
-
// src/cleanup-path-ownership.ts
|
|
2676
|
-
import { lstatSync, readdirSync as readdirSync7 } from "node:fs";
|
|
2677
|
-
function readPathOwnership(targetPath) {
|
|
2678
|
-
try {
|
|
2679
|
-
const st = lstatSync(targetPath);
|
|
2680
|
-
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
2681
|
-
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
2682
|
-
const foreign = effectiveUid !== null && (st.uid !== effectiveUid || effectiveGid !== null && st.gid !== effectiveGid);
|
|
2683
|
-
return { uid: st.uid, gid: st.gid, foreign };
|
|
2684
|
-
} catch {
|
|
2685
|
-
return null;
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
2689
|
-
const root = readPathOwnership(targetPath);
|
|
2690
|
-
if (!root) return false;
|
|
2691
|
-
if (root.foreign) return true;
|
|
2692
|
-
try {
|
|
2693
|
-
const names = readdirSync7(targetPath);
|
|
2694
|
-
let checked = 0;
|
|
2695
|
-
for (const name of names) {
|
|
2696
|
-
if (checked >= maxEntries) break;
|
|
2697
|
-
const child = `${targetPath.replace(/\/$/, "")}/${name}`;
|
|
2698
|
-
const info = readPathOwnership(child);
|
|
2699
|
-
checked += 1;
|
|
2700
|
-
if (info?.foreign) return true;
|
|
2701
|
-
}
|
|
2702
|
-
} catch {
|
|
2703
|
-
}
|
|
2704
|
-
return false;
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
// src/cleanup-privileged-remove.ts
|
|
2708
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2709
|
-
import path16 from "node:path";
|
|
2710
|
-
|
|
2711
|
-
// src/cleanup-harness-path-validate.ts
|
|
2712
|
-
import path15 from "node:path";
|
|
2713
|
-
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
2714
|
-
const resolved = path15.resolve(targetPath);
|
|
2715
|
-
const suffix = `${path15.sep}${cacheDirName}`;
|
|
2716
|
-
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
2717
|
-
if (!cachePath) return "path_outside_harness";
|
|
2718
|
-
const rel = path15.relative(worktreesDir, cachePath);
|
|
2719
|
-
if (rel.startsWith("..") || path15.isAbsolute(rel)) return "path_outside_harness";
|
|
2720
|
-
const parts = rel.split(path15.sep);
|
|
2721
|
-
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
2722
|
-
if (!resolved.startsWith(path15.resolve(harnessRoot))) return "path_outside_harness";
|
|
2723
|
-
return null;
|
|
2724
|
-
}
|
|
2725
|
-
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
2726
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, "node_modules");
|
|
2727
|
-
}
|
|
2728
|
-
function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
2729
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
2730
|
-
}
|
|
2731
|
-
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
2732
|
-
const resolved = path15.resolve(targetPath);
|
|
2733
|
-
const relToWt = path15.relative(worktreesDir, resolved);
|
|
2734
|
-
if (relToWt.startsWith("..") || path15.isAbsolute(relToWt)) return "path_outside_harness";
|
|
2735
|
-
const parts = relToWt.split(path15.sep);
|
|
2736
|
-
if (parts.length < 3) return "path_outside_harness";
|
|
2737
|
-
if (!resolved.startsWith(path15.resolve(harnessRoot))) return "path_outside_harness";
|
|
2738
|
-
return null;
|
|
2739
|
-
}
|
|
2740
|
-
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
2741
|
-
const resolved = path15.resolve(targetPath);
|
|
2742
|
-
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
// src/cleanup-privileged-remove.ts
|
|
2746
|
-
function resolvePrivilegedCleanupMode() {
|
|
2747
|
-
const raw = (process.env.KYNVER_CLEANUP_PRIVILEGED ?? "auto").trim().toLowerCase();
|
|
2748
|
-
if (raw === "0" || raw === "false" || raw === "off" || raw === "no") return "off";
|
|
2749
|
-
if (raw === "1" || raw === "true" || raw === "force" || raw === "yes") return "force";
|
|
2750
|
-
return "auto";
|
|
2751
|
-
}
|
|
2752
|
-
function runSudoNonInteractive(argv) {
|
|
2753
|
-
const res = spawnSync2("sudo", ["-n", ...argv], {
|
|
2754
|
-
encoding: "utf8",
|
|
2755
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2756
|
-
});
|
|
2757
|
-
return {
|
|
2758
|
-
ok: res.status === 0,
|
|
2759
|
-
stderr: `${res.stderr ?? ""}${res.stdout ?? ""}`.trim()
|
|
2760
|
-
};
|
|
2761
|
-
}
|
|
2762
|
-
function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir) {
|
|
2763
|
-
if (!isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir)) {
|
|
2764
|
-
return { ok: false, error: "path is not an allowed harness generated cache" };
|
|
2765
|
-
}
|
|
2766
|
-
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
2767
|
-
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
2768
|
-
if (effectiveUid === null || effectiveGid === null) {
|
|
2769
|
-
return { ok: false, error: "privileged reclaim requires POSIX uid/gid" };
|
|
2770
|
-
}
|
|
2771
|
-
const chown = runSudoNonInteractive([
|
|
2772
|
-
"chown",
|
|
2773
|
-
"-R",
|
|
2774
|
-
`${effectiveUid}:${effectiveGid}`,
|
|
2775
|
-
path16.resolve(targetPath)
|
|
2776
|
-
]);
|
|
2777
|
-
if (chown.ok) {
|
|
2778
|
-
return { ok: true, method: "chown_then_rm" };
|
|
2779
|
-
}
|
|
2780
|
-
const rm = runSudoNonInteractive(["rm", "-rf", path16.resolve(targetPath)]);
|
|
2781
|
-
if (rm.ok) {
|
|
2782
|
-
return { ok: true, method: "sudo_rm" };
|
|
2783
|
-
}
|
|
2784
|
-
const detail = chown.stderr || rm.stderr || "sudo -n failed (password required or not permitted)";
|
|
2785
|
-
return {
|
|
2786
|
-
ok: false,
|
|
2787
|
-
error: detail
|
|
2788
|
-
};
|
|
2789
|
-
}
|
|
2790
|
-
var HARNESS_ROOT_OWNED_CACHE_RUNBOOK = "Root-owned harness caches require operator reclaim: configure passwordless sudo for chown/rm under ~/.kynver/harness/worktrees, or run `node scripts/reclaim-harness-root-owned-cache.mjs --execute` as the harness owner with sudo available. See docs/runbooks/harness-root-owned-cache-reclaim.md.";
|
|
2791
|
-
|
|
2792
|
-
// src/cleanup-remove-path.ts
|
|
2793
|
-
function permissionFailure(error) {
|
|
2794
|
-
const code = error?.code;
|
|
2795
|
-
return code === "EACCES" || code === "EPERM";
|
|
2796
|
-
}
|
|
2797
|
-
function removeHarnessGeneratedPath(candidate, execute, deps = {}) {
|
|
2798
|
-
if (!existsSync13(candidate.path)) {
|
|
2799
|
-
return { executed: false, skipped: true, skipReason: "missing_worktree" };
|
|
2800
|
-
}
|
|
2801
|
-
if (!execute) {
|
|
2802
|
-
return { executed: false, skipped: true, skipReason: "dry_run" };
|
|
2803
|
-
}
|
|
2804
|
-
const harnessRoot = candidate.harnessRoot;
|
|
2805
|
-
const worktreesDir = harnessRoot ? harnessWorktreesDir(harnessRoot) : null;
|
|
2806
|
-
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2807
|
-
const bytesReported = bytesBefore ?? void 0;
|
|
2808
|
-
const removePath = deps.removePath ?? rmSync;
|
|
2809
|
-
const hasForeignOwnedEntry = deps.hasForeignOwnedEntry ?? pathHasForeignOwnedEntry;
|
|
2810
|
-
try {
|
|
2811
|
-
removePath(candidate.path, { recursive: true, force: true });
|
|
2812
|
-
return { executed: true, skipped: false, bytes: bytesReported };
|
|
2813
|
-
} catch (error) {
|
|
2814
|
-
if (!permissionFailure(error) || !harnessRoot || !worktreesDir) {
|
|
2815
|
-
return {
|
|
2816
|
-
executed: false,
|
|
2817
|
-
skipped: true,
|
|
2818
|
-
skipReason: "remove_failed",
|
|
2819
|
-
error: error.message
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
const foreign = hasForeignOwnedEntry(candidate.path);
|
|
2823
|
-
const mode = resolvePrivilegedCleanupMode();
|
|
2824
|
-
const shouldTryPrivileged = mode === "force" || mode === "auto" && foreign;
|
|
2825
|
-
if (!shouldTryPrivileged) {
|
|
2826
|
-
return foreign ? {
|
|
2827
|
-
executed: false,
|
|
2828
|
-
skipped: true,
|
|
2829
|
-
skipReason: "foreign_owner",
|
|
2830
|
-
error: `${error.message}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
2831
|
-
} : {
|
|
2832
|
-
executed: false,
|
|
2833
|
-
skipped: true,
|
|
2834
|
-
skipReason: "remove_failed",
|
|
2835
|
-
error: error.message
|
|
2836
|
-
};
|
|
2837
|
-
}
|
|
2838
|
-
const reclaim = tryPrivilegedReclaimHarnessCache(candidate.path, harnessRoot, worktreesDir);
|
|
2839
|
-
if (reclaim.ok && reclaim.method === "sudo_rm") {
|
|
2840
|
-
return {
|
|
2841
|
-
executed: true,
|
|
2842
|
-
skipped: false,
|
|
2843
|
-
bytes: bytesReported,
|
|
2844
|
-
privilegedReclaim: true
|
|
2845
|
-
};
|
|
2846
|
-
}
|
|
2847
|
-
if (reclaim.ok) {
|
|
2848
|
-
try {
|
|
2849
|
-
removePath(candidate.path, { recursive: true, force: true });
|
|
2850
|
-
return {
|
|
2851
|
-
executed: true,
|
|
2852
|
-
skipped: false,
|
|
2853
|
-
bytes: bytesReported,
|
|
2854
|
-
privilegedReclaim: true
|
|
2855
|
-
};
|
|
2856
|
-
} catch (retryError) {
|
|
2857
|
-
return {
|
|
2858
|
-
executed: false,
|
|
2859
|
-
skipped: true,
|
|
2860
|
-
skipReason: "foreign_owner",
|
|
2861
|
-
error: `${retryError.message}; privileged chown succeeded but rm still failed`
|
|
2862
|
-
};
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
return {
|
|
2866
|
-
executed: false,
|
|
2867
|
-
skipped: true,
|
|
2868
|
-
skipReason: "foreign_owner",
|
|
2869
|
-
error: `${error.message}; privileged reclaim failed: ${reclaim.error}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
2870
|
-
};
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
|
|
2874
|
-
// src/cleanup-execute.ts
|
|
2875
|
-
function skipRunMetadataRemoval(candidate) {
|
|
2876
|
-
const harnessRoot = candidate.harnessRoot;
|
|
2877
|
-
if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
|
|
2878
|
-
return {
|
|
2879
|
-
...candidate,
|
|
2880
|
-
executed: false,
|
|
2881
|
-
skipped: true,
|
|
2882
|
-
skipReason: "run_metadata_protected"
|
|
2883
|
-
};
|
|
2884
|
-
}
|
|
2885
|
-
function removeDependencyCache(candidate, execute) {
|
|
2886
|
-
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
2887
|
-
if (metadataSkip) return metadataSkip;
|
|
2888
|
-
const outcome = removeHarnessGeneratedPath(candidate, execute);
|
|
2889
|
-
return {
|
|
2890
|
-
...candidate,
|
|
2891
|
-
bytes: outcome.bytes ?? candidate.bytes,
|
|
2892
|
-
executed: outcome.executed,
|
|
2893
|
-
skipped: outcome.skipped,
|
|
2894
|
-
skipReason: outcome.skipReason,
|
|
2895
|
-
error: outcome.error
|
|
2896
|
-
};
|
|
2897
|
-
}
|
|
2898
|
-
function removeNodeModules(candidate, execute) {
|
|
2899
|
-
return removeDependencyCache(candidate, execute);
|
|
2900
|
-
}
|
|
2901
|
-
function removeNextCache(candidate, execute) {
|
|
2902
|
-
return removeDependencyCache(candidate, execute);
|
|
2903
|
-
}
|
|
2904
|
-
function removeBuildCache(candidate, execute) {
|
|
2905
|
-
return removeDependencyCache(candidate, execute);
|
|
2906
|
-
}
|
|
2907
|
-
function removeRunDirectory(candidate, execute) {
|
|
2908
|
-
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
2909
|
-
if (metadataSkip) return metadataSkip;
|
|
2910
|
-
if (!existsSync14(candidate.path)) {
|
|
2911
|
-
return {
|
|
2912
|
-
...candidate,
|
|
2913
|
-
executed: false,
|
|
2914
|
-
skipped: true,
|
|
2915
|
-
skipReason: "missing_worktree"
|
|
2916
|
-
};
|
|
2917
|
-
}
|
|
2918
|
-
if (!execute) {
|
|
2919
|
-
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2920
|
-
}
|
|
2921
|
-
try {
|
|
2922
|
-
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2923
|
-
rmSync2(candidate.path, { recursive: true, force: true });
|
|
2924
|
-
return {
|
|
2925
|
-
...candidate,
|
|
2926
|
-
bytes: bytesBefore,
|
|
2927
|
-
executed: true,
|
|
2928
|
-
skipped: false
|
|
2929
|
-
};
|
|
2930
|
-
} catch (error) {
|
|
2931
|
-
return {
|
|
2932
|
-
...candidate,
|
|
2933
|
-
executed: false,
|
|
2934
|
-
skipped: true,
|
|
2935
|
-
skipReason: "remove_failed",
|
|
2936
|
-
error: error.message
|
|
2937
|
-
};
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
function removeWorktree(candidate, execute) {
|
|
2941
|
-
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
2942
|
-
if (metadataSkip) return metadataSkip;
|
|
2943
|
-
if (!existsSync14(candidate.path)) {
|
|
2944
|
-
return {
|
|
2945
|
-
...candidate,
|
|
2946
|
-
executed: false,
|
|
2947
|
-
skipped: true,
|
|
2948
|
-
skipReason: "missing_worktree"
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
if (!execute) {
|
|
2952
|
-
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2953
|
-
}
|
|
2954
|
-
const repo = candidate.repo;
|
|
2955
|
-
try {
|
|
2956
|
-
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2957
|
-
if (repo) {
|
|
2958
|
-
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
2959
|
-
}
|
|
2960
|
-
if (existsSync14(candidate.path)) {
|
|
2961
|
-
rmSync2(candidate.path, { recursive: true, force: true });
|
|
2962
|
-
}
|
|
2963
|
-
return {
|
|
2964
|
-
...candidate,
|
|
2965
|
-
bytes: bytesBefore,
|
|
2966
|
-
executed: true,
|
|
2967
|
-
skipped: false
|
|
2968
|
-
};
|
|
2969
|
-
} catch (error) {
|
|
2970
|
-
return {
|
|
2971
|
-
...candidate,
|
|
2972
|
-
executed: false,
|
|
2973
|
-
skipped: true,
|
|
2974
|
-
skipReason: "remove_failed",
|
|
2975
|
-
error: error.message
|
|
2976
|
-
};
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
// src/cleanup-scan.ts
|
|
2981
|
-
import { existsSync as existsSync15, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
2982
|
-
import path17 from "node:path";
|
|
2983
|
-
function pathAgeMs2(target, now) {
|
|
2984
|
-
try {
|
|
2985
|
-
const mtime = statSync6(target).mtimeMs;
|
|
2986
|
-
return Math.max(0, now - mtime);
|
|
2987
|
-
} catch {
|
|
2988
|
-
return 0;
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
function isPathInside(child, parent) {
|
|
2992
|
-
const rel = path17.relative(parent, child);
|
|
2993
|
-
return rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel);
|
|
2994
|
-
}
|
|
2995
|
-
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
2996
|
-
const out = [];
|
|
2997
|
-
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
2998
|
-
if (rel === ".next") continue;
|
|
2999
|
-
const target = path17.join(worktreePath, rel);
|
|
3000
|
-
if (!existsSync15(target)) continue;
|
|
3001
|
-
const resolved = path17.resolve(target);
|
|
3002
|
-
if (seen.has(resolved)) continue;
|
|
3003
|
-
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
3004
|
-
seen.add(resolved);
|
|
3005
|
-
out.push({
|
|
3006
|
-
kind: "remove_build_cache",
|
|
3007
|
-
path: resolved,
|
|
3008
|
-
bytes: null,
|
|
3009
|
-
runId: meta.runId,
|
|
3010
|
-
worker: meta.worker,
|
|
3011
|
-
repo: meta.repo,
|
|
3012
|
-
ageMs: pathAgeMs2(resolved, opts.now)
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
return out;
|
|
3016
|
-
}
|
|
3017
|
-
function scanBuildCacheCandidates(opts) {
|
|
3018
|
-
const candidates = [];
|
|
3019
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3020
|
-
for (const entry of opts.index.values()) {
|
|
3021
|
-
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
3022
|
-
candidates.push(
|
|
3023
|
-
...collectBuildCacheForWorktree(entry.worktreePath, opts, seen, {
|
|
3024
|
-
runId: entry.runId,
|
|
3025
|
-
worker: entry.workerName,
|
|
3026
|
-
repo: entry.run.repo
|
|
3027
|
-
})
|
|
3028
|
-
);
|
|
3029
|
-
}
|
|
3030
|
-
if (!opts.includeOrphans || !existsSync15(opts.worktreesDir)) return candidates;
|
|
3031
|
-
for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
|
|
3032
|
-
if (!runEntry.isDirectory()) continue;
|
|
3033
|
-
const runPath = path17.join(opts.worktreesDir, runEntry.name);
|
|
3034
|
-
for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
|
|
3035
|
-
if (!workerEntry.isDirectory()) continue;
|
|
3036
|
-
const worktreePath = path17.join(runPath, workerEntry.name);
|
|
3037
|
-
candidates.push(
|
|
3038
|
-
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
3039
|
-
runId: runEntry.name,
|
|
3040
|
-
worker: workerEntry.name
|
|
3041
|
-
})
|
|
3042
|
-
);
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
return candidates;
|
|
3046
|
-
}
|
|
3047
|
-
function scanWorktreeCandidates(opts) {
|
|
3048
|
-
const indexedEnabled = opts.worktreesAgeMs > 0 || opts.includeOrphans;
|
|
3049
|
-
const orphanEnabled = opts.includeOrphans;
|
|
3050
|
-
if (!indexedEnabled && !orphanEnabled) return [];
|
|
3051
|
-
const candidates = [];
|
|
3052
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3053
|
-
if (indexedEnabled) {
|
|
3054
|
-
for (const entry of opts.index.values()) {
|
|
3055
|
-
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
3056
|
-
const resolved = entry.worktreePath;
|
|
3057
|
-
if (!existsSync15(resolved)) continue;
|
|
3058
|
-
if (seen.has(resolved)) continue;
|
|
3059
|
-
seen.add(resolved);
|
|
3060
|
-
candidates.push({
|
|
3061
|
-
kind: "remove_worktree",
|
|
3062
|
-
path: resolved,
|
|
3063
|
-
bytes: null,
|
|
3064
|
-
runId: entry.runId,
|
|
3065
|
-
worker: entry.workerName,
|
|
3066
|
-
repo: entry.run.repo,
|
|
3067
|
-
ageMs: pathAgeMs2(resolved, opts.now)
|
|
3068
|
-
});
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
if (!orphanEnabled || !existsSync15(opts.worktreesDir)) return candidates;
|
|
3072
|
-
const indexedPaths = /* @__PURE__ */ new Set();
|
|
3073
|
-
for (const entry of opts.index.values()) {
|
|
3074
|
-
indexedPaths.add(path17.resolve(entry.worktreePath));
|
|
3075
|
-
}
|
|
3076
|
-
for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
|
|
3077
|
-
if (!runEntry.isDirectory()) continue;
|
|
3078
|
-
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
3079
|
-
const runPath = path17.join(opts.worktreesDir, runEntry.name);
|
|
3080
|
-
let workerEntries;
|
|
3081
|
-
try {
|
|
3082
|
-
workerEntries = readdirSync8(runPath, { withFileTypes: true });
|
|
3083
|
-
} catch {
|
|
3084
|
-
continue;
|
|
3085
|
-
}
|
|
3086
|
-
for (const workerEntry of workerEntries) {
|
|
3087
|
-
if (!workerEntry.isDirectory()) continue;
|
|
3088
|
-
const worktreePath = path17.resolve(path17.join(runPath, workerEntry.name));
|
|
3089
|
-
if (seen.has(worktreePath)) continue;
|
|
3090
|
-
if (indexedPaths.has(worktreePath)) continue;
|
|
3091
|
-
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
3092
|
-
seen.add(worktreePath);
|
|
3093
|
-
candidates.push({
|
|
3094
|
-
kind: "remove_worktree",
|
|
3095
|
-
path: worktreePath,
|
|
3096
|
-
bytes: null,
|
|
3097
|
-
runId: runEntry.name,
|
|
3098
|
-
worker: workerEntry.name,
|
|
3099
|
-
ageMs: pathAgeMs2(worktreePath, opts.now)
|
|
3100
|
-
});
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
return candidates;
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
|
-
// src/cleanup-dependency-scan.ts
|
|
3107
|
-
import { existsSync as existsSync16, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
3108
|
-
import path18 from "node:path";
|
|
3109
|
-
var DEPENDENCY_CACHE_DIRS = [
|
|
3110
|
-
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
3111
|
-
{ dirName: ".next", kind: "remove_next_cache" }
|
|
3112
|
-
];
|
|
3113
|
-
function pathAgeMs3(target, now) {
|
|
3114
|
-
try {
|
|
3115
|
-
const mtime = statSync7(target).mtimeMs;
|
|
3116
|
-
return Math.max(0, now - mtime);
|
|
3117
|
-
} catch {
|
|
3118
|
-
return 0;
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
function isPathInside2(child, parent) {
|
|
3122
|
-
const rel = path18.relative(parent, child);
|
|
3123
|
-
return rel === "" || !rel.startsWith("..") && !path18.isAbsolute(rel);
|
|
3124
|
-
}
|
|
3125
|
-
function pushCandidate(candidates, seen, opts, targetPath, kind, meta) {
|
|
3126
|
-
if (!existsSync16(targetPath)) return;
|
|
3127
|
-
const resolved = path18.resolve(targetPath);
|
|
3128
|
-
if (seen.has(resolved)) return;
|
|
3129
|
-
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
3130
|
-
seen.add(resolved);
|
|
3131
|
-
candidates.push({
|
|
3132
|
-
kind,
|
|
3133
|
-
path: resolved,
|
|
3134
|
-
bytes: null,
|
|
3135
|
-
harnessRoot: opts.harnessRoot,
|
|
3136
|
-
runId: meta.runId,
|
|
3137
|
-
worker: meta.worker,
|
|
3138
|
-
repo: meta.repo,
|
|
3139
|
-
ageMs: pathAgeMs3(resolved, opts.now)
|
|
3140
|
-
});
|
|
3141
|
-
}
|
|
3142
|
-
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
3143
|
-
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
3144
|
-
pushCandidate(candidates, seen, opts, path18.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
function scanDependencyCacheCandidates(opts) {
|
|
3148
|
-
const candidates = [];
|
|
3149
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3150
|
-
for (const entry of opts.index.values()) {
|
|
3151
|
-
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
3152
|
-
scanWorktreeDependencyCaches(candidates, seen, opts, entry.worktreePath, {
|
|
3153
|
-
runId: entry.runId,
|
|
3154
|
-
worker: entry.workerName,
|
|
3155
|
-
repo: entry.run.repo
|
|
3156
|
-
});
|
|
3157
|
-
}
|
|
3158
|
-
if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
|
|
3159
|
-
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
3160
|
-
if (!runEntry.isDirectory()) continue;
|
|
3161
|
-
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
3162
|
-
const runPath = path18.join(opts.worktreesDir, runEntry.name);
|
|
3163
|
-
let workerEntries;
|
|
3164
|
-
try {
|
|
3165
|
-
workerEntries = readdirSync9(runPath, { withFileTypes: true });
|
|
3166
|
-
} catch {
|
|
3167
|
-
continue;
|
|
3168
|
-
}
|
|
3169
|
-
for (const workerEntry of workerEntries) {
|
|
3170
|
-
if (!workerEntry.isDirectory()) continue;
|
|
3171
|
-
const worktreePath = path18.join(runPath, workerEntry.name);
|
|
3172
|
-
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
3173
|
-
runId: runEntry.name,
|
|
3174
|
-
worker: workerEntry.name
|
|
3175
|
-
});
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
return candidates;
|
|
3179
|
-
}
|
|
3180
|
-
|
|
3181
|
-
// src/cleanup-duplicate-worktrees.ts
|
|
3182
|
-
init_git();
|
|
3183
|
-
import { existsSync as existsSync17, statSync as statSync8 } from "node:fs";
|
|
3184
|
-
import path19 from "node:path";
|
|
3185
|
-
function pathAgeMs4(target, now) {
|
|
3186
|
-
try {
|
|
3187
|
-
const mtime = statSync8(target).mtimeMs;
|
|
3188
|
-
return Math.max(0, now - mtime);
|
|
3189
|
-
} catch {
|
|
3190
|
-
return 0;
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
function parseWorktreePorcelain(output) {
|
|
3194
|
-
const records = [];
|
|
3195
|
-
let current = null;
|
|
3196
|
-
for (const line of output.split("\n")) {
|
|
3197
|
-
if (!line.trim()) continue;
|
|
3198
|
-
const [key, ...rest] = line.split(" ");
|
|
3199
|
-
const value = rest.join(" ");
|
|
3200
|
-
if (key === "worktree") {
|
|
3201
|
-
if (current) records.push(current);
|
|
3202
|
-
current = { path: value };
|
|
3203
|
-
continue;
|
|
3204
|
-
}
|
|
3205
|
-
if (!current) continue;
|
|
3206
|
-
if (key === "branch") current.branch = value;
|
|
3207
|
-
if (key === "HEAD") current.head = value;
|
|
3208
|
-
if (key === "bare") current.bare = true;
|
|
3209
|
-
}
|
|
3210
|
-
if (current) records.push(current);
|
|
3211
|
-
return records;
|
|
3212
|
-
}
|
|
3213
|
-
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
3214
|
-
const rel = path19.relative(path19.resolve(worktreesDir), path19.resolve(worktreePath));
|
|
3215
|
-
return rel !== "" && !rel.startsWith("..") && !path19.isAbsolute(rel);
|
|
3216
|
-
}
|
|
3217
|
-
var MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO = 200;
|
|
3218
|
-
function scanDuplicateWorktreeCandidates(opts) {
|
|
3219
|
-
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return [];
|
|
3220
|
-
const repos = /* @__PURE__ */ new Set();
|
|
3221
|
-
for (const entry of opts.index.values()) {
|
|
3222
|
-
if (entry.run.repo) repos.add(path19.resolve(entry.run.repo));
|
|
3223
|
-
}
|
|
3224
|
-
const indexedPaths = /* @__PURE__ */ new Set();
|
|
3225
|
-
for (const entry of opts.index.values()) {
|
|
3226
|
-
indexedPaths.add(path19.resolve(entry.worktreePath));
|
|
3227
|
-
}
|
|
3228
|
-
const candidates = [];
|
|
3229
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3230
|
-
for (const repoRoot of repos) {
|
|
3231
|
-
let repoCandidateCount = 0;
|
|
3232
|
-
let porcelain;
|
|
3233
|
-
try {
|
|
3234
|
-
porcelain = git(repoRoot, ["worktree", "list", "--porcelain"], { allowFailure: true });
|
|
3235
|
-
} catch {
|
|
3236
|
-
continue;
|
|
3237
|
-
}
|
|
3238
|
-
const worktrees = parseWorktreePorcelain(porcelain);
|
|
3239
|
-
for (const wt of worktrees) {
|
|
3240
|
-
if (repoCandidateCount >= MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO) break;
|
|
3241
|
-
const resolved = path19.resolve(wt.path);
|
|
3242
|
-
if (resolved === path19.resolve(repoRoot)) continue;
|
|
3243
|
-
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
3244
|
-
if (indexedPaths.has(resolved)) continue;
|
|
3245
|
-
if (seen.has(resolved)) continue;
|
|
3246
|
-
if (!existsSync17(resolved)) continue;
|
|
3247
|
-
const rel = path19.relative(opts.worktreesDir, resolved);
|
|
3248
|
-
const parts = rel.split(path19.sep);
|
|
3249
|
-
const runId = parts[0];
|
|
3250
|
-
const worker = parts[1] ?? "unknown";
|
|
3251
|
-
seen.add(resolved);
|
|
3252
|
-
repoCandidateCount += 1;
|
|
3253
|
-
candidates.push({
|
|
3254
|
-
kind: "remove_worktree",
|
|
3255
|
-
path: resolved,
|
|
3256
|
-
bytes: null,
|
|
3257
|
-
runId,
|
|
3258
|
-
worker,
|
|
3259
|
-
repo: repoRoot,
|
|
3260
|
-
ageMs: pathAgeMs4(resolved, opts.now)
|
|
3261
|
-
});
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
return candidates;
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
|
-
// src/cleanup-worktree-index.ts
|
|
3268
|
-
init_run_store();
|
|
3269
|
-
init_util();
|
|
3270
|
-
import path20 from "node:path";
|
|
3271
|
-
function filterWorktreeIndexForRoot(index, harnessRoot) {
|
|
3272
|
-
const resolvedRoot = path20.resolve(harnessRoot);
|
|
3273
|
-
const scoped = /* @__PURE__ */ new Map();
|
|
3274
|
-
for (const [key, entry] of index) {
|
|
3275
|
-
const entryRoot = entry.harnessRoot ? path20.resolve(entry.harnessRoot) : null;
|
|
3276
|
-
if (entryRoot && entryRoot !== resolvedRoot) continue;
|
|
3277
|
-
scoped.set(key, entry);
|
|
3278
|
-
}
|
|
3279
|
-
return scoped;
|
|
3280
|
-
}
|
|
3281
|
-
function buildWorktreeIndexAt(harnessRoot) {
|
|
3282
|
-
const index = /* @__PURE__ */ new Map();
|
|
3283
|
-
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
3284
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
3285
|
-
const workerPath = path20.join(
|
|
3286
|
-
runDirectoryAt(harnessRoot, run.id),
|
|
3287
|
-
"workers",
|
|
3288
|
-
safeSlug(name),
|
|
3289
|
-
"worker.json"
|
|
3290
|
-
);
|
|
3291
|
-
const worker = readJson(workerPath, void 0);
|
|
3292
|
-
if (!worker?.worktreePath) continue;
|
|
3293
|
-
index.set(path20.resolve(worker.worktreePath), {
|
|
3294
|
-
harnessRoot,
|
|
3295
|
-
worktreePath: path20.resolve(worker.worktreePath),
|
|
3296
|
-
runId: run.id,
|
|
3297
|
-
workerName: name,
|
|
3298
|
-
run,
|
|
3299
|
-
worker
|
|
3300
|
-
});
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
return index;
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
// src/cleanup-retention-config.ts
|
|
3307
|
-
function envFlag(name) {
|
|
3308
|
-
const v = process.env[name];
|
|
3309
|
-
return v === "1" || v === "true" || v === "yes";
|
|
3310
|
-
}
|
|
3311
|
-
function envMs(name, fallback) {
|
|
3312
|
-
const raw = process.env[name];
|
|
3313
|
-
if (!raw) return fallback;
|
|
3314
|
-
const n = Number(raw);
|
|
3315
|
-
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
3316
|
-
}
|
|
3317
|
-
function resolveHarnessRetention(options = {}) {
|
|
3318
|
-
const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
|
|
3319
|
-
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
|
|
3320
|
-
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
3321
|
-
const scanDependencyCaches = options.scanDependencyCaches ?? (options.nodeModulesAgeMs !== void 0 || process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS != null);
|
|
3322
|
-
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
3323
|
-
const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
|
|
3324
|
-
const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
|
|
3325
|
-
const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
|
|
3326
|
-
const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
|
|
3327
|
-
const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
3328
|
-
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
3329
|
-
const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
|
|
3330
|
-
const storageCapEnv = envMs("KYNVER_CLEANUP_STORAGE_ENTRY_CAP", 2e3);
|
|
3331
|
-
const storagePerRunEntryCap = options.storagePerRunEntryCap !== void 0 ? options.storagePerRunEntryCap : accountBytes ? storageCapEnv > 0 ? storageCapEnv : null : null;
|
|
3332
|
-
const byteCapEnv = envMs("KYNVER_CLEANUP_BYTE_ENTRY_CAP", 2e3);
|
|
3333
|
-
const byteAccountingEntryCap = options.byteAccountingEntryCap ?? (Number.isFinite(byteCapEnv) && byteCapEnv > 0 ? Math.floor(byteCapEnv) : 2e3);
|
|
3334
|
-
return {
|
|
3335
|
-
execute,
|
|
3336
|
-
finalizeStaleRuns: finalizeStaleRuns2,
|
|
3337
|
-
scanDependencyCaches,
|
|
3338
|
-
nodeModulesAgeMs,
|
|
3339
|
-
worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
|
|
3340
|
-
terminalWorktreesAgeMs: terminalWorktreesAgeMs >= 0 ? terminalWorktreesAgeMs : 0,
|
|
3341
|
-
runDirectoriesAgeMs: runDirectoriesAgeMs >= 0 ? runDirectoriesAgeMs : 0,
|
|
3342
|
-
maxActionsPerSweep: Number.isFinite(maxActionsPerSweep) && maxActionsPerSweep > 0 ? Math.floor(maxActionsPerSweep) : DEFAULT_MAX_ACTIONS_PER_SWEEP,
|
|
3343
|
-
includeOrphans,
|
|
3344
|
-
runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
|
|
3345
|
-
accountBytes,
|
|
3346
|
-
storagePerRunEntryCap,
|
|
3347
|
-
byteAccountingEntryCap
|
|
3348
|
-
};
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
// src/cleanup-orphan-safety.ts
|
|
3352
|
-
init_git();
|
|
3353
|
-
import { existsSync as existsSync18 } from "node:fs";
|
|
3354
|
-
import path21 from "node:path";
|
|
3355
|
-
init_util();
|
|
3356
|
-
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
3357
|
-
function assessOrphanWorktreeSafety(input) {
|
|
3358
|
-
const now = input.now ?? Date.now();
|
|
3359
|
-
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
3360
|
-
if (!existsSync18(input.worktreePath)) return null;
|
|
3361
|
-
if (input.runId && input.workerName) {
|
|
3362
|
-
const workerDir = path21.join(
|
|
3363
|
-
input.harnessRoot,
|
|
3364
|
-
"runs",
|
|
3365
|
-
input.runId,
|
|
3366
|
-
"workers",
|
|
3367
|
-
input.workerName
|
|
3368
|
-
);
|
|
3369
|
-
const worker = readJson(
|
|
3370
|
-
path21.join(workerDir, "worker.json"),
|
|
3371
|
-
void 0
|
|
3372
|
-
);
|
|
3373
|
-
if (worker && heartbeatContentIsFresh(worker, now, heartbeatFreshMs)) {
|
|
3374
|
-
return "active_worker";
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
const gitDir = path21.join(input.worktreePath, ".git");
|
|
3378
|
-
if (!existsSync18(gitDir)) return null;
|
|
3379
|
-
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
3380
|
-
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
3381
|
-
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
3382
|
-
if (materialWorktreeChanges(dirtyLines).length > 0) return "dirty_worktree";
|
|
3383
|
-
const upstreamAhead = gitCapture(input.worktreePath, [
|
|
3384
|
-
"rev-list",
|
|
3385
|
-
"--count",
|
|
3386
|
-
"@{u}..HEAD"
|
|
3387
|
-
]);
|
|
3388
|
-
if (upstreamAhead.status === 0) {
|
|
3389
|
-
const count = Number(upstreamAhead.stdout.trim());
|
|
3390
|
-
if (Number.isFinite(count) && count > 0) return "pr_or_unmerged_commits";
|
|
3391
|
-
}
|
|
3392
|
-
const mainAhead = gitCapture(input.worktreePath, [
|
|
3393
|
-
"rev-list",
|
|
3394
|
-
"--count",
|
|
3395
|
-
"origin/main..HEAD"
|
|
3396
|
-
]);
|
|
3397
|
-
if (mainAhead.status !== 0) {
|
|
3398
|
-
if (upstreamAhead.status !== 0) return "pr_or_unmerged_commits";
|
|
3399
|
-
return null;
|
|
3400
|
-
}
|
|
3401
|
-
const mainCount = Number(mainAhead.stdout.trim());
|
|
3402
|
-
if (Number.isFinite(mainCount) && mainCount > 0) return "pr_or_unmerged_commits";
|
|
3403
|
-
return null;
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
// src/harness-storage-snapshot.ts
|
|
3407
|
-
init_paths();
|
|
3408
|
-
import { existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync9 } from "node:fs";
|
|
3409
|
-
import path22 from "node:path";
|
|
3410
|
-
function harnessStorageSnapshot(opts = {}) {
|
|
3411
|
-
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
3412
|
-
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
3413
|
-
const now = opts.now ?? Date.now();
|
|
3414
|
-
const scannedAt = new Date(now).toISOString();
|
|
3415
|
-
if (!existsSync19(worktreesDir)) {
|
|
3416
|
-
return {
|
|
3417
|
-
harnessRoot,
|
|
3418
|
-
worktreesDir,
|
|
3419
|
-
worktreesBytes: 0,
|
|
3420
|
-
runCount: 0,
|
|
3421
|
-
workerCount: 0,
|
|
3422
|
-
oldestRunAt: null,
|
|
3423
|
-
scannedAt
|
|
3424
|
-
};
|
|
3425
|
-
}
|
|
3426
|
-
let totalBytes = 0;
|
|
3427
|
-
let runCount = 0;
|
|
3428
|
-
let workerCount = 0;
|
|
3429
|
-
let oldestMs = null;
|
|
3430
|
-
let entries;
|
|
3431
|
-
try {
|
|
3432
|
-
entries = readdirSync10(worktreesDir, { withFileTypes: true });
|
|
3433
|
-
} catch {
|
|
3434
|
-
return {
|
|
3435
|
-
harnessRoot,
|
|
3436
|
-
worktreesDir,
|
|
3437
|
-
worktreesBytes: null,
|
|
3438
|
-
runCount: 0,
|
|
3439
|
-
workerCount: 0,
|
|
3440
|
-
oldestRunAt: null,
|
|
3441
|
-
scannedAt
|
|
3442
|
-
};
|
|
3443
|
-
}
|
|
3444
|
-
for (const runEntry of entries) {
|
|
3445
|
-
if (!runEntry.isDirectory()) continue;
|
|
3446
|
-
runCount += 1;
|
|
3447
|
-
const runPath = path22.join(worktreesDir, runEntry.name);
|
|
3448
|
-
try {
|
|
3449
|
-
const st = statSync9(runPath);
|
|
3450
|
-
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
3451
|
-
} catch {
|
|
3452
|
-
}
|
|
3453
|
-
try {
|
|
3454
|
-
for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
|
|
3455
|
-
if (workerEntry.isDirectory()) workerCount += 1;
|
|
3456
|
-
}
|
|
3457
|
-
} catch {
|
|
3458
|
-
}
|
|
3459
|
-
if (totalBytes !== null && opts.perRunEntryCap !== null) {
|
|
3460
|
-
const cap = opts.perRunEntryCap ?? 5e4;
|
|
3461
|
-
if (cap <= 0) {
|
|
3462
|
-
totalBytes = null;
|
|
3463
|
-
} else {
|
|
3464
|
-
const runBytes = directorySizeBytes(runPath, cap);
|
|
3465
|
-
if (runBytes === null) totalBytes = null;
|
|
3466
|
-
else totalBytes += runBytes;
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
return {
|
|
3471
|
-
harnessRoot,
|
|
3472
|
-
worktreesDir,
|
|
3473
|
-
worktreesBytes: totalBytes,
|
|
3474
|
-
runCount,
|
|
3475
|
-
workerCount,
|
|
3476
|
-
oldestRunAt: oldestMs === null ? null : new Date(oldestMs).toISOString(),
|
|
3477
|
-
scannedAt
|
|
3478
|
-
};
|
|
3479
|
-
}
|
|
3480
|
-
|
|
3481
|
-
// src/cleanup-harness-roots.ts
|
|
3482
|
-
init_paths();
|
|
3483
|
-
import { existsSync as existsSync20 } from "node:fs";
|
|
3484
|
-
import { homedir as homedir4 } from "node:os";
|
|
3485
|
-
import path23 from "node:path";
|
|
3486
|
-
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
3487
|
-
"/var/tmp/kynver-harness",
|
|
3488
|
-
path23.join(homedir4(), ".openclaw", "harness")
|
|
3489
|
-
];
|
|
3490
|
-
function addRoot(seen, roots, candidate) {
|
|
3491
|
-
if (!candidate?.trim()) return;
|
|
3492
|
-
const resolved = normalizeHarnessRoot(candidate.trim());
|
|
3493
|
-
if (seen.has(resolved)) return;
|
|
3494
|
-
seen.add(resolved);
|
|
3495
|
-
roots.push(resolved);
|
|
3496
|
-
}
|
|
3497
|
-
function shouldScanWellKnownRoots(options) {
|
|
3498
|
-
if (options.scanWellKnown != null) return options.scanWellKnown;
|
|
3499
|
-
if (process.env.VITEST === "true") return false;
|
|
3500
|
-
return process.env.KYNVER_CLEANUP_SCAN_WELL_KNOWN !== "0" && !["0", "false", "no"].includes((process.env.KYNVER_CLEANUP_SCAN_WELL_KNOWN ?? "").toLowerCase());
|
|
3501
|
-
}
|
|
3502
|
-
function resolveHarnessScanRoots(options = {}) {
|
|
3503
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3504
|
-
const roots = [];
|
|
3505
|
-
addRoot(seen, roots, options.harnessRoot ?? resolveHarnessRoot());
|
|
3506
|
-
const extra = process.env.KYNVER_CLEANUP_EXTRA_ROOTS?.split(",").map((part) => part.trim()).filter(Boolean);
|
|
3507
|
-
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
3508
|
-
if (shouldScanWellKnownRoots(options)) {
|
|
3509
|
-
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
3510
|
-
const resolved = path23.resolve(candidate);
|
|
3511
|
-
if (!seen.has(resolved) && existsSync20(resolved)) addRoot(seen, roots, resolved);
|
|
3512
|
-
}
|
|
3513
|
-
}
|
|
3514
|
-
return roots;
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
|
-
// src/cleanup-disk-pressure.ts
|
|
3518
|
-
init_disk_gate();
|
|
3519
|
-
function envFlag2(name) {
|
|
3520
|
-
const v = process.env[name];
|
|
3521
|
-
return v === "1" || v === "true" || v === "yes";
|
|
3522
|
-
}
|
|
3523
|
-
function envNumber(name, fallback) {
|
|
3524
|
-
const raw = process.env[name];
|
|
3525
|
-
if (!raw) return fallback;
|
|
3526
|
-
const n = Number(raw);
|
|
3527
|
-
return Number.isFinite(n) ? n : fallback;
|
|
3528
|
-
}
|
|
3529
|
-
function observeCleanupDiskPressure(input = {}) {
|
|
3530
|
-
const diskPath = input.diskPath?.trim() || process.env.KYNVER_DISK_GUARD_PATH?.trim() || "/";
|
|
3531
|
-
const maxUsedPercent = envNumber("KYNVER_DISK_GUARD_MAX_USED_PERCENT", 75);
|
|
3532
|
-
const diskGate = observeRunnerDiskGate({
|
|
3533
|
-
...input,
|
|
3534
|
-
diskPath,
|
|
3535
|
-
diskMaxUsedPercent: input.diskMaxUsedPercent ?? maxUsedPercent
|
|
3536
|
-
});
|
|
3537
|
-
let pressured = !diskGate.ok || diskGate.usedPercent >= maxUsedPercent;
|
|
3538
|
-
const force = process.env.KYNVER_CLEANUP_DISK_PRESSURE_FORCE?.trim().toLowerCase();
|
|
3539
|
-
if (force === "none" || force === "off" || force === "0") pressured = false;
|
|
3540
|
-
else if (force === "pressured" || force === "on" || force === "1") pressured = true;
|
|
3541
|
-
return { diskGate, pressured, maxUsedPercent };
|
|
3542
|
-
}
|
|
3543
|
-
function applyDiskPressureToRetention(retention, pressure) {
|
|
3544
|
-
if (!pressure.pressured) return retention;
|
|
3545
|
-
const executeOnPressure = retention.execute || !envFlag2("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");
|
|
3546
|
-
return {
|
|
3547
|
-
...retention,
|
|
3548
|
-
execute: executeOnPressure,
|
|
3549
|
-
nodeModulesAgeMs: 0,
|
|
3550
|
-
// Disk pressure means the current-run-only cleanup scope has already fallen
|
|
3551
|
-
// behind. Expand the sweep to every known harness root/run, but keep the
|
|
3552
|
-
// worktree salvage/PR/dirty/live-worker guards in the cleanup pipeline.
|
|
3553
|
-
runIdFilter: void 0,
|
|
3554
|
-
includeOrphans: true,
|
|
3555
|
-
terminalWorktreesAgeMs: 0,
|
|
3556
|
-
runDirectoriesAgeMs: 0,
|
|
3557
|
-
worktreesAgeMs: retention.worktreesAgeMs > 0 ? retention.worktreesAgeMs : DEFAULT_WORKTREES_AGE_MS,
|
|
3558
|
-
diskPressure: true,
|
|
3559
|
-
diskGate: pressure.diskGate
|
|
3560
|
-
};
|
|
3561
|
-
}
|
|
3562
|
-
|
|
3563
|
-
// src/cleanup-progress.ts
|
|
3564
|
-
function emitCleanupProgress(phase, detail) {
|
|
3565
|
-
if (process.env.KYNVER_CLEANUP_QUIET === "1") return;
|
|
3566
|
-
const suffix = detail ? `: ${detail}` : "";
|
|
3567
|
-
console.error(`[kynver cleanup] ${phase}${suffix}`);
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
// src/cleanup-git-rev-cache.ts
|
|
3571
|
-
init_git();
|
|
3572
|
-
var CleanupGitRevCache = class {
|
|
3573
|
-
aheadOfMain = /* @__PURE__ */ new Map();
|
|
3574
|
-
countAheadOfMain(worktreePath, base = "origin/main") {
|
|
3575
|
-
const key = `${worktreePath}\0${base}`;
|
|
3576
|
-
if (this.aheadOfMain.has(key)) return this.aheadOfMain.get(key) ?? null;
|
|
3577
|
-
const result = gitCapture(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
|
|
3578
|
-
if (result.status !== 0) {
|
|
3579
|
-
this.aheadOfMain.set(key, null);
|
|
3580
|
-
return null;
|
|
3581
|
-
}
|
|
3582
|
-
const count = Number(result.stdout.trim());
|
|
3583
|
-
const parsed = Number.isFinite(count) ? count : null;
|
|
3584
|
-
this.aheadOfMain.set(key, parsed);
|
|
3585
|
-
return parsed;
|
|
3586
|
-
}
|
|
3587
|
-
};
|
|
3588
|
-
|
|
3589
|
-
// src/cleanup-git-probe.ts
|
|
3590
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3591
|
-
import { existsSync as existsSync21 } from "node:fs";
|
|
3592
|
-
import path24 from "node:path";
|
|
3593
|
-
var CLEANUP_GIT_PROBE_TIMEOUT_MS = 5e3;
|
|
3594
|
-
function cleanupGitCapture(cwd, args) {
|
|
3595
|
-
if (!existsSync21(path24.join(cwd, ".git"))) {
|
|
3596
|
-
return { status: null, stdout: "", stderr: "", error: "not_a_git_repo" };
|
|
3597
|
-
}
|
|
3598
|
-
try {
|
|
3599
|
-
const res = spawnSync3("git", args, {
|
|
3600
|
-
cwd,
|
|
3601
|
-
encoding: "utf8",
|
|
3602
|
-
timeout: CLEANUP_GIT_PROBE_TIMEOUT_MS
|
|
3603
|
-
});
|
|
3604
|
-
const timedOut = res.error?.message?.includes("ETIMEDOUT") === true;
|
|
3605
|
-
return {
|
|
3606
|
-
status: timedOut ? null : res.status,
|
|
3607
|
-
stdout: res.stdout || "",
|
|
3608
|
-
stderr: res.stderr || "",
|
|
3609
|
-
error: timedOut ? "git_timeout" : res.error ? res.error.message : null
|
|
3610
|
-
};
|
|
3611
|
-
} catch (error) {
|
|
3612
|
-
return {
|
|
3613
|
-
status: null,
|
|
3614
|
-
stdout: "",
|
|
3615
|
-
stderr: "",
|
|
3616
|
-
error: error.message
|
|
3617
|
-
};
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
function gitStatusShortForCleanup(worktreePath) {
|
|
3621
|
-
const captured = cleanupGitCapture(worktreePath, ["status", "--short"]);
|
|
3622
|
-
if (captured.error === "not_a_git_repo") return [];
|
|
3623
|
-
if (captured.error === "git_timeout" || captured.status !== 0) return null;
|
|
3624
|
-
return captured.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
// src/cleanup-git-status-cache.ts
|
|
3628
|
-
var CleanupGitStatusCache = class {
|
|
3629
|
-
cache = /* @__PURE__ */ new Map();
|
|
3630
|
-
porcelain(worktreePath) {
|
|
3631
|
-
const resolved = worktreePath;
|
|
3632
|
-
const cached = this.cache.get(resolved);
|
|
3633
|
-
if (cached !== void 0) return cached;
|
|
3634
|
-
const lines = gitStatusShortForCleanup(resolved) ?? [];
|
|
3635
|
-
this.cache.set(resolved, lines);
|
|
3636
|
-
return lines;
|
|
3637
|
-
}
|
|
3638
|
-
};
|
|
3639
|
-
|
|
3640
|
-
// src/cleanup-run-terminal-cache.ts
|
|
3641
|
-
var CleanupRunTerminalCache = class {
|
|
3642
|
-
cache = /* @__PURE__ */ new Map();
|
|
3643
|
-
derive(run) {
|
|
3644
|
-
const cached = this.cache.get(run.id);
|
|
3645
|
-
if (cached !== void 0) return cached;
|
|
3646
|
-
const derived = deriveTerminalRunStatus(run);
|
|
3647
|
-
this.cache.set(run.id, derived);
|
|
3648
|
-
return derived;
|
|
3649
|
-
}
|
|
3650
|
-
};
|
|
3651
|
-
|
|
3652
|
-
// src/cleanup-summary.ts
|
|
3653
|
-
var DEFAULT_SAMPLE_ACTIONS = 12;
|
|
3654
|
-
var DEFAULT_SAMPLE_SKIPS = 8;
|
|
3655
|
-
function tallyActionKinds(actions) {
|
|
3656
|
-
const counts = {};
|
|
3657
|
-
for (const action of actions) {
|
|
3658
|
-
counts[action.kind] = (counts[action.kind] ?? 0) + 1;
|
|
3659
|
-
}
|
|
3660
|
-
return counts;
|
|
3661
|
-
}
|
|
3662
|
-
function buildCleanupCompactSummary(summary, options = {}) {
|
|
3663
|
-
const maxActions = options.maxSampleActions ?? DEFAULT_SAMPLE_ACTIONS;
|
|
3664
|
-
const maxSkips = options.maxSampleSkips ?? DEFAULT_SAMPLE_SKIPS;
|
|
3665
|
-
return {
|
|
3666
|
-
harnessRoot: summary.harnessRoot,
|
|
3667
|
-
scanRoots: summary.scanRoots,
|
|
3668
|
-
dryRun: summary.dryRun,
|
|
3669
|
-
execute: summary.execute,
|
|
3670
|
-
scannedAt: summary.scannedAt,
|
|
3671
|
-
finalizedRuns: summary.finalizedRuns.length,
|
|
3672
|
-
actionCount: summary.actions.length,
|
|
3673
|
-
actionKinds: tallyActionKinds(summary.actions),
|
|
3674
|
-
totals: summary.totals,
|
|
3675
|
-
...summary.storage ? { storage: summary.storage } : {},
|
|
3676
|
-
...summary.preservedLivePaths?.length ? { preservedLivePaths: summary.preservedLivePaths } : {},
|
|
3677
|
-
...summary.removedRunDirectories != null ? { removedRunDirectories: summary.removedRunDirectories } : {},
|
|
3678
|
-
sampleActions: summary.actions.slice(0, maxActions).map((action) => ({
|
|
3679
|
-
kind: action.kind,
|
|
3680
|
-
path: action.path,
|
|
3681
|
-
...action.skipReason ? { skipReason: action.skipReason } : {},
|
|
3682
|
-
...action.bytes != null ? { bytes: action.bytes } : {},
|
|
3683
|
-
...action.runId ? { runId: action.runId } : {},
|
|
3684
|
-
...action.worker ? { worker: action.worker } : {}
|
|
3685
|
-
})),
|
|
3686
|
-
sampleSkips: summary.skips.slice(0, maxSkips)
|
|
3687
|
-
};
|
|
3688
|
-
}
|
|
3689
|
-
|
|
3690
|
-
// src/cleanup.ts
|
|
3691
|
-
function resolvePaths(options = {}) {
|
|
3692
|
-
const harnessRoot = options.harnessRoot ? normalizeHarnessRoot(options.harnessRoot) : resolveHarnessRoot();
|
|
3693
|
-
const scanRoots = resolveHarnessScanRoots({ harnessRoot });
|
|
3694
|
-
const now = options.now ?? Date.now();
|
|
3695
|
-
return { harnessRoot, scanRoots, now };
|
|
3696
|
-
}
|
|
3697
|
-
function normalizeGuardSkip(skip) {
|
|
3698
|
-
if (typeof skip === "string") return { reason: skip };
|
|
3699
|
-
return skip;
|
|
3700
|
-
}
|
|
3701
|
-
function recordSkip(skips, pathValue, reason, detail) {
|
|
3702
|
-
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
3703
|
-
}
|
|
3704
|
-
function attachCandidateBytes(candidate, accountBytes, byteEntryCap) {
|
|
3705
|
-
if (!accountBytes || candidate.bytes != null) return candidate;
|
|
3706
|
-
return { ...candidate, bytes: directorySizeBytes(candidate.path, byteEntryCap) };
|
|
3707
|
-
}
|
|
3708
|
-
function tallySkipReasons(actions, skips) {
|
|
3709
|
-
const counts = {};
|
|
3710
|
-
for (const skip of skips) {
|
|
3711
|
-
counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
|
|
3712
|
-
}
|
|
3713
|
-
for (const action of actions) {
|
|
3714
|
-
if (action.skipReason) {
|
|
3715
|
-
counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
return counts;
|
|
3719
|
-
}
|
|
3720
|
-
function removeDependencyCacheAction(candidate, execute) {
|
|
3721
|
-
if (candidate.kind === "remove_next_cache") return removeNextCache(candidate, execute);
|
|
3722
|
-
return removeNodeModules(candidate, execute);
|
|
3723
|
-
}
|
|
3724
|
-
function pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir) {
|
|
3725
|
-
if (candidate.kind === "remove_next_cache") {
|
|
3726
|
-
return isHarnessNextCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
3727
|
-
}
|
|
3728
|
-
return isHarnessNodeModulesPath(candidate.path, harnessRoot, worktreesDir);
|
|
3729
|
-
}
|
|
3730
|
-
function mergeWorktreeIndexes(scanRoots) {
|
|
3731
|
-
const merged = /* @__PURE__ */ new Map();
|
|
3732
|
-
for (const root of scanRoots) {
|
|
3733
|
-
for (const [key, value] of buildWorktreeIndexAt(root)) merged.set(key, value);
|
|
3734
|
-
}
|
|
3735
|
-
return merged;
|
|
3736
|
-
}
|
|
3737
|
-
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
3738
|
-
if (candidate.runId && candidate.worker) {
|
|
3739
|
-
return path25.join(worktreesDir, candidate.runId, candidate.worker);
|
|
3740
|
-
}
|
|
3741
|
-
return path25.resolve(candidate.path, "..");
|
|
3742
|
-
}
|
|
3743
|
-
function runHarnessCleanup(options = {}) {
|
|
3744
|
-
let retention = resolveHarnessRetention(options);
|
|
3745
|
-
const diskPressure = observeCleanupDiskPressure();
|
|
3746
|
-
retention = applyDiskPressureToRetention(retention, diskPressure);
|
|
3747
|
-
const paths = resolvePaths(options);
|
|
3748
|
-
emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
|
|
3749
|
-
const activeGuards = collectActiveWorktreeGuards(paths.scanRoots, paths.now);
|
|
3750
|
-
const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
|
|
3751
|
-
if (finalizedRuns.length > 0) {
|
|
3752
|
-
emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
|
|
3753
|
-
}
|
|
3754
|
-
emitCleanupProgress("index", "building worktree index");
|
|
3755
|
-
const index = mergeWorktreeIndexes(paths.scanRoots);
|
|
3756
|
-
emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
|
|
3757
|
-
const liveness = {
|
|
3758
|
-
runTerminalCache: new CleanupRunTerminalCache(),
|
|
3759
|
-
gitStatusCache: new CleanupGitStatusCache(),
|
|
3760
|
-
gitRevCache: new CleanupGitRevCache()
|
|
3761
|
-
};
|
|
3762
|
-
const skips = [];
|
|
3763
|
-
const actions = [];
|
|
3764
|
-
const processedPaths = /* @__PURE__ */ new Set();
|
|
3765
|
-
const maxActions = retention.maxActionsPerSweep;
|
|
3766
|
-
const atSweepCap = () => actions.length >= maxActions;
|
|
3767
|
-
for (const harnessRoot of paths.scanRoots) {
|
|
3768
|
-
if (atSweepCap()) break;
|
|
3769
|
-
emitCleanupProgress("root", harnessRoot);
|
|
3770
|
-
const worktreesDir = path25.join(harnessRoot, "worktrees");
|
|
3771
|
-
const rootIndex = filterWorktreeIndexForRoot(index, harnessRoot);
|
|
3772
|
-
const scanOpts = {
|
|
3773
|
-
harnessRoot,
|
|
3774
|
-
worktreesDir,
|
|
3775
|
-
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
3776
|
-
worktreesAgeMs: retention.worktreesAgeMs,
|
|
3777
|
-
includeOrphans: retention.includeOrphans,
|
|
3778
|
-
runIdFilter: retention.runIdFilter,
|
|
3779
|
-
index: rootIndex,
|
|
3780
|
-
now: paths.now
|
|
3781
|
-
};
|
|
3782
|
-
const dependencyCandidates = retention.scanDependencyCaches ? scanDependencyCacheCandidates(scanOpts) : [];
|
|
3783
|
-
emitCleanupProgress(
|
|
3784
|
-
"dependency",
|
|
3785
|
-
retention.scanDependencyCaches ? `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}` : "skipped (worktree-only sweep)"
|
|
3786
|
-
);
|
|
3787
|
-
let dependencyProcessed = 0;
|
|
3788
|
-
for (const raw of dependencyCandidates) {
|
|
3789
|
-
if (atSweepCap()) break;
|
|
3790
|
-
dependencyProcessed += 1;
|
|
3791
|
-
if (dependencyProcessed % 50 === 0) {
|
|
3792
|
-
emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
|
|
3793
|
-
}
|
|
3794
|
-
const resolved = path25.resolve(raw.path);
|
|
3795
|
-
if (processedPaths.has(resolved)) continue;
|
|
3796
|
-
processedPaths.add(resolved);
|
|
3797
|
-
const candidate = { ...raw, path: resolved };
|
|
3798
|
-
const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
|
|
3799
|
-
if (pathSkip) {
|
|
3800
|
-
recordSkip(skips, candidate.path, pathSkip);
|
|
3801
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
3802
|
-
continue;
|
|
3803
|
-
}
|
|
3804
|
-
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
3805
|
-
const indexed = rootIndex.get(path25.resolve(worktreePath)) ?? null;
|
|
3806
|
-
const guardReason = skipDependencyCacheRemoval({
|
|
3807
|
-
indexed,
|
|
3808
|
-
includeOrphans: true,
|
|
3809
|
-
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
3810
|
-
ageMs: candidate.ageMs,
|
|
3811
|
-
worktreePath,
|
|
3812
|
-
activeWorktreePaths: activeGuards.activeWorktreePaths,
|
|
3813
|
-
diskPressure: retention.diskPressure,
|
|
3814
|
-
gitStatusCache: liveness.gitStatusCache
|
|
3815
|
-
});
|
|
3816
|
-
if (guardReason) {
|
|
3817
|
-
recordSkip(skips, candidate.path, guardReason);
|
|
3818
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3819
|
-
continue;
|
|
3820
|
-
}
|
|
3821
|
-
actions.push(
|
|
3822
|
-
removeDependencyCacheAction(
|
|
3823
|
-
attachCandidateBytes(candidate, retention.accountBytes, retention.byteAccountingEntryCap),
|
|
3824
|
-
retention.execute
|
|
3825
|
-
)
|
|
3826
|
-
);
|
|
3827
|
-
}
|
|
3828
|
-
if (retention.scanDependencyCaches) for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
3829
|
-
if (atSweepCap()) break;
|
|
3830
|
-
const resolved = path25.resolve(raw.path);
|
|
3831
|
-
if (processedPaths.has(resolved)) continue;
|
|
3832
|
-
processedPaths.add(resolved);
|
|
3833
|
-
const candidate = { ...raw, path: resolved };
|
|
3834
|
-
const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
3835
|
-
if (pathSkip) {
|
|
3836
|
-
recordSkip(skips, candidate.path, pathSkip);
|
|
3837
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
3838
|
-
continue;
|
|
3839
|
-
}
|
|
3840
|
-
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
3841
|
-
const indexed = rootIndex.get(path25.resolve(worktreePath)) ?? null;
|
|
3842
|
-
const guardReason = skipBuildCacheRemoval({
|
|
3843
|
-
indexed,
|
|
3844
|
-
includeOrphans: true,
|
|
3845
|
-
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
3846
|
-
ageMs: candidate.ageMs,
|
|
3847
|
-
worktreePath,
|
|
3848
|
-
activeWorktreePaths: activeGuards.activeWorktreePaths,
|
|
3849
|
-
diskPressure: retention.diskPressure,
|
|
3850
|
-
gitStatusCache: liveness.gitStatusCache
|
|
3851
|
-
});
|
|
3852
|
-
if (guardReason) {
|
|
3853
|
-
recordSkip(skips, candidate.path, guardReason);
|
|
3854
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3855
|
-
continue;
|
|
3856
|
-
}
|
|
3857
|
-
actions.push(
|
|
3858
|
-
removeBuildCache(
|
|
3859
|
-
attachCandidateBytes(candidate, retention.accountBytes, retention.byteAccountingEntryCap),
|
|
3860
|
-
retention.execute
|
|
3861
|
-
)
|
|
3862
|
-
);
|
|
3863
|
-
}
|
|
3864
|
-
const worktreeCandidates = [
|
|
3865
|
-
...scanWorktreeCandidates(scanOpts),
|
|
3866
|
-
...scanDuplicateWorktreeCandidates(scanOpts)
|
|
3867
|
-
];
|
|
3868
|
-
emitCleanupProgress("worktrees", `${worktreeCandidates.length} candidate(s) at ${harnessRoot}`);
|
|
3869
|
-
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
3870
|
-
let worktreeProcessed = 0;
|
|
3871
|
-
for (const raw of worktreeCandidates) {
|
|
3872
|
-
if (atSweepCap()) break;
|
|
3873
|
-
worktreeProcessed += 1;
|
|
3874
|
-
if (worktreeProcessed % 50 === 0) {
|
|
3875
|
-
emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
|
|
3876
|
-
}
|
|
3877
|
-
const resolved = path25.resolve(raw.path);
|
|
3878
|
-
if (worktreeSeen.has(resolved)) continue;
|
|
3879
|
-
worktreeSeen.add(resolved);
|
|
3880
|
-
const candidate = { ...raw, path: resolved };
|
|
3881
|
-
const indexed = rootIndex.get(path25.resolve(candidate.path)) ?? null;
|
|
3882
|
-
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
3883
|
-
worktreePath: candidate.path,
|
|
3884
|
-
harnessRoot,
|
|
3885
|
-
runId: candidate.runId,
|
|
3886
|
-
workerName: candidate.worker,
|
|
3887
|
-
now: paths.now
|
|
3888
|
-
});
|
|
3889
|
-
const guardSkip = skipWorktreeRemoval({
|
|
3890
|
-
indexed,
|
|
3891
|
-
worktreePath: path25.resolve(candidate.path),
|
|
3892
|
-
includeOrphans: retention.includeOrphans,
|
|
3893
|
-
worktreesAgeMs: retention.worktreesAgeMs,
|
|
3894
|
-
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
3895
|
-
ageMs: candidate.ageMs,
|
|
3896
|
-
orphanSafety,
|
|
3897
|
-
worktreeRemovalGuard: options.worktreeRemovalGuard,
|
|
3898
|
-
liveness,
|
|
3899
|
-
now: paths.now,
|
|
3900
|
-
harnessRoot,
|
|
3901
|
-
writeSalvageEvidence: retention.execute
|
|
3902
|
-
});
|
|
3903
|
-
if (guardSkip) {
|
|
3904
|
-
const { reason: guardReason, detail: guardDetail } = normalizeGuardSkip(guardSkip);
|
|
3905
|
-
recordSkip(skips, candidate.path, guardReason, guardDetail);
|
|
3906
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3907
|
-
continue;
|
|
3908
|
-
}
|
|
3909
|
-
actions.push(
|
|
3910
|
-
removeWorktree(
|
|
3911
|
-
attachCandidateBytes(candidate, retention.accountBytes, retention.byteAccountingEntryCap),
|
|
3912
|
-
retention.execute
|
|
3913
|
-
)
|
|
3914
|
-
);
|
|
3915
|
-
}
|
|
3916
|
-
if (!atSweepCap() && retention.runDirectoriesAgeMs >= 0) {
|
|
3917
|
-
for (const raw of scanStaleRunDirectoryCandidates({
|
|
3918
|
-
harnessRoot,
|
|
3919
|
-
worktreesDir,
|
|
3920
|
-
runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
|
|
3921
|
-
runIdFilter: retention.runIdFilter,
|
|
3922
|
-
activeGuards,
|
|
3923
|
-
now: paths.now
|
|
3924
|
-
})) {
|
|
3925
|
-
if (atSweepCap()) break;
|
|
3926
|
-
const resolved = path25.resolve(raw.path);
|
|
3927
|
-
if (processedPaths.has(resolved)) continue;
|
|
3928
|
-
processedPaths.add(resolved);
|
|
3929
|
-
const candidate = { ...raw, path: resolved };
|
|
3930
|
-
const runId = candidate.runId ?? path25.basename(resolved);
|
|
3931
|
-
const dirSkip = skipRunDirectoryRemoval({
|
|
3932
|
-
harnessRoot,
|
|
3933
|
-
runId,
|
|
3934
|
-
runPath: resolved,
|
|
3935
|
-
ageMs: candidate.ageMs,
|
|
3936
|
-
runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
|
|
3937
|
-
activeGuards
|
|
3938
|
-
});
|
|
3939
|
-
if (dirSkip) {
|
|
3940
|
-
recordSkip(skips, candidate.path, dirSkip);
|
|
3941
|
-
actions.push({ ...candidate, executed: false, skipped: true, skipReason: dirSkip });
|
|
3942
|
-
continue;
|
|
3943
|
-
}
|
|
3944
|
-
actions.push(
|
|
3945
|
-
removeRunDirectory(
|
|
3946
|
-
attachCandidateBytes(candidate, retention.accountBytes, retention.byteAccountingEntryCap),
|
|
3947
|
-
retention.execute
|
|
3948
|
-
)
|
|
3949
|
-
);
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
let candidateBytes = 0;
|
|
3954
|
-
let removedRunDirectories = 0;
|
|
3955
|
-
let reclaimableBytes = 0;
|
|
3956
|
-
let removedBytes = 0;
|
|
3957
|
-
let removedPaths = 0;
|
|
3958
|
-
let skippedPaths = 0;
|
|
3959
|
-
for (const action of actions) {
|
|
3960
|
-
if (action.bytes) candidateBytes += action.bytes;
|
|
3961
|
-
if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
|
|
3962
|
-
if (action.executed) {
|
|
3963
|
-
removedPaths += 1;
|
|
3964
|
-
removedBytes += action.bytes ?? 0;
|
|
3965
|
-
if (action.kind === "remove_run_directory") removedRunDirectories += 1;
|
|
3966
|
-
} else if (action.skipped) {
|
|
3967
|
-
skippedPaths += 1;
|
|
3968
|
-
if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
const storage = retention.accountBytes ? harnessStorageSnapshot({
|
|
3972
|
-
harnessRoot: paths.harnessRoot,
|
|
3973
|
-
now: paths.now,
|
|
3974
|
-
perRunEntryCap: retention.storagePerRunEntryCap
|
|
3975
|
-
}) : void 0;
|
|
3976
|
-
emitCleanupProgress(
|
|
3977
|
-
"complete",
|
|
3978
|
-
`${actions.length} action(s), ${skippedPaths} skipped, ${removedPaths} removed`
|
|
3979
|
-
);
|
|
3980
|
-
const preservedLivePaths = collectPreservedLivePaths(actions, skips);
|
|
3981
|
-
const compactSummary = buildCleanupCompactSummary({
|
|
3982
|
-
harnessRoot: paths.harnessRoot,
|
|
3983
|
-
scanRoots: paths.scanRoots,
|
|
3984
|
-
dryRun: !retention.execute,
|
|
3985
|
-
execute: retention.execute,
|
|
3986
|
-
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
3987
|
-
worktreesAgeMs: retention.worktreesAgeMs,
|
|
3988
|
-
includeOrphans: retention.includeOrphans,
|
|
3989
|
-
diskPressure: retention.diskPressure,
|
|
3990
|
-
diskGate: retention.diskGate ? {
|
|
3991
|
-
ok: retention.diskGate.ok,
|
|
3992
|
-
path: retention.diskGate.path,
|
|
3993
|
-
freeBytes: retention.diskGate.freeBytes,
|
|
3994
|
-
usedPercent: retention.diskGate.usedPercent,
|
|
3995
|
-
reason: retention.diskGate.reason
|
|
3996
|
-
} : void 0,
|
|
3997
|
-
scannedAt: new Date(paths.now).toISOString(),
|
|
3998
|
-
finalizedRuns,
|
|
3999
|
-
actions,
|
|
4000
|
-
skips,
|
|
4001
|
-
totals: {
|
|
4002
|
-
candidateBytes,
|
|
4003
|
-
reclaimableBytes,
|
|
4004
|
-
removedBytes,
|
|
4005
|
-
removedPaths,
|
|
4006
|
-
skippedPaths,
|
|
4007
|
-
skipReasons: tallySkipReasons(actions, skips)
|
|
4008
|
-
},
|
|
4009
|
-
...storage ? { storage } : {},
|
|
4010
|
-
...preservedLivePaths.length > 0 ? { preservedLivePaths } : {},
|
|
4011
|
-
...removedRunDirectories > 0 ? { removedRunDirectories } : {}
|
|
4012
|
-
});
|
|
4013
|
-
return {
|
|
4014
|
-
harnessRoot: paths.harnessRoot,
|
|
4015
|
-
scanRoots: paths.scanRoots,
|
|
4016
|
-
dryRun: !retention.execute,
|
|
4017
|
-
execute: retention.execute,
|
|
4018
|
-
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
4019
|
-
worktreesAgeMs: retention.worktreesAgeMs,
|
|
4020
|
-
includeOrphans: retention.includeOrphans,
|
|
4021
|
-
diskPressure: retention.diskPressure,
|
|
4022
|
-
diskGate: retention.diskGate ? {
|
|
4023
|
-
ok: retention.diskGate.ok,
|
|
4024
|
-
path: retention.diskGate.path,
|
|
4025
|
-
freeBytes: retention.diskGate.freeBytes,
|
|
4026
|
-
usedPercent: retention.diskGate.usedPercent,
|
|
4027
|
-
reason: retention.diskGate.reason
|
|
4028
|
-
} : void 0,
|
|
4029
|
-
scannedAt: new Date(paths.now).toISOString(),
|
|
4030
|
-
finalizedRuns,
|
|
4031
|
-
actions,
|
|
4032
|
-
skips,
|
|
4033
|
-
totals: {
|
|
4034
|
-
candidateBytes,
|
|
4035
|
-
reclaimableBytes,
|
|
4036
|
-
removedBytes,
|
|
4037
|
-
removedPaths,
|
|
4038
|
-
skippedPaths,
|
|
4039
|
-
skipReasons: tallySkipReasons(actions, skips)
|
|
4040
|
-
},
|
|
4041
|
-
...storage ? { storage } : {},
|
|
4042
|
-
...preservedLivePaths.length > 0 ? { preservedLivePaths } : {},
|
|
4043
|
-
...removedRunDirectories > 0 ? { removedRunDirectories } : {},
|
|
4044
|
-
compactSummary
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
export {
|
|
4048
|
-
DEFAULT_MAX_ACTIONS_PER_SWEEP,
|
|
4049
|
-
DEFAULT_NODE_MODULES_AGE_MS,
|
|
4050
|
-
DEFAULT_RUN_DIRECTORIES_AGE_MS,
|
|
4051
|
-
DEFAULT_WORKTREES_AGE_MS,
|
|
4052
|
-
runHarnessCleanup
|
|
4053
|
-
};
|
|
4054
|
-
//# sourceMappingURL=cleanup.js.map
|
|
1
|
+
var k=(e,r)=>()=>(e&&(r=e(e=0)),r);import{existsSync as ro,mkdirSync as to,readFileSync as Gr,readdirSync as Gi,statSync as $r,writeFileSync as no}from"node:fs";import oo from"node:path";function ur(e){console.error(e),process.exit(1)}function cr(e){return process.platform!=="win32"?e:{windowsHide:!0,...e}}function Ee(e){try{return JSON.parse(e)}catch{return null}}function _(e,r){try{return JSON.parse(Gr(e,"utf8"))}catch(t){if(arguments.length>1)return r;ur(`failed to read ${e}: ${t.message}`)}}function Yr(e,r){to(oo.dirname(e),{recursive:!0}),no(e,`${JSON.stringify(r,null,2)}
|
|
2
|
+
`)}function b(e){return String(e||"").toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"worker"}function Pe(e){try{return $r(e).size}catch{return 0}}function We(e){try{return $r(e).mtime.toISOString()}catch{return null}}function Vr(e,r){return ro(e)?Gr(e,"utf8").split(`
|
|
3
|
+
`).slice(-r).join(`
|
|
4
|
+
`):""}function L(e){if(!e)return!1;try{return process.kill(e,0),!0}catch{return!1}}function zr(e){let r=null,t=-1/0;for(let n of e){if(!n)continue;let o=Date.parse(n);Number.isFinite(o)&&o>t&&(t=o,r=n)}return r}function lr(e){return Math.max(0,Math.round((Date.now()-e)/1e3))}var y=k(()=>{"use strict"});var so,Vi,Jr=k(()=>{"use strict";so=["ANTHROPIC_API_KEY","ANALYST_API_KEY","RECRUITER_API_KEY","AUTH_SECRET","NEXTAUTH_SECRET","DATABASE_URL","PRODUCTION_DATABASE_URL","KYNVER_PRODUCTION_DATABASE_URL","REDIS_URL","GOOGLE_CLIENT_SECRET","GITHUB_CLIENT_SECRET","KYNVER_API_KEY","KYNVER_SERVICE_SECRET","KYNVER_RUNTIME_SECRET","KYNVER_CRON_SECRET","OPENCLAW_CRON_SECRET","QSTASH_TOKEN","QSTASH_CURRENT_SIGNING_KEY","QSTASH_NEXT_SIGNING_KEY","TOOL_SECRETS_KEK","TOOL_EXECUTOR_DISPATCH_SECRET","CLOUDFLARE_API_TOKEN","STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET","STRIPE_IDENTITY_WEBHOOK_SECRET","VOYAGE_API_KEY","PERPLEXITY_API_KEY","FRED_API_KEY","FMP_API_KEY","CURSOR_API_KEY"],Vi=new Set(so)});import{spawnSync as Xr}from"node:child_process";function le(e,r,t={}){let n=Xr("git",r,cr({cwd:e,encoding:"utf8"}));if(n.status!==0&&!t.allowFailure){let o=`git ${r.join(" ")} failed: ${n.stderr||n.stdout}`;if(t.throwError)throw new Error(o);ur(o)}return n.stdout||""}function de(e){return le(e,["status","--short"],{allowFailure:!0}).split(`
|
|
5
|
+
`).map(r=>r.trim()).filter(Boolean)}function v(e,r){try{let t=Xr("git",r,cr({cwd:e,encoding:"utf8"}));return{status:t.status,stdout:t.stdout||"",stderr:t.stderr||"",error:t.error?t.error.message:null}}catch(t){return{status:null,stdout:"",stderr:"",error:t.message}}}function qr(e,r,t){let n=v(e,["merge-base","--is-ancestor",r,t]);return n.status===0?{isAncestor:!0,error:null}:n.status===1?{isAncestor:!1,error:null}:{isAncestor:null,error:n.error||n.stderr||n.stdout||`git exited ${n.status}`}}function Oe(e,r="origin/main"){let t=typeof r=="string"?{base:r}:r,n=t.baseCommit?.trim()||t.base?.trim()||"origin/main",o=t.baseCommit?.trim()||null;if(!e)return dr(n,"missing worktree path");let s=v(e,["rev-parse","HEAD"]);if(s.status!==0)return dr(n,s.error||s.stderr||s.stdout||"failed to resolve HEAD");let i;if(o)i=o;else{let f=v(e,["rev-parse",n]);if(f.status!==0)return dr(n,f.error||f.stderr||f.stdout||`failed to resolve ${n}`,s.stdout.trim());i=f.stdout.trim()}let a=s.stdout.trim();if(a===i)return{checked:!0,base:n,head:a,baseHead:i,baseIsAncestorOfHead:!0,headIsAncestorOfBase:!0,relation:"synced"};let u=qr(e,i,a),c=qr(e,a,i),l=u.error||c.error||void 0;if(u.isAncestor==null||c.isAncestor==null)return{checked:!1,base:n,head:a,baseHead:i,baseIsAncestorOfHead:u.isAncestor,headIsAncestorOfBase:c.isAncestor,relation:"unknown",...l?{error:l}:{}};let p=u.isAncestor?"ahead":c.isAncestor?"merged":"diverged";return{checked:!0,base:n,head:a,baseHead:i,baseIsAncestorOfHead:u.isAncestor,headIsAncestorOfBase:c.isAncestor,relation:p,...l?{error:l}:{}}}function dr(e,r,t=null){return{checked:!1,base:e,head:t,baseHead:null,baseIsAncestorOfHead:null,headIsAncestorOfBase:null,relation:"unknown",error:r}}var H=k(()=>{"use strict";y();Jr()});import{homedir as Qr}from"node:os";import Zr from"node:path";function io(e){return e==="~"?Qr():e.startsWith("~/")||e.startsWith("~\\")?Zr.join(Qr(),e.slice(2)):e}function Ie(e){return Zr.resolve(io(e))}var pe=k(()=>{"use strict"});var pr=k(()=>{"use strict";H();pe()});var fr=k(()=>{"use strict"});var et=k(()=>{"use strict"});import{existsSync as ao,readFileSync as uo,statfsSync as co}from"node:fs";function mo(){if(process.platform!=="linux")return!1;for(let e of["/proc/sys/kernel/osrelease","/proc/version"])try{if(!ao(e))continue;let r=uo(e,"utf8");if(/microsoft|wsl/i.test(r))return!0}catch{}return!1}function rt(e={}){if(!(e.forceWsl===void 0?mo():e.forceWsl))return null;let t=e.wslHostMount?.trim()||process.env.KYNVER_WSL_HOST_MOUNT?.trim()||fo,n=e.wslHostFreeWarnBytes??lo,o=e.wslHostFreeCriticalBytes??po,s=e.statfs??co,i;try{i=s(t)}catch(R){return{ok:!1,path:t,freeBytes:0,totalBytes:0,usedPercent:100,warnBelowBytes:n,criticalBelowBytes:o,reason:`Windows host disk probe failed at ${t}: ${R.message}`,probeError:R.message}}let a=Number(i.bavail)*Number(i.bsize),u=Number(i.blocks)*Number(i.bsize),c=u>0?(u-a)/u*100:100,l=a<n,p=a<o,f=!l&&!p,g=(a/(1024*1024*1024)).toFixed(1),d=null;return f||(d=`Windows host disk ${t} at ${p?"critical":"warning"}: ${g} GiB free (<${(p?o:n)/1024/1024/1024} GiB); WSL VHDX cannot grow safely. ${go()}`),{ok:f,path:t,freeBytes:a,totalBytes:u,usedPercent:c,warnBelowBytes:n,criticalBelowBytes:o,reason:d,probeError:null}}function go(){return"Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md."}var lo,po,fo,tt=k(()=>{"use strict";lo=25*1024*1024*1024,po=12*1024*1024*1024,fo="/mnt/c"});import{statfsSync as ho}from"node:fs";function Ne(e={}){let r=e.diskPath?.trim()||"/",t=e.diskFreeWarnBytes??ko,n=e.diskFreeCriticalBytes??Ro,o=e.diskMaxUsedPercent??yo,s=e.diskHardMaxUsedPercent??bo,i=ho(r),a=Number(i.bavail)*Number(i.bsize),u=Number(i.blocks)*Number(i.bsize),c=u>0?(u-a)/u*100:100,l=a<t,p=a<n,f=a<t*2,g=f&&c>o,d=f&&c>s,R=!l&&!p&&!g&&!d,x=e.skipWslHostCheck?null:rt(e.wslHost),W=R&&(x?x.ok:!0),U=null;return W||(U=[p?`free space below critical ${n} bytes`:null,l?`free space below warning ${t} bytes`:null,d?`used percent above hard cap ${s}%`:null,g?`used percent above cap ${o}%`:null,x&&!x.ok?x.reason:null].filter(Boolean).join("; ")),So({ok:W,path:r,freeBytes:a,totalBytes:u,usedPercent:c,warnBelowBytes:t,criticalBelowBytes:n,maxUsedPercent:o,hardMaxUsedPercent:s,reason:U,wslHost:x})}function So(e){let r=process.env.KYNVER_DISPATCH_DISK_GATE_FORCE?.trim().toLowerCase();return r?r==="ok"||r==="open"||r==="none"?e.ok?e:{...e,ok:!0,reason:`disk gate forced open by KYNVER_DISPATCH_DISK_GATE_FORCE (suppressed: ${e.reason??"no reason"})`}:r==="block"||r==="pressured"?{...e,ok:!1,reason:`disk gate forced closed by KYNVER_DISPATCH_DISK_GATE_FORCE${e.reason?` (underlying: ${e.reason})`:""}`}:e:e}var ko,Ro,yo,bo,Me=k(()=>{"use strict";tt();ko=30*1024*1024*1024,Ro=15*1024*1024*1024,yo=80,bo=90});import{existsSync as _o,readdirSync as xo,statSync as vo}from"node:fs";import mr from"node:path";function De(){return ot()}function gr(){let{runsDir:e}=De();return nt(e)}function Te(e){return nt(B(e))}function Co(e){try{return vo(e).isDirectory()}catch{return!1}}function nt(e){if(!_o(e))return[];let r=[];for(let t of xo(e,{withFileTypes:!0})){if(t.name==="runs")continue;let n=mr.join(e,t.name);if(!Co(n))continue;let o=_(mr.join(n,"run.json"),void 0);o?.id&&r.push(o)}return r}function hr(e){let{runsDir:r}=De();Yr(mr.join(kr(r,e.id),"run.json"),e)}function fe(e){let{harnessRoot:r}=De();return me(r,e)}function me(e,r){return kr(B(e),b(r))}var V=k(()=>{"use strict";F();y()});import{existsSync as Ao,readdirSync as wo}from"node:fs";import Eo from"node:path";function Rr(e){let r=new Set;for(let n of Object.keys(e.workers||{}))r.add(b(n));let t=Eo.join(fe(e.id),"workers");if(!Ao(t))return[...r];for(let n of wo(t,{withFileTypes:!0}))n.isDirectory()&&r.add(b(n.name));return[...r]}var yr=k(()=>{"use strict";V();y()});function br(e){let r=e.trim();if(!r.startsWith("{"))return null;try{let t=JSON.parse(r);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{return null}return null}function st(e){let r=e.trim();if(!r)return null;let t=br(r);if(t)return t;let n=[],o=/```(?:json)?\s*([\s\S]*?)```/gi,s;for(;(s=o.exec(r))!==null;){let u=br(s[1]??"");u&&n.push(u)}let i=r.indexOf("{"),a=r.lastIndexOf("}");if(i>=0&&a>i){let u=br(r.slice(i,a+1));u&&n.push(u)}return n.length>0?n[n.length-1]:null}function it(e){let r=e.finalResult??e.final_result;if(r!=null)return typeof r=="string"?st(r)??(r.trim()||null):r;let t=typeof e.summary=="string"?e.summary.trim():"";if(!t)return null;let n=st(t);return n||t}var at=k(()=>{"use strict"});import{existsSync as Po,readFileSync as Wo}from"node:fs";function ut(e){return e==="complete"}function ct(e){return ut(e.lastHeartbeatPhase)?e.terminalFinalResult!==void 0&&e.terminalFinalResult!==null?e.terminalFinalResult:e.lastHeartbeatSummary?.trim()||"completed":null}function oe(e){let r={heartbeatCount:0,lastHeartbeatAt:null,lastHeartbeatPhase:null,lastHeartbeatSummary:null,terminalFinalResult:null,heartbeatBlocker:null,timestampAnomalies:[],lastBoxResourceSnapshot:null,lastPrEvidence:[]};if(!Po(e))return r;let t=Date.now()+Oo,n=new Date(t).toISOString(),o=Wo(e,"utf8").split(`
|
|
6
|
+
`).filter(Boolean);for(let s of o){let i=Ee(s);if(!i||typeof i!="object"||Array.isArray(i))continue;let a=i;if(r.heartbeatCount++,a.ts){let u=String(a.ts),c=Date.parse(u);Number.isFinite(c)&&c>t?r.timestampAnomalies.push({kind:"future_heartbeat_timestamp",observedAt:u,clampedTo:n}):r.lastHeartbeatAt=u}a.phase!==void 0&&a.phase!==null&&(r.lastHeartbeatPhase=String(a.phase)),a.summary!==void 0&&a.summary!==null&&(r.lastHeartbeatSummary=String(a.summary)),ut(r.lastHeartbeatPhase)&&(r.terminalFinalResult=it(a)),r.heartbeatBlocker=a.blocker?String(a.blocker):null,a.boxResourceSnapshot&&typeof a.boxResourceSnapshot=="object"&&!Array.isArray(a.boxResourceSnapshot)&&(r.lastBoxResourceSnapshot=a.boxResourceSnapshot),Array.isArray(a.prEvidence)&&(r.lastPrEvidence=a.prEvidence.filter(u=>!!u&&typeof u=="object"&&typeof u.prUrl=="string"))}return r}var Oo,Ue=k(()=>{"use strict";at();y();Oo=6e4});function Mo(e){return e?(e.split("/").pop()??e).replace(/\.exe$/i,"").toLowerCase():null}function Do(e){if(!e)return[];let r=[],t="",n,o=!1;for(let s of e){if(o){t+=s,o=!1;continue}if(s==="\\"){o=!0;continue}if(n){s===n?n=void 0:t+=s;continue}if(s==='"'||s==="'"){n=s;continue}if(/\s/.test(s)){t&&(r.push(t),t="");continue}t+=s}return t&&r.push(t),r}function To(e){let r=Mo(e[0]);return!!(r&&Io.has(r))}function Sr(e){return!e||e.includes("/")||e.includes("*")?!1:/\.(?:json|md|mjs|cjs|js|ts|tsx|yaml|yml)$/iu.test(e)}function lt(e){if(!e)return!1;let r=e.trim();return r.startsWith("!")&&!r.includes("/")&&!r.endsWith("/**")}function Uo(e){if(!e.startsWith("--glob="))return e;let r=e.slice(7);return r.startsWith("!")&&!r.includes("/")&&!r.endsWith("/**")?`--glob=${r}/**`:e}function Ho(e){let r=[];for(let t=1;t<e.length;t+=1){let n=e[t];if(n){if(n==="--"){r.push(...e.slice(t+1));break}if(n.startsWith("-")){if(n.includes("="))continue;No.has(n)&&(t+=1);continue}r.push(n)}}return r}function Bo(e){let r=!1,t=e.map(o=>{let s=Uo(o);return s!==o&&(r=!0),s}),n=Ho(t);if(n.length===2){let[o,s]=n;if(Sr(s))return{argv:[t[0]??"rg","-g",s,o,"."],changed:!0}}return{argv:t,changed:r}}function _r(e){let r=e.trim();if(!r)return{command:r,changed:!1};let t=r.includes("&&")?" && ":r.includes("||")?" || ":"; ",n=r.split(/\s*(?:&&|\|\||;)\s*/u),o=!1,s=n.map(i=>{let a=Do(i.trim());if(!a.length||!To(a))return i;let u=Bo(a);return u.changed?(o=!0,u.argv.join(" ")):i});return o?{command:s.join(t),changed:!0}:{command:r,changed:!1}}function dt(e){if(!e)return{};let r=e.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(|$)/iu);if(r)return{pattern:r[1],target:r[2]?.trim()};let t=e.match(/^search\s+"(.+)"\s+in\s+(.+)$/iu);return t?{pattern:t[1],target:t[2]?.trim()}:{}}function Fo(e){let{pattern:r,target:t}=dt(e);return r?lt(t)?{kind:"rg_exclude_syntax",pattern:r,target:t}:Sr(t)?{kind:"bad_scope",pattern:r,target:t}:{kind:"not_repo_search",pattern:r,target:t}:{kind:"not_repo_search"}}function Ko(e){let{pattern:r,target:t}=dt(e);if(!r)return null;if(lt(t)){let n=`${t.trim()}/**`;return{command:`rg "${r}" -g '${n}' .`,changed:!0}}return t&&Sr(t)?{command:`rg -g ${t} "${r}" .`,changed:!0}:null}function jo(e){if(e.kind==="bad_scope"&&e.pattern?.includes("agent-os-land-pr")&&e.target==="package.json")return"Search package.json with a glob from the repo root: `rg -g package.json agent-os-land-pr .` \u2014 or run `node scripts/agent-os-land-pr.mjs <pr-url>` directly.";if(e.kind==="bad_scope"&&e.pattern&&e.target)return`Use \`rg -g '${e.target}' ${e.pattern} .\` from the repo root instead of treating ${e.target} as a folder.`;if(e.kind==="rg_exclude_syntax"&&e.pattern){let r=e.target?`${e.target.trim()}/**`:"!node_modules/**";return`Repo search scope \`${e.target??"!node_modules"}\` is not a valid ripgrep path. Use \`rg "${e.pattern}" -g '${r}' .\` from the repo root (exclude globs need a \`/**\` suffix).`}return e.kind==="no_matches"&&e.pattern?`No matches for "${e.pattern}". Try a broader pattern, drop overly short tokens, or search from the repo root with \`rg "${e.pattern}" .\`.`:null}function Lo(e){let r=e.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(agent\)|\s*failed|$)/iu);return r?`search "${r[1]}" in ${r[2]?.trim()}`:null}function xr(e){let r=e.meta?.trim()||(e.command?Lo(e.command):null)||null;if(r){let t=Fo(r),n=jo(t);if(n)return n;let o=Ko(r);if(o?.changed)return`Repo search used an invalid scope. Retry with: \`${o.command}\`.`}if(e.command&&/\b(rg|ripgrep)\b/i.test(e.command)){let t=_r(e.command);if(t.changed)return`Ripgrep scope may be invalid. Retry with: \`${t.command}\`.`;if(e.exitCode===1)return"Ripgrep returned no matches (exit 1). Try a broader pattern or search from the repo root."}return null}var Io,No,pt=k(()=>{"use strict";Io=new Set(["rg","ripgrep","grep"]),No=new Set(["-e","--regexp","-f","--file","-m","--max-count","-A","--after-context","-B","--before-context","-C","--context","-g","--glob","--iglob"])});function ge(e,r=200){let t=e.replace(/\s+/g," ").trim();return t.length>r?`${t.slice(0,r-1)}\u2026`:t}function Vo(e){let r=e.trim();if(!r)return null;if(r.startsWith("{"))try{return JSON.parse(r)}catch{}let t=r.indexOf("{"),n=r.lastIndexOf("}");if(t>=0&&n>t)try{return JSON.parse(r.slice(t,n+1))}catch{return null}return null}function he(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function zo(e){let r=e.metadata;if(!he(r))return null;let t=r.vulnerabilities;if(!he(t))return null;let n=s=>typeof t[s]=="number"?t[s]:0,o={info:n("info"),low:n("low"),moderate:n("moderate"),high:n("high"),critical:n("critical"),total:n("total")};return typeof t.total!="number"&&!o.critical&&!o.high&&!o.moderate&&!o.low&&!o.info?null:o}function Jo(e){let r=[];e.critical&&r.push(`${e.critical} critical`),e.high&&r.push(`${e.high} high`),e.moderate&&r.push(`${e.moderate} moderate`),e.low&&r.push(`${e.low} low`),e.info&&r.push(`${e.info} info`);let t=r.length?r.join(", "):"see report";return`npm audit: ${e.total} vulnerabilit${e.total===1?"y":"ies"} (${t}) \u2014 remediation required`}function qo(e,r){let t=e.error;if(he(t)){let s=typeof t.summary=="string"?t.summary.trim():"",i=typeof t.code=="string"?t.code.trim():"";if(s)return i?`${i}: ${s}`:s;if(i)return i}let n=typeof e.message=="string"?e.message.trim():"";if(n)return n;let o=r.trim();return o?ge(o.split(`
|
|
7
|
+
`).find(Boolean)??o,160):"npm audit failed"}function Xo(e){let r=`${e.stdout}
|
|
8
|
+
${e.stderr}`.trim(),t=Vo(r);if(!t||!he(t)){let o=ge(r||`exit ${e.exitCode}`,180);return{kind:"command_failure",exitCode:e.exitCode,summary:`npm audit failed (invalid or missing JSON): ${o}`,parseError:"invalid_json"}}if(he(t.error))return{kind:"command_failure",exitCode:e.exitCode,summary:`npm audit command failed: ${qo(t,e.stderr)}`};let n=zo(t);return n?e.exitCode===0&&n.total===0?{kind:"success",exitCode:0,summary:"npm audit: no vulnerabilities reported",audit:n}:{kind:"audit_findings",exitCode:e.exitCode,summary:Jo(n),audit:n}:{kind:"command_failure",exitCode:e.exitCode,summary:"npm audit failed: JSON response missing vulnerability metadata",parseError:"missing_metadata"}}function Qo(e){return Go.test(e)}function Zo(e){return $o.test(e)}function es(e){let r=xr({command:e.command,exitCode:e.exitCode});if(e.exitCode===0)return{kind:"success",exitCode:0,summary:"ripgrep finished (exit 0)"};if(e.exitCode===1){let n=(e.stderr||e.interleaved).trim();if(n&&Yo.test(n)){let i=ge(n,160);return{kind:"command_failure",exitCode:1,summary:r??`ripgrep failed (exit 1): ${i}`}}let o=_r(e.command),s=o.changed&&!r?` Retry with: \`${o.command}\`.`:"";return{kind:"search_no_matches",exitCode:1,summary:r??`ripgrep: no matches (exit 1).${s} Try a broader pattern or search from the repo root.`}}let t=ge(e.interleaved||e.stdout||e.stderr||`exit ${e.exitCode}`,180);return{kind:"command_failure",exitCode:e.exitCode,summary:r??`ripgrep failed (exit ${e.exitCode}): ${t}`}}function ft(e){let r=e.stdout??"",t=e.stderr??"",n=e.interleavedOutput??"";if(Qo(e.command)){let i=r.trim()||n.trim()||t.trim();return Xo({exitCode:e.exitCode,stdout:i,stderr:t})}if(Zo(e.command))return es({command:e.command,exitCode:e.exitCode,stdout:r,stderr:t,interleaved:n});let o=xr({command:e.command,exitCode:e.exitCode});if(o&&e.exitCode!==0)return{kind:"command_failure",exitCode:e.exitCode,summary:o};if(e.exitCode===0)return{kind:"success",exitCode:0,summary:"command succeeded (exit 0)"};let s=ge(n||r||t||`exit ${e.exitCode}`,180);return{kind:"command_failure",exitCode:e.exitCode,summary:`command failed (exit ${e.exitCode}): ${s}`}}var Go,$o,Yo,mt=k(()=>{"use strict";pt();Go=/\bnpm\s+audit\b/i,$o=/\b(rg|ripgrep)\b/i,Yo=/\b(error|invalid|unknown|panic|not found)\b/i});import{existsSync as rs,readFileSync as ts}from"node:fs";function ns(e){let r=e.timestamp_ms;return e.timestamp||e.ts||(r?new Date(r).toISOString():void 0)}function os(e){if(!e)return null;for(let r of Object.keys(e))if(r.endsWith("ToolCall")){let t=r.slice(0,-8);return t.length?t:r}return null}function ss(e,r){e.finalResult=r.result||r.subtype||r.terminal_reason||"completed",r.is_error&&(e.error=String(r.result||r.api_error_status||"stream result error"))}function is(e){if(e.type!=="tool_call"||e.subtype!=="completed")return null;let t=(e.tool_call&&typeof e.tool_call=="object"&&!Array.isArray(e.tool_call)?e.tool_call:null)?.shellToolCall;if(!t||typeof t!="object"||Array.isArray(t))return null;let n=t,o=n.args,s=o&&typeof o=="object"&&!Array.isArray(o)&&typeof o.command=="string"?String(o.command):"",i=n.result;if(!i||typeof i!="object"||Array.isArray(i))return null;let a=i.success??i.failure;if(!a||typeof a!="object"||Array.isArray(a))return null;let u=a,c=typeof u.exitCode=="number"?u.exitCode:0;return{command:s,exitCode:c,stdout:typeof u.stdout=="string"?u.stdout:"",stderr:typeof u.stderr=="string"?u.stderr:"",interleaved:typeof u.interleavedOutput=="string"?u.interleavedOutput:""}}function as(e,r){r.kind==="success"||r.kind==="search_no_matches"||(e.lastShellOutcome=r)}function gt(e){let r={firstEventAt:null,lastEventAt:null,currentTool:null,finalResult:null,error:null,lastShellOutcome:null};if(!rs(e))return r;let t=ts(e,"utf8").split(`
|
|
9
|
+
`).filter(Boolean);for(let n of t){let o=Ee(n);if(!o)continue;let s=ns(o);if(s&&(r.firstEventAt||=s,r.lastEventAt=s),o.type==="stream_event"&&o.event&&typeof o.event=="object"&&o.event.type==="content_block_start"){let a=o.event.content_block;a?.type==="tool_use"&&(r.currentTool=String(a.name||"tool"))}if(o.type==="assistant"&&o.message&&typeof o.message=="object"){let a=o.message.content;if(Array.isArray(a)){let u=a.find(c=>c?.type==="tool_use");u&&(r.currentTool=String(u.name||r.currentTool))}}if(o.type==="tool_call"&&o.subtype==="started"){let a=o.tool_call&&typeof o.tool_call=="object"&&!Array.isArray(o.tool_call)?o.tool_call:void 0,u=os(a);u&&(r.currentTool=u)}let i=is(o);i&&as(r,ft({command:i.command,exitCode:i.exitCode,stdout:i.stdout,stderr:i.stderr,interleavedOutput:i.interleaved})),o.type==="result"&&ss(r,o)}return r}var ht=k(()=>{"use strict";mt();y()});function cs(e,r=240){let t=e.replace(/\s+/g," ").trim();return t.length>r?`${t.slice(0,r-1)}\u2026`:t}function kt(e){let r=(e??"").trim();if(!r)return null;for(let t of us)if(t.test.test(r))return{blocked:!0,reason:`${t.label}: ${cs(r)}`};return null}var us,Rt=k(()=>{"use strict";us=[{test:/\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,label:"provider rejected the requested model"},{test:/\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,label:"provider rejected the requested model"},{test:/\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,label:"provider rejected the requested model"},{test:/model preflight failed/i,label:"model/provider preflight failed"},{test:/\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,label:"provider CLI is missing or not on PATH"},{test:/\bfailed to spawn\b/i,label:"provider failed to spawn the worker process"},{test:/\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,label:"provider authentication failed"}]});function yt(e){if(typeof e!="string")return null;let r=e.trim();return r.length?r:null}function ls(e){return e==null?!1:typeof e=="string"?e.trim().length>0:typeof e=="boolean"?e:Array.isArray(e)?e.length>0:typeof e=="object"?Object.keys(e).length>0:!0}function ds(e){return!e?.checked||e.headIsAncestorOfBase!==!1?null:yt(e.head)}function ps(e,r,t){let n=["exited_with_changes_salvage"];if((e==="uncommitted"||e==="both")&&n.push(`${r} uncommitted change${r===1?"":"s"} with no final result`),(e==="committed_ahead"||e==="both")&&t){let o=t.length>12?t.slice(0,12):t;n.push(`commit ${o} ahead of base with no final result`)}return n.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding"),n.join(": ")}function bt(e){if(e.alive||ls(e.finalResult))return null;let r=(e.changedFiles??[]).filter(i=>i.trim()).length,t=yt(e.headCommit)??ds(e.gitAncestry),n=r>0,o=!!t;if(!n&&!o)return{kind:"none",salvageable:!1,uncommittedCount:0,headCommit:null,attentionReason:"process exited without a final result"};let s=n&&o?"both":n?"uncommitted":"committed_ahead";return{kind:s,salvageable:!0,uncommittedCount:r,headCommit:t,attentionReason:ps(s,r,t)}}var St=k(()=>{"use strict"});function ke(e){if(typeof e!="string")return null;let r=e.trim();return r.length?r:null}function fs(e){return e==null?!1:typeof e=="string"?e.trim().length>0:typeof e=="boolean"?e:Array.isArray(e)?e.length>0:typeof e=="object"?Object.keys(e).length>0:!0}function ms(e){if(ke(e.headCommit)||ke(e.prUrl)||ke(e.artifactBundlePath)||ke(e.patchPath))return!0;let r=e.gitAncestry;return!!(r?.checked&&r.headIsAncestorOfBase===!1&&ke(r.head))}function se(e){return fs(e.finalResult)?e.changedFiles.length===0?{blocked:!1}:ms(e)?{blocked:!0,detail:`Worktree has ${e.changedFiles.length} uncommitted change(s); commit or discard before landing`}:{blocked:!0,reason:"dirty_worktree_no_pr",detail:`Worktree has ${e.changedFiles.length} uncommitted change(s) with no commit or PR; commit, open a PR, or discard before landing`}:{blocked:!1}}function _t(e){if(e.blocked)return e.detail??e.reason??"dirty_worktree_no_pr"}var He=k(()=>{"use strict"});function vr(e){let r=e.trim();if(!r.startsWith("{"))return null;try{let t=JSON.parse(r);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{return null}return null}function xt(e){let r=e.targetPrReconciliation??e.target_pr_reconciliation??e.targetPrs??e.target_prs;return Array.isArray(r)?r.length:0}function Be(e){let r=e.trim();if(!r)return null;let t=vr(r);if(t)return t;let n=[],o=/```(?:json)?\s*([\s\S]*?)```/gi,s;for(;(s=o.exec(r))!==null;){let l=vr(s[1]??"");l&&n.push(l)}let i=r.indexOf("{"),a=r.lastIndexOf("}");if(i>=0&&a>i){let l=vr(r.slice(i,a+1));l&&n.push(l)}if(n.length===0)return null;let u=n[n.length-1],c=xt(u);for(let l of n){let p=xt(l);p>c&&(u=l,c=p)}return u}var Cr=k(()=>{"use strict"});function ie(e){if(typeof e!="string")return null;let r=e.trim();return r.length?r:null}function gs(e){return e==null?!1:typeof e=="string"?e.trim().length>0:typeof e=="object"?Object.keys(e).length>0:!0}function z(e){let r=e.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);return r?`https://github.com/${r[1]}/pull/${r[2]}`:ie(e)}function Fe(e){let r=e.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);return r?`${r[1].toLowerCase()}/pull/${r[2]}`:e.trim().toLowerCase()}function vt(e){let r=null;if(typeof e=="string"){let o=Be(e);o&&(r=o)}else e&&typeof e=="object"&&!Array.isArray(e)&&(r=e);if(!r)return[];let t=r.targetPrReconciliation??r.target_pr_reconciliation;if(!Array.isArray(t))return[];let n=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=o,i=z(String(s.prUrl??s.pr_url??"")),a=ie(s.outcome);!i||a!=="merged"&&a!=="skipped"&&a!=="blocked"||n.push({prUrl:i,outcome:a,mergeCommit:ie(s.mergeCommit??s.merge_commit),reason:ie(s.reason)})}return n}function Ct(e,r){let t=[],n=z(ie(e.prUrl)??"");if(n&&t.push(n),r&&typeof r=="object"&&!Array.isArray(r)){let o=r,s=z(String(o.prUrl??o.pr_url??""));s&&t.push(s)}return[...new Set(t)]}function At(e){let{contract:r,snapshot:t}=e,n=e.finalResult??t.finalResult;if(!r.landingOnly&&r.targetPrUrls.length===0&&!r.repairEnforceOriginalPr)return{blocked:!1};if(!gs(n))return(r.requiresTargetPrReconciliation??(r.landingOnly||!!r.repairEnforceOriginalPr||r.targetPrUrls.length>0))&&r.targetPrUrls.length>0?{blocked:!0,reason:"missing_target_pr_reconciliation",detail:`Final result required to reconcile target PR(s): ${r.targetPrUrls.join(", ")}`}:{blocked:!1};if(!(r.requiresTargetPrReconciliation??(r.landingOnly||!!r.repairEnforceOriginalPr||r.targetPrUrls.length>0))&&!r.repairEnforceOriginalPr)return{blocked:!1};let s=r.repairEnforceOriginalPr?z(ie(r.targetPrUrl)??"")??(r.targetPrUrls.length===1?z(r.targetPrUrls[0]):null):null;if(s){let p=Ct(t,n);if(!(n&&typeof n=="object"&&!Array.isArray(n)&&n.supersedesOriginalTargetPr===!0)){for(let R of p)if(R!==s)return{blocked:!0,reason:"duplicate_repair_pr",detail:`Repair worker opened or attached PR ${R} instead of canonical target ${s}`}}let d=vt(n).find(R=>R.prUrl===s);if(!d||d.outcome!=="merged"&&!(d.reason?.trim()&&(d.outcome==="skipped"||d.outcome==="blocked")))return{blocked:!0,reason:"missing_repair_target_reconciliation",detail:`Repair worker must reconcile target PR ${s}`}}let i=vt(n),a=new Map(i.map(p=>[Fe(p.prUrl),p])),u=new Set(r.targetPrUrls.map(p=>Fe(z(p)??p)).filter(Boolean)),c=Ct(t,n);if(r.landingOnly)for(let p of c){if(u.size>0&&!u.has(Fe(p)))return{blocked:!0,reason:"unrelated_implementation_pr",detail:`Landing-only worker attached unrelated PR ${p}`};if(u.size===0)return{blocked:!0,reason:"unrelated_implementation_pr",detail:"Landing-only worker must not open new implementation PRs"}}if(r.targetPrUrls.length===0)return{blocked:!1};let l=[];for(let p of r.targetPrUrls){let f=Fe(z(p)??p),g=a.get(f);if(!g){l.push(f);continue}g.outcome!=="merged"&&!g.reason?.trim()&&l.push(f)}return l.length>0?{blocked:!0,reason:l.every(p=>a.has(p))?"incomplete_target_pr_landing":"missing_target_pr_reconciliation",detail:`Target PR reconciliation incomplete: ${l.join(", ")}`}:{blocked:!1}}function wt(e){if(e.blocked)return e.detail??e.reason}var Et=k(()=>{"use strict";Cr()});function Rs(e){let r=Date.now();if(e.completionBlocker&&!Pt(e.completionBlocker))return{state:"blocked",reason:e.completionBlocker};if(e.finalResult){if(e.localOnly&&bs(e.finalResult))return{state:"done",reason:"local-only worker superseded by merged PR"};let o={finalResult:e.finalResult,changedFiles:e.changedFiles??[],gitAncestry:e.gitAncestry??null,prUrl:e.prUrl??null},s=se(o);if(s.blocked){let i=_t(s);return{state:"needs_attention",reason:s.reason?`landing blocked (${s.reason}): ${i}`:`landing blocked: ${i}`}}if(e.landingContract){let i=At({contract:e.landingContract,snapshot:o,finalResult:e.finalResult}),a=wt(i);if(a)return{state:"needs_attention",reason:i.reason?`landing contract (${i.reason}): ${a}`:`landing contract: ${a}`}}return{state:"done",reason:"final result recorded"}}if(!e.alive){if(ys(e))return{state:"done",reason:"empty abandoned worker record"};let o=kt(e.error);if(o)return{state:"blocked",reason:o.reason};let s=bt({alive:!1,finalResult:null,changedFiles:e.changedFiles,gitAncestry:e.gitAncestry});if(s?.salvageable){let a=e.error?.trim();return{state:"needs_attention",reason:a?`${s.attentionReason} (${a})`:s.attentionReason}}let i=e.error?.trim();return{state:"needs_attention",reason:i?`process exited without a final result: ${i}`:s?.attentionReason??"process exited without a final result"}}if(e.heartbeatBlocker)return{state:"blocked",reason:`worker heartbeat reported blocker: ${e.heartbeatBlocker}`};let t=e.startedAt?Date.parse(e.startedAt):NaN;if(!e.firstEventAt&&e.stdoutBytes===0&&e.heartbeatBytes===0&&Number.isFinite(t)&&r-t>hs)return{state:"needs_attention",reason:`no first stream event ${lr(t)}s after start`};let n=e.lastActivityAt?Date.parse(e.lastActivityAt):NaN;return Number.isFinite(n)&&r-n>ks?{state:"stale",reason:`no log/event/heartbeat activity for ${lr(n)}s`}:{state:"ok",reason:"recent activity"}}function Pt(e){let r=e?.trim();return r?/completion acknowledged but board not advanced/i.test(r)&&/task already terminal/i.test(r):!1}function ys(e){return e.finalResult||e.taskId||e.agentOsId||e.stdoutBytes>0||(e.stderrBytes??0)>0||e.heartbeatBytes>0||e.error?.trim()||(e.changedFiles??[]).some(r=>r.trim())?!1:/empty worker dir|marked abandoned/i.test(e.reconcileReason??"")}function bs(e){let r=null;if(typeof e=="string"?r=Be(e):e&&typeof e=="object"&&!Array.isArray(e)&&(r=e),!r)return!1;let t=r.targetPrReconciliation??r.target_pr_reconciliation;return Array.isArray(t)?t.some(n=>!n||typeof n!="object"||Array.isArray(n)?!1:String(n.outcome??"").trim()==="merged"):!1}function Ss(e,r,t){let n=e.completionSnapshot?.finalResult;return e.completionAckSource==="local-pr-merged-reconcile"&&n!==void 0&&n!==null?n:r||(n??ct(t))}function Re(e,r={}){let t=gt(e.stdoutPath),n=oe(e.heartbeatPath),o=typeof e.completionReportedAt=="string"&&e.completionReportedAt.trim().length>0,s=Ss(e,t.finalResult,n),i=o?!1:L(e.pid),a=Pe(e.stdoutPath),u=Pe(e.stderrPath),c=Pe(e.heartbeatPath),l=de(e.worktreePath),p=Oe(e.worktreePath,{base:r.base,baseCommit:r.baseCommit}),f=zr([t.lastEventAt,n.lastHeartbeatAt,We(e.stdoutPath),We(e.stderrPath),We(e.heartbeatPath)]),g=t.error||!i&&!s&&Vr(e.stderrPath,10).trim()||void 0,d=typeof e.completionBlocker=="string"&&e.completionBlocker.trim()?e.completionBlocker.trim():null,R=Pt(d)?null:d,x=e.repairTargetPrUrl?{landingOnly:!1,targetPrUrls:[e.repairTargetPrUrl],targetPrUrl:e.repairTargetPrUrl,repairEnforceOriginalPr:!0}:null,W=Rs({alive:i,finalResult:s,firstEventAt:t.firstEventAt,stdoutBytes:a,stderrBytes:u,heartbeatBytes:c,lastActivityAt:f,heartbeatBlocker:n.heartbeatBlocker,startedAt:e.startedAt,error:g,changedFiles:l,gitAncestry:p,completionBlocker:R,landingContract:x,prUrl:e.repairTargetPrUrl??e.taskPrUrl??null,localOnly:e.localOnly===!0,taskId:e.taskId??null,agentOsId:e.agentOsId??null,reconcileReason:e.reconcileReason??null}),U=R||W.state==="blocked"?"blocked":o||W.state==="done"?"done":s?"exited":i?"running":"exited";return{runId:e.runId,worker:e.name,pid:e.pid,alive:i,status:U,attention:W,branch:e.branch,worktreePath:e.worktreePath,ownedPaths:e.ownedPaths,stdoutBytes:a,stderrBytes:u,heartbeatBytes:c,firstEventAt:t.firstEventAt,lastEventAt:t.lastEventAt,lastActivityAt:f,currentTool:o?null:t.currentTool,heartbeatCount:n.heartbeatCount,lastHeartbeatAt:n.lastHeartbeatAt,lastHeartbeatPhase:n.lastHeartbeatPhase,lastHeartbeatSummary:n.lastHeartbeatSummary,heartbeatBlocker:n.heartbeatBlocker,timestampAnomalies:n.timestampAnomalies,finalResult:s,error:g,changedFiles:l,gitAncestry:p,instructionPolicyFingerprint:e.instructionPolicyFingerprint??null,instructionPolicyEvidence:e.instructionPolicyEvidence??null,model:e.model??e.orchestrationAudit?.model??null,provider:e.orchestrationAudit?.provider??null,boxKind:e.boxKind??null,boxId:e.boxId??null,runtimeId:e.runtimeId??null,personaSlug:e.personaSlug??null,dispatched:e.dispatched??null,localOnly:e.localOnly??null}}function J(e){return!!(e.finalResult||e.alive===!1||e.status==="exited"||e.status==="done")}function Wt(e){return e.finalResult?e.attention.state==="needs_attention"||e.attention.state==="blocked":!1}var hs,ks,q=k(()=>{"use strict";Ue();ht();Rt();St();H();He();Et();Cr();y();hs=18e4,ks=6e5});var Ot=k(()=>{"use strict";q()});var _s,xs,It=k(()=>{"use strict";et();je();fr();Ar();Me();V();yr();Ot();y();_s=500*1024*1024,xs=4*1024*1024*1024});var Ar=k(()=>{"use strict";It()});import{existsSync as ws,mkdirSync as wu,readFileSync as Es,writeFileSync as Eu}from"node:fs";import{homedir as Ps,totalmem as Wu}from"node:os";import wr from"node:path";function Ke(){if(!ws(Nt))return{};try{return JSON.parse(Es(Nt,"utf8"))}catch{return{}}}var Mt,Nt,Hu,Bu,Fu,je=k(()=>{"use strict";pr();pe();y();fr();Ar();Me();Mt=wr.join(Ps(),".kynver"),Nt=wr.join(Mt,"config.json"),Hu=wr.join(Mt,"credentials");Bu=500*1024*1024,Fu=4*1024*1024*1024});import{existsSync as Dt}from"node:fs";import{homedir as Ut}from"node:os";import G from"node:path";function A(e){let r=G.resolve(Ie(e.trim()));for(;Ws.has(G.basename(r));)r=G.dirname(r);return r}function $(){let e=process.env.KYNVER_HARNESS_ROOT||process.env.OPUS_HARNESS_ROOT;if(e)return A(e);let r=Ke().harnessRoot?.trim();if(r)return A(r);let t=G.join(Ut(),".kynver","harness");return Dt(t)?t:Dt(Tt)?Tt:t}function B(e){return G.join(A(e),"runs")}function ae(e){return G.join(A(e),"worktrees")}function ot(){let e=$();return{harnessRoot:e,runsDir:B(e),worktreesDir:ae(e)}}function kr(e,r){return G.join(e,b(r))}var Tt,Ws,F=k(()=>{"use strict";je();pe();y();Tt=G.join(Ut(),".openclaw","harness"),Ws=new Set(["runs","worktrees"])});F();import P from"node:path";He();import Ye from"node:path";H();q();var Er=[".next",".turbo","dist","build",".cache","node_modules/.cache"];function Ht(e){let r=e.replace(/\\/g,"/").replace(/\/+$/,"");if(r==="node_modules"||r.startsWith("node_modules/"))return!0;for(let t of Er)if(r===t||r.startsWith(`${t}/`))return!0;return!1}function w(e){return e.filter(r=>{let t=r.trim(),n=t.startsWith("??")?t.slice(2).trim():t.length>3?t.slice(3).trim():t;return!Ht(n)})}function X(e){if(typeof e=="string")return e.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i)?.[0]??null;if(e&&typeof e=="object"){let r=e;for(let t of["prUrl","pr_url","pullRequestUrl"]){let n=r[t];if(typeof n=="string"&&n.trim())return n.trim()}}return null}function ye(e){let r=e.gitAncestry?.relation;return r==="merged"||r==="synced"?w(e.changedFiles).length>0:!!(X(e.finalResult)||r==="ahead"||r==="diverged"||e.changedFiles.length>0&&e.finalResult)}y();function be(e){return e.status||(e.status=Re(e.worker,{base:e.run.base,baseCommit:e.run.baseCommit})),e.status}function Pr(e,r){if(e.status)return w(e.status.changedFiles).length>0;let t=r?r.porcelain(e.worktreePath):de(e.worktreePath);return w(t).length>0}function Os(e){let r=e.worker.completionSnapshot?.finalResult;return r??(e.worker.taskPrUrl?{prUrl:e.worker.taskPrUrl}:null)}function Wr(e,r){if(e.status)return e.status;let t=e.worker,n=typeof t.completionReportedAt=="string"&&t.completionReportedAt.trim().length>0,o=!!(t.status&&["done","exited","blocked","failed","abandoned"].includes(t.status))||n,s=Os(e);if(o&&!L(t.pid)){let i=r?.gitStatusCache?r.gitStatusCache.porcelain(e.worktreePath):de(e.worktreePath),a=e.run.baseCommit?.trim()||e.run.base?.trim()||"origin/main",c=r?.gitRevCache?.countAheadOfMain(e.worktreePath,a)===0?{checked:!0,base:a,relation:"synced"}:Oe(e.worktreePath,{base:e.run.base,baseCommit:e.run.baseCommit}),l={runId:e.runId,worker:e.workerName,pid:t.pid,alive:!1,status:t.status??(n?"done":"exited"),attention:{state:n?"done":"stale"},branch:t.branch,worktreePath:e.worktreePath,ownedPaths:t.ownedPaths,stdoutBytes:0,stderrBytes:0,heartbeatBytes:0,firstEventAt:null,lastEventAt:null,lastActivityAt:t.completionReportedAt??null,currentTool:null,heartbeatCount:0,lastHeartbeatAt:null,lastHeartbeatPhase:null,lastHeartbeatSummary:null,heartbeatBlocker:null,changedFiles:i,gitAncestry:c,finalResult:s,completionBlocker:typeof t.completionBlocker=="string"&&t.completionBlocker.trim()||null,prUrl:t.repairTargetPrUrl??t.taskPrUrl??X(s)};return e.status=l,l}return be(e)}q();V();yr();q();y();import Is from"node:path";var Ns=new Set(["running","dispatching","pending","queued","needs_attention"]),Q=new Set(["completed","failed","cancelled","done"]);function Z(e){let r=Rr(e);if(r.length===0)return"failed";let t=!1,n=!1,o=!1,s=!1;for(let i of r){let a=_(Is.join(fe(e.id),"workers",b(i),"worker.json"),void 0);if(!a)continue;let u=Re(a,{base:e.base,baseCommit:e.baseCommit});if(u.alive&&!u.finalResult){t=!0;break}typeof a.completionBlocker=="string"&&a.completionBlocker&&(o=!0),Wt(u)&&(s=!0),u.finalResult&&u.attention.state==="done"&&(n=!0)}return t||o||s?null:n?"completed":"failed"}function Bt(){let e=[];for(let r of gr()){if(!Ns.has(r.status))continue;let t=Z(r);if(!t||t===r.status)continue;let n=r.status;r.status=t,hr(r),e.push({runId:r.id,from:n,to:t})}return e}q();Ue();import{existsSync as Le,mkdirSync as Cc,readdirSync as jt,renameSync as Ac,statSync as Ms}from"node:fs";import ee from"node:path";je();pr();pe();Ue();F();V();F();y();import Y from"node:path";var bc=`${Y.sep}runs${Y.sep}runs${Y.sep}`;function Ft(e){return{workerJsonPath:Y.join(e,"worker.json"),stdoutPath:Y.join(e,"stdout.jsonl"),stderrPath:Y.join(e,"stderr.log"),heartbeatPath:Y.join(e,"heartbeat.jsonl"),lastStatusPath:Y.join(e,"last-status.json")}}y();var Se=900*1e3,Mc=3600*1e3;function Lt(e,r){let t=ee.resolve(e),n=ee.resolve(B(r)),o=ee.relative(n,t);return o!==".."&&!o.startsWith("..")&&!ee.isAbsolute(o)}function Ds(e){if(!Le(e))return[];try{return jt(e,{withFileTypes:!0}).filter(r=>r.isDirectory()&&r.name!=="runs").map(r=>r.name)}catch{return[]}}function Ts(e){let r=ee.join(e,"workers");if(!Le(r))return[];try{return jt(r,{withFileTypes:!0}).filter(t=>t.isDirectory()).map(t=>t.name)}catch{return[]}}function Kt(e,r,t){if(!Le(e))return!1;try{let n=r-Ms(e).mtimeMs;return Number.isFinite(n)&&n>=0&&n<t}catch{return!1}}function Us(e,r=Date.now(),t=Se){if(!Le(e))return!1;let n=Ft(e),o=_(n.workerJsonPath,void 0);if(o?.status==="running"&&L(o.pid))return!0;let s=oe(n.heartbeatPath);if(s.lastHeartbeatAt){let i=r-Date.parse(s.lastHeartbeatAt);if(Number.isFinite(i)&&i>=0&&i<t)return!0}return!!(Kt(n.stdoutPath,r,t)||Kt(n.heartbeatPath,r,t))}function Hs(e,r=Date.now(),t=Se){for(let n of Ts(e))if(Us(ee.join(e,"workers",n),r,t))return!0;return!1}function Gt(e,r=Date.now()){let t=new Set,n=B(e);for(let o of Ds(n)){let s=ee.join(n,o);Hs(s,r)&&t.add(`${e}\0${o}`)}return t}y();function Or(e,r=Date.now(),t=Se){let n=oe(e.heartbeatPath);if(!n.lastHeartbeatAt)return!1;let o=r-Date.parse(n.lastHeartbeatAt);return Number.isFinite(o)&&o>=0&&o<t}function Ge(e,r=Date.now(),t=Se){return L(e.pid)?!0:Or(e,r,t)}function Bs(e,r){return r?r.runTerminalCache.derive(e.run):Z(e.run)}function K(e,r=Date.now()){return Ge(e.worker,r)}function Ir(e,r){return Q.has(e.run.status)?!1:Bs(e,r)!==null}function $t(e,r,t=Date.now()){if(K(e,t))return!0;if(Q.has(e.run.status)||Ir(e,r))return!1;let n=be(e);return J(n)?!1:!!K(e,t)}He();q();function Yt(e,r){if(!(typeof e.worker.completionBlocker=="string"?e.worker.completionBlocker.trim():""))return!1;if(K(e))return!0;let n=r??be(e);return!!(!J(n)||ye(n)||w(n.changedFiles).length>0||se({finalResult:n.finalResult,changedFiles:n.changedFiles,gitAncestry:n.gitAncestry,prUrl:X(n.finalResult)}).blocked)}H();y();import{existsSync as zt,mkdirSync as Fs,writeFileSync as Vt}from"node:fs";import $e from"node:path";function Jt(e,r,t){return $e.join(e,"salvage",b(r),b(t))}function qt(e){return zt($e.join(Jt(e.harnessRoot,e.runId,e.workerName),"evidence.json"))}function Xt(e){let r=Jt(e.harnessRoot,e.indexed.runId,e.indexed.workerName);Fs(r,{recursive:!0});let t=$e.join(r,"salvage.patch"),n=!1;if(zt(e.indexed.worktreePath)){let s=v(e.indexed.worktreePath,["diff","HEAD"]),i=v(e.indexed.worktreePath,["diff","--cached"]),a=[s.stdout,i.stdout].filter(u=>u.trim()).join(`
|
|
10
|
+
`);a.trim()&&(Vt(t,a.endsWith(`
|
|
11
|
+
`)?a:`${a}
|
|
12
|
+
`),n=!0)}let o={capturedAt:new Date(e.now??Date.now()).toISOString(),skipReason:e.skipReason,runId:e.indexed.runId,workerName:e.indexed.workerName,worktreePath:e.indexed.worktreePath,taskId:e.indexed.worker.taskId,agentOsId:e.indexed.worker.agentOsId,branch:e.indexed.worker.branch,completionBlocker:e.indexed.worker.completionBlocker,finalResult:e.status.finalResult,changedFiles:e.status.changedFiles,gitAncestry:e.status.gitAncestry,prUrl:e.status.prUrl??null,...n?{patchPath:t}:{}};return Vt($e.join(r,"evidence.json"),`${JSON.stringify(o,null,2)}
|
|
13
|
+
`),o}var Ks=new Set(["completion_blocked","dirty_worktree","pr_or_unmerged_commits","landing_blocked"]);function js(e){return!Ks.has(e.skipReason)||K(e.indexed,e.now)?!1:qt({harnessRoot:e.harnessRoot,runId:e.indexed.runId,workerName:e.indexed.workerName})?!0:e.writeSalvageEvidence?(Xt({harnessRoot:e.harnessRoot,indexed:e.indexed,skipReason:e.skipReason,status:e.status,now:e.now}),!0):!1}function Ls(e){let{indexed:r,includeOrphans:t,worktreesAgeMs:n,terminalWorktreesAgeMs:o}=e;return r?Q.has(r.run.status)||e.liveness&&Ir(r,e.liveness)||e.liveness&&J(Wr(r,e.liveness))&&!K(r)?o:n:t?o:n}function Gs(e,r){if(e.harnessRoot?.trim())return Ye.resolve(e.harnessRoot);let t=r.worker.workerDir;if(!t)return null;let n=`${Ye.sep}runs${Ye.sep}`,o=t.indexOf(n);return o<0?null:t.slice(0,o+n.length-1)}function Qt(e){let{indexed:r,includeOrphans:t,worktreesAgeMs:n,ageMs:o,orphanSafety:s,worktreeRemovalGuard:i}=e,a=e.now??Date.now();if(!r)return t?s??null:"orphan_without_flag";let u=Ls(e);if(n<=0&&!t&&u<=0)return"worktrees_disabled";if(u>0&&o<u)return"below_age_threshold";if(K(r,a))return"active_worker";let c=Wr(r,e.liveness),l=Gs(e,r),p=d=>l&&js({indexed:r,harnessRoot:l,skipReason:d,status:c,now:a,writeSalvageEvidence:e.writeSalvageEvidence===!0})?null:d;if(Yt(r,c)){let d=p("completion_blocked");if(d)return d}if(Pr(r,e.liveness?.gitStatusCache)){let d=p("dirty_worktree");if(d)return d}let f=e.liveness?.gitRevCache?.countAheadOfMain(e.worktreePath);if(f!=null&&f>0){let d=p("pr_or_unmerged_commits");if(d)return d}if($t(r,e.liveness,a)||!J(c))return"run_still_active";if(ye(c)){let d=p("pr_or_unmerged_commits");if(d)return d}if(w(c.changedFiles).length>0){let d=p("dirty_worktree");if(d)return d}if(se({finalResult:c.finalResult,changedFiles:c.changedFiles,gitAncestry:c.gitAncestry,prUrl:X(c.finalResult)}).blocked){let d=p("landing_blocked");if(d)return d}if(i&&e.worktreePath){let d=i({worktreePath:e.worktreePath,indexed:!!r,runId:r?.runId,worker:r?.workerName});if(d)return d.detail?{reason:d.reason,detail:d.detail}:d.reason}return null}function Nr(e){let{indexed:r,nodeModulesAgeMs:t,ageMs:n,worktreePath:o,activeWorktreePaths:s,diskPressure:i}=e;return!i&&n<t?"below_age_threshold":s.has(Ye.resolve(o))||r&&K(r)?"active_worker":r&&Pr(r,e.gitStatusCache)?"dirty_worktree":null}function Zt(e){return Nr(e)}var Mr=216e5,Ve=6048e5;var Dr=36e5,ze=120;var en=new Set(["active_worker","run_still_active","completion_blocked","landing_blocked"]);function rn(e,r){let t=[],n=new Set,o=(s,i,a)=>{let u=`${s}\0${i}`;n.has(u)||t.length>=24||(n.add(u),t.push({path:s,reason:i,...a?{detail:a}:{}}))};for(let s of r)en.has(s.reason)&&o(s.path,s.reason,s.detail);for(let s of e)!s.skipped||!s.skipReason||en.has(s.skipReason)&&o(s.path,s.skipReason);return t}import{existsSync as sn,readdirSync as an,statSync as Vs}from"node:fs";import un from"node:path";V();F();import tn from"node:path";y();function Ys(e,r){return Ge(e,r)}function nn(e,r=Date.now()){let t=new Set,n=new Set;for(let o of e){for(let s of Te(o)){let i=!1;for(let a of Object.keys(s.workers||{})){let u=_(tn.join(me(o,s.id),"workers",b(a),"worker.json"),void 0);if(!u?.worktreePath)continue;let c=tn.resolve(u.worktreePath);Ys(u,r)&&(i=!0,t.add(c))}i&&n.add(`${o}\0${s.id}`)}for(let s of Gt(o))n.add(s)}return{activeWorktreePaths:t,liveRunKeys:n}}function on(e,r,t,n){return t?n.has(`${r}\0${t}`):!1}y();function zs(e,r){try{let t=Vs(e).mtimeMs;return Math.max(0,r-t)}catch{return 0}}function Js(e,r){let t=un.join(e,"runs",r,"run.json");return sn(t)?_(t,null):null}function cn(e){try{return an(e).length===0}catch{return!1}}function ln(e){let{harnessRoot:r,runId:t,runPath:n,ageMs:o,runDirectoriesAgeMs:s,activeGuards:i}=e;if(on(n,r,t,i.liveRunKeys)||!cn(n))return"run_still_active";let a=Js(r,t);return a&&!Q.has(a.status)&&!Z(a)?"run_still_active":s>0&&o<s?"below_age_threshold":null}function dn(e){if(!sn(e.worktreesDir))return[];let r=[],t;try{t=an(e.worktreesDir,{withFileTypes:!0})}catch{return[]}for(let n of t){if(!n.isDirectory())continue;let o=n.name;if(e.runIdFilter&&o!==e.runIdFilter)continue;let s=un.join(e.worktreesDir,o);cn(s)&&r.push({kind:"remove_run_directory",path:s,bytes:null,harnessRoot:e.harnessRoot,runId:o,ageMs:zs(s,e.now)})}return r}H();import{existsSync as Ur,rmSync as _n}from"node:fs";import{execFileSync as qs}from"node:child_process";import{existsSync as pn,readdirSync as Xs,statSync as Qs}from"node:fs";import Zs from"node:path";var ei=2500;function ri(e,r=ei){if(!pn(e))return 0;try{let n=qs("du",["-sb",e],{encoding:"utf8",timeout:r,stdio:["ignore","pipe","ignore"]}).trim().split(/\s+/)[0],o=Number(n);return Number.isFinite(o)&&o>=0?o:null}catch{return null}}function j(e,r=5e4){if(!pn(e))return 0;let t=ri(e);if(t!==null)return t;let n=0,o=0,s=[e];for(;s.length>0;){let i=s.pop(),a;try{a=Xs(i)}catch{continue}for(let u of a){if(o++>r)return null;let c=Zs.join(i,u),l;try{l=Qs(c)}catch{continue}l.isDirectory()?s.push(c):n+=l.size}}return n}import{existsSync as si,rmSync as ii}from"node:fs";F();import{lstatSync as ti,readdirSync as ni}from"node:fs";function fn(e){try{let r=ti(e),t=typeof process.getuid=="function"?process.getuid():null,n=typeof process.getgid=="function"?process.getgid():null,o=t!==null&&(r.uid!==t||n!==null&&r.gid!==n);return{uid:r.uid,gid:r.gid,foreign:o}}catch{return null}}function mn(e,r=32){let t=fn(e);if(!t)return!1;if(t.foreign)return!0;try{let n=ni(e),o=0;for(let s of n){if(o>=r)break;let i=`${e.replace(/\/$/,"")}/${s}`,a=fn(i);if(o+=1,a?.foreign)return!0}}catch{}return!1}import{spawnSync as oi}from"node:child_process";import kn from"node:path";import E from"node:path";function gn(e,r,t,n){let o=E.resolve(e),s=`${E.sep}${n}`,i=o.endsWith(s)?o:null;if(!i)return"path_outside_harness";let a=E.relative(t,i);if(a.startsWith("..")||E.isAbsolute(a))return"path_outside_harness";let u=a.split(E.sep);return u.length<3||u[u.length-1]!==n||!o.startsWith(E.resolve(r))?"path_outside_harness":null}function Je(e,r,t){return gn(e,r,t,"node_modules")}function qe(e,r,t){return gn(e,r,t,".next")}function Xe(e,r,t){let n=E.resolve(e),o=E.relative(t,n);return o.startsWith("..")||E.isAbsolute(o)||o.split(E.sep).length<3||!n.startsWith(E.resolve(r))?"path_outside_harness":null}function hn(e,r,t){let n=E.resolve(e);return Je(n,r,t)===null||qe(n,r,t)===null||Xe(n,r,t)===null}function yn(){let e=(process.env.KYNVER_CLEANUP_PRIVILEGED??"auto").trim().toLowerCase();return e==="0"||e==="false"||e==="off"||e==="no"?"off":e==="1"||e==="true"||e==="force"||e==="yes"?"force":"auto"}function Rn(e){let r=oi("sudo",["-n",...e],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return{ok:r.status===0,stderr:`${r.stderr??""}${r.stdout??""}`.trim()}}function bn(e,r,t){if(!hn(e,r,t))return{ok:!1,error:"path is not an allowed harness generated cache"};let n=typeof process.getuid=="function"?process.getuid():null,o=typeof process.getgid=="function"?process.getgid():null;if(n===null||o===null)return{ok:!1,error:"privileged reclaim requires POSIX uid/gid"};let s=Rn(["chown","-R",`${n}:${o}`,kn.resolve(e)]);if(s.ok)return{ok:!0,method:"chown_then_rm"};let i=Rn(["rm","-rf",kn.resolve(e)]);return i.ok?{ok:!0,method:"sudo_rm"}:{ok:!1,error:s.stderr||i.stderr||"sudo -n failed (password required or not permitted)"}}var Tr="Root-owned harness caches require operator reclaim: configure passwordless sudo for chown/rm under ~/.kynver/harness/worktrees, or run `node scripts/reclaim-harness-root-owned-cache.mjs --execute` as the harness owner with sudo available. See docs/runbooks/harness-root-owned-cache-reclaim.md.";function ai(e){let r=e?.code;return r==="EACCES"||r==="EPERM"}function Sn(e,r,t={}){if(!si(e.path))return{executed:!1,skipped:!0,skipReason:"missing_worktree"};if(!r)return{executed:!1,skipped:!0,skipReason:"dry_run"};let n=e.harnessRoot,o=n?ae(n):null,i=e.bytes??j(e.path)??void 0,a=t.removePath??ii,u=t.hasForeignOwnedEntry??mn;try{return a(e.path,{recursive:!0,force:!0}),{executed:!0,skipped:!1,bytes:i}}catch(c){if(!ai(c)||!n||!o)return{executed:!1,skipped:!0,skipReason:"remove_failed",error:c.message};let l=u(e.path),p=yn();if(!(p==="force"||p==="auto"&&l))return l?{executed:!1,skipped:!0,skipReason:"foreign_owner",error:`${c.message}; ${Tr}`}:{executed:!1,skipped:!0,skipReason:"remove_failed",error:c.message};let g=bn(e.path,n,o);if(g.ok&&g.method==="sudo_rm")return{executed:!0,skipped:!1,bytes:i,privilegedReclaim:!0};if(g.ok)try{return a(e.path,{recursive:!0,force:!0}),{executed:!0,skipped:!1,bytes:i,privilegedReclaim:!0}}catch(d){return{executed:!1,skipped:!0,skipReason:"foreign_owner",error:`${d.message}; privileged chown succeeded but rm still failed`}}return{executed:!1,skipped:!0,skipReason:"foreign_owner",error:`${c.message}; privileged reclaim failed: ${g.error}; ${Tr}`}}}function Hr(e){let r=e.harnessRoot;return!r||!Lt(e.path,r)?null:{...e,executed:!1,skipped:!0,skipReason:"run_metadata_protected"}}function Br(e,r){let t=Hr(e);if(t)return t;let n=Sn(e,r);return{...e,bytes:n.bytes??e.bytes,executed:n.executed,skipped:n.skipped,skipReason:n.skipReason,error:n.error}}function xn(e,r){return Br(e,r)}function vn(e,r){return Br(e,r)}function Cn(e,r){return Br(e,r)}function An(e,r){let t=Hr(e);if(t)return t;if(!Ur(e.path))return{...e,executed:!1,skipped:!0,skipReason:"missing_worktree"};if(!r)return{...e,executed:!1,skipped:!0,skipReason:"dry_run"};try{let n=e.bytes??j(e.path);return _n(e.path,{recursive:!0,force:!0}),{...e,bytes:n,executed:!0,skipped:!1}}catch(n){return{...e,executed:!1,skipped:!0,skipReason:"remove_failed",error:n.message}}}function wn(e,r){let t=Hr(e);if(t)return t;if(!Ur(e.path))return{...e,executed:!1,skipped:!0,skipReason:"missing_worktree"};if(!r)return{...e,executed:!1,skipped:!0,skipReason:"dry_run"};let n=e.repo;try{let o=e.bytes??j(e.path);return n&&le(n,["worktree","remove","--force",e.path],{allowFailure:!0}),Ur(e.path)&&_n(e.path,{recursive:!0,force:!0}),{...e,bytes:o,executed:!0,skipped:!1}}catch(o){return{...e,executed:!1,skipped:!0,skipReason:"remove_failed",error:o.message}}}import{existsSync as Qe,readdirSync as Ze,statSync as ui}from"node:fs";import D from"node:path";function Fr(e,r){try{let t=ui(e).mtimeMs;return Math.max(0,r-t)}catch{return 0}}function Pn(e,r){let t=D.relative(r,e);return t===""||!t.startsWith("..")&&!D.isAbsolute(t)}function En(e,r,t,n){let o=[];for(let s of Er){if(s===".next")continue;let i=D.join(e,s);if(!Qe(i))continue;let a=D.resolve(i);t.has(a)||Pn(a,r.harnessRoot)&&(t.add(a),o.push({kind:"remove_build_cache",path:a,bytes:null,runId:n.runId,worker:n.worker,repo:n.repo,ageMs:Fr(a,r.now)}))}return o}function Wn(e){let r=[],t=new Set;for(let n of e.index.values())e.runIdFilter&&n.runId!==e.runIdFilter||r.push(...En(n.worktreePath,e,t,{runId:n.runId,worker:n.workerName,repo:n.run.repo}));if(!e.includeOrphans||!Qe(e.worktreesDir))return r;for(let n of Ze(e.worktreesDir,{withFileTypes:!0})){if(!n.isDirectory())continue;let o=D.join(e.worktreesDir,n.name);for(let s of Ze(o,{withFileTypes:!0})){if(!s.isDirectory())continue;let i=D.join(o,s.name);r.push(...En(i,e,t,{runId:n.name,worker:s.name}))}}return r}function On(e){let r=e.worktreesAgeMs>0||e.includeOrphans,t=e.includeOrphans;if(!r&&!t)return[];let n=[],o=new Set;if(r)for(let i of e.index.values()){if(e.runIdFilter&&i.runId!==e.runIdFilter)continue;let a=i.worktreePath;Qe(a)&&(o.has(a)||(o.add(a),n.push({kind:"remove_worktree",path:a,bytes:null,runId:i.runId,worker:i.workerName,repo:i.run.repo,ageMs:Fr(a,e.now)})))}if(!t||!Qe(e.worktreesDir))return n;let s=new Set;for(let i of e.index.values())s.add(D.resolve(i.worktreePath));for(let i of Ze(e.worktreesDir,{withFileTypes:!0})){if(!i.isDirectory()||e.runIdFilter&&i.name!==e.runIdFilter)continue;let a=D.join(e.worktreesDir,i.name),u;try{u=Ze(a,{withFileTypes:!0})}catch{continue}for(let c of u){if(!c.isDirectory())continue;let l=D.resolve(D.join(a,c.name));o.has(l)||s.has(l)||Pn(l,e.harnessRoot)&&(o.add(l),n.push({kind:"remove_worktree",path:l,bytes:null,runId:i.name,worker:c.name,ageMs:Fr(l,e.now)}))}}return n}import{existsSync as Mn,readdirSync as In,statSync as ci}from"node:fs";import ue from"node:path";var li=[{dirName:"node_modules",kind:"remove_node_modules"},{dirName:".next",kind:"remove_next_cache"}];function di(e,r){try{let t=ci(e).mtimeMs;return Math.max(0,r-t)}catch{return 0}}function pi(e,r){let t=ue.relative(r,e);return t===""||!t.startsWith("..")&&!ue.isAbsolute(t)}function fi(e,r,t,n,o,s){if(!Mn(n))return;let i=ue.resolve(n);r.has(i)||pi(i,t.harnessRoot)&&(r.add(i),e.push({kind:o,path:i,bytes:null,harnessRoot:t.harnessRoot,runId:s.runId,worker:s.worker,repo:s.repo,ageMs:di(i,t.now)}))}function Nn(e,r,t,n,o){for(let s of li)fi(e,r,t,ue.join(n,s.dirName),s.kind,o)}function Dn(e){let r=[],t=new Set;for(let n of e.index.values())e.runIdFilter&&n.runId!==e.runIdFilter||Nn(r,t,e,n.worktreePath,{runId:n.runId,worker:n.workerName,repo:n.run.repo});if(!e.includeOrphans||!Mn(e.worktreesDir))return r;for(let n of In(e.worktreesDir,{withFileTypes:!0})){if(!n.isDirectory()||e.runIdFilter&&n.name!==e.runIdFilter)continue;let o=ue.join(e.worktreesDir,n.name),s;try{s=In(o,{withFileTypes:!0})}catch{continue}for(let i of s){if(!i.isDirectory())continue;let a=ue.join(o,i.name);Nn(r,t,e,a,{runId:n.name,worker:i.name})}}return r}H();import{existsSync as Tn,statSync as mi}from"node:fs";import T from"node:path";function gi(e,r){try{let t=mi(e).mtimeMs;return Math.max(0,r-t)}catch{return 0}}function hi(e){let r=[],t=null;for(let n of e.split(`
|
|
14
|
+
`)){if(!n.trim())continue;let[o,...s]=n.split(" "),i=s.join(" ");if(o==="worktree"){t&&r.push(t),t={path:i};continue}t&&(o==="branch"&&(t.branch=i),o==="HEAD"&&(t.head=i),o==="bare"&&(t.bare=!0))}return t&&r.push(t),r}function ki(e,r){let t=T.relative(T.resolve(r),T.resolve(e));return t!==""&&!t.startsWith("..")&&!T.isAbsolute(t)}var Ri=200;function Un(e){if(!e.includeOrphans||!Tn(e.worktreesDir))return[];let r=new Set;for(let s of e.index.values())s.run.repo&&r.add(T.resolve(s.run.repo));let t=new Set;for(let s of e.index.values())t.add(T.resolve(s.worktreePath));let n=[],o=new Set;for(let s of r){let i=0,a;try{a=le(s,["worktree","list","--porcelain"],{allowFailure:!0})}catch{continue}let u=hi(a);for(let c of u){if(i>=Ri)break;let l=T.resolve(c.path);if(l===T.resolve(s)||!ki(l,e.worktreesDir)||t.has(l)||o.has(l)||!Tn(l))continue;let f=T.relative(e.worktreesDir,l).split(T.sep),g=f[0],d=f[1]??"unknown";o.add(l),i+=1,n.push({kind:"remove_worktree",path:l,bytes:null,runId:g,worker:d,repo:s,ageMs:gi(l,e.now)})}}return n}V();y();import _e from"node:path";function Hn(e,r){let t=_e.resolve(r),n=new Map;for(let[o,s]of e){let i=s.harnessRoot?_e.resolve(s.harnessRoot):null;i&&i!==t||n.set(o,s)}return n}function Bn(e){let r=new Map;for(let t of Te(e))for(let n of Object.keys(t.workers||{})){let o=_e.join(me(e,t.id),"workers",b(n),"worker.json"),s=_(o,void 0);s?.worktreePath&&r.set(_e.resolve(s.worktreePath),{harnessRoot:e,worktreePath:_e.resolve(s.worktreePath),runId:t.id,workerName:n,run:t,worker:s})}return r}function xe(e){let r=process.env[e];return r==="1"||r==="true"||r==="yes"}function re(e,r){let t=process.env[e];if(!t)return r;let n=Number(t);return Number.isFinite(n)&&n>=0?n:r}function Fn(e={}){let r=e.execute===!0||e.execute!==!1&&xe("KYNVER_CLEANUP_EXECUTE"),t=e.finalizeStaleRuns!==!1&&!xe("KYNVER_CLEANUP_SKIP_FINALIZE"),n=e.nodeModulesAgeMs??re("KYNVER_CLEANUP_NODE_MODULES_AGE_MS",216e5),o=e.scanDependencyCaches??(e.nodeModulesAgeMs!==void 0||process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS!=null),s=e.worktreesAgeMs??re("KYNVER_CLEANUP_WORKTREES_AGE_MS",0),i=e.terminalWorktreesAgeMs??re("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS",216e5),a=e.runDirectoriesAgeMs??re("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS",36e5),u=e.maxActionsPerSweep??re("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP",120),c=e.includeOrphans===!0||xe("KYNVER_CLEANUP_INCLUDE_ORPHANS"),p=xe("KYNVER_CLEANUP_SCOPE_ALL")||process.env.KYNVER_CLEANUP_SCOPE==="all"?e.runIdFilter:e.runIdFilter??(process.env.KYNVER_CLEANUP_RUN_ID||void 0),f=e.accountBytes!==!1&&!xe("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING"),g=re("KYNVER_CLEANUP_STORAGE_ENTRY_CAP",2e3),d=e.storagePerRunEntryCap!==void 0?e.storagePerRunEntryCap:f&&g>0?g:null,R=re("KYNVER_CLEANUP_BYTE_ENTRY_CAP",2e3),x=e.byteAccountingEntryCap??(Number.isFinite(R)&&R>0?Math.floor(R):2e3);return{execute:r,finalizeStaleRuns:t,scanDependencyCaches:o,nodeModulesAgeMs:n,worktreesAgeMs:s>0?s:0,terminalWorktreesAgeMs:i>=0?i:0,runDirectoriesAgeMs:a>=0?a:0,maxActionsPerSweep:Number.isFinite(u)&&u>0?Math.floor(u):120,includeOrphans:c,runIdFilter:p?String(p):void 0,accountBytes:f,storagePerRunEntryCap:d,byteAccountingEntryCap:x}}H();import{existsSync as Kn}from"node:fs";import Kr from"node:path";y();var bi=1800*1e3;function jn(e){let r=e.now??Date.now(),t=e.heartbeatFreshMs??bi;if(!Kn(e.worktreePath))return null;if(e.runId&&e.workerName){let c=Kr.join(e.harnessRoot,"runs",e.runId,"workers",e.workerName),l=_(Kr.join(c,"worker.json"),void 0);if(l&&Or(l,r,t))return"active_worker"}let n=Kr.join(e.worktreePath,".git");if(!Kn(n))return null;let o=v(e.worktreePath,["status","--porcelain"]);if(o.status!==0)return"pr_or_unmerged_commits";let s=o.stdout.split(`
|
|
15
|
+
`).map(c=>c.trim()).filter(c=>c.length>0);if(w(s).length>0)return"dirty_worktree";let i=v(e.worktreePath,["rev-list","--count","@{u}..HEAD"]);if(i.status===0){let c=Number(i.stdout.trim());if(Number.isFinite(c)&&c>0)return"pr_or_unmerged_commits"}let a=v(e.worktreePath,["rev-list","--count","origin/main..HEAD"]);if(a.status!==0)return i.status!==0?"pr_or_unmerged_commits":null;let u=Number(a.stdout.trim());return Number.isFinite(u)&&u>0?"pr_or_unmerged_commits":null}F();import{existsSync as Si,readdirSync as Ln,statSync as _i}from"node:fs";import xi from"node:path";function Gn(e={}){let r=A(e.harnessRoot??$()),t=ae(r),n=e.now??Date.now(),o=new Date(n).toISOString();if(!Si(t))return{harnessRoot:r,worktreesDir:t,worktreesBytes:0,runCount:0,workerCount:0,oldestRunAt:null,scannedAt:o};let s=0,i=0,a=0,u=null,c;try{c=Ln(t,{withFileTypes:!0})}catch{return{harnessRoot:r,worktreesDir:t,worktreesBytes:null,runCount:0,workerCount:0,oldestRunAt:null,scannedAt:o}}for(let l of c){if(!l.isDirectory())continue;i+=1;let p=xi.join(t,l.name);try{let f=_i(p);u=u===null?f.mtimeMs:Math.min(u,f.mtimeMs)}catch{}try{for(let f of Ln(p,{withFileTypes:!0}))f.isDirectory()&&(a+=1)}catch{}if(s!==null&&e.perRunEntryCap!==null){let f=e.perRunEntryCap??5e4;if(f<=0)s=null;else{let g=j(p,f);g===null?s=null:s+=g}}}return{harnessRoot:r,worktreesDir:t,worktreesBytes:s,runCount:i,workerCount:a,oldestRunAt:u===null?null:new Date(u).toISOString(),scannedAt:o}}F();import{existsSync as vi}from"node:fs";import{homedir as Ci}from"node:os";import $n from"node:path";var Ai=["/var/tmp/kynver-harness",$n.join(Ci(),".openclaw","harness")];function jr(e,r,t){if(!t?.trim())return;let n=A(t.trim());e.has(n)||(e.add(n),r.push(n))}function wi(e){return e.scanWellKnown!=null?e.scanWellKnown:process.env.VITEST==="true"?!1:process.env.KYNVER_CLEANUP_SCAN_WELL_KNOWN!=="0"&&!["0","false","no"].includes((process.env.KYNVER_CLEANUP_SCAN_WELL_KNOWN??"").toLowerCase())}function Yn(e={}){let r=new Set,t=[];jr(r,t,e.harnessRoot??$());let n=process.env.KYNVER_CLEANUP_EXTRA_ROOTS?.split(",").map(o=>o.trim()).filter(Boolean);for(let o of n??[])jr(r,t,o);if(wi(e))for(let o of Ai){let s=$n.resolve(o);!r.has(s)&&vi(s)&&jr(r,t,s)}return t}Me();function Ei(e){let r=process.env[e];return r==="1"||r==="true"||r==="yes"}function Pi(e,r){let t=process.env[e];if(!t)return r;let n=Number(t);return Number.isFinite(n)?n:r}function Vn(e={}){let r=e.diskPath?.trim()||process.env.KYNVER_DISK_GUARD_PATH?.trim()||"/",t=Pi("KYNVER_DISK_GUARD_MAX_USED_PERCENT",75),n=Ne({...e,diskPath:r,diskMaxUsedPercent:e.diskMaxUsedPercent??t}),o=!n.ok||n.usedPercent>=t,s=process.env.KYNVER_CLEANUP_DISK_PRESSURE_FORCE?.trim().toLowerCase();return s==="none"||s==="off"||s==="0"?o=!1:(s==="pressured"||s==="on"||s==="1")&&(o=!0),{diskGate:n,pressured:o,maxUsedPercent:t}}function zn(e,r){if(!r.pressured)return e;let t=e.execute||!Ei("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");return{...e,execute:t,nodeModulesAgeMs:0,runIdFilter:void 0,includeOrphans:!0,terminalWorktreesAgeMs:0,runDirectoriesAgeMs:0,worktreesAgeMs:e.worktreesAgeMs>0?e.worktreesAgeMs:6048e5,diskPressure:!0,diskGate:r.diskGate}}function N(e,r){if(process.env.KYNVER_CLEANUP_QUIET==="1")return;let t=r?`: ${r}`:"";console.error(`[kynver cleanup] ${e}${t}`)}H();var er=class{aheadOfMain=new Map;countAheadOfMain(r,t="origin/main"){let n=`${r}\0${t}`;if(this.aheadOfMain.has(n))return this.aheadOfMain.get(n)??null;let o=v(r,["rev-list","--count",`${t}..HEAD`]);if(o.status!==0)return this.aheadOfMain.set(n,null),null;let s=Number(o.stdout.trim()),i=Number.isFinite(s)?s:null;return this.aheadOfMain.set(n,i),i}};import{spawnSync as Wi}from"node:child_process";import{existsSync as Oi}from"node:fs";import Ii from"node:path";var Ni=5e3;function Mi(e,r){if(!Oi(Ii.join(e,".git")))return{status:null,stdout:"",stderr:"",error:"not_a_git_repo"};try{let t=Wi("git",r,{cwd:e,encoding:"utf8",timeout:Ni}),n=t.error?.message?.includes("ETIMEDOUT")===!0;return{status:n?null:t.status,stdout:t.stdout||"",stderr:t.stderr||"",error:n?"git_timeout":t.error?t.error.message:null}}catch(t){return{status:null,stdout:"",stderr:"",error:t.message}}}function Jn(e){let r=Mi(e,["status","--short"]);return r.error==="not_a_git_repo"?[]:r.error==="git_timeout"||r.status!==0?null:r.stdout.split(`
|
|
16
|
+
`).map(t=>t.trim()).filter(Boolean)}var rr=class{cache=new Map;porcelain(r){let t=r,n=this.cache.get(t);if(n!==void 0)return n;let o=Jn(t)??[];return this.cache.set(t,o),o}};var tr=class{cache=new Map;derive(r){let t=this.cache.get(r.id);if(t!==void 0)return t;let n=Z(r);return this.cache.set(r.id,n),n}};function Di(e){let r={};for(let t of e)r[t.kind]=(r[t.kind]??0)+1;return r}function qn(e,r={}){let t=r.maxSampleActions??12,n=r.maxSampleSkips??8;return{harnessRoot:e.harnessRoot,scanRoots:e.scanRoots,dryRun:e.dryRun,execute:e.execute,scannedAt:e.scannedAt,finalizedRuns:e.finalizedRuns.length,actionCount:e.actions.length,actionKinds:Di(e.actions),totals:e.totals,...e.storage?{storage:e.storage}:{},...e.preservedLivePaths?.length?{preservedLivePaths:e.preservedLivePaths}:{},...e.removedRunDirectories!=null?{removedRunDirectories:e.removedRunDirectories}:{},sampleActions:e.actions.slice(0,t).map(o=>({kind:o.kind,path:o.path,...o.skipReason?{skipReason:o.skipReason}:{},...o.bytes!=null?{bytes:o.bytes}:{},...o.runId?{runId:o.runId}:{},...o.worker?{worker:o.worker}:{}})),sampleSkips:e.skips.slice(0,n)}}function Ti(e={}){let r=e.harnessRoot?A(e.harnessRoot):$(),t=Yn({harnessRoot:r}),n=e.now??Date.now();return{harnessRoot:r,scanRoots:t,now:n}}function Ui(e){return typeof e=="string"?{reason:e}:e}function ce(e,r,t,n){e.push({path:r,reason:t,...n?{detail:n}:{}})}function nr(e,r,t){return!r||e.bytes!=null?e:{...e,bytes:j(e.path,t)}}function Xn(e,r){let t={};for(let n of r)t[n.reason]=(t[n.reason]??0)+1;for(let n of e)n.skipReason&&(t[n.skipReason]=(t[n.skipReason]??0)+1);return t}function Hi(e,r){return e.kind==="remove_next_cache"?vn(e,r):xn(e,r)}function Bi(e,r,t){return e.kind==="remove_next_cache"?qe(e.path,r,t):Je(e.path,r,t)}function Fi(e){let r=new Map;for(let t of e)for(let[n,o]of Bn(t))r.set(n,o);return r}function Qn(e,r){return e.runId&&e.worker?P.join(r,e.runId,e.worker):P.resolve(e.path,"..")}function Ki(e={}){let r=Fn(e),t=Vn();r=zn(r,t);let n=Ti(e);N("scan",`${n.scanRoots.length} harness root(s)`);let o=nn(n.scanRoots,n.now),s=r.finalizeStaleRuns?Bt().map(h=>({runId:h.runId,from:h.from,to:h.to})):[];s.length>0&&N("finalize",`${s.length} stale run(s) marked terminal`),N("index","building worktree index");let i=Fi(n.scanRoots);N("index",`${i.size} indexed worktree(s)`);let a={runTerminalCache:new tr,gitStatusCache:new rr,gitRevCache:new er},u=[],c=[],l=new Set,p=r.maxActionsPerSweep,f=()=>c.length>=p;for(let h of n.scanRoots){if(f())break;N("root",h);let te=P.join(h,"worktrees"),Ae=Hn(i,h),we={harnessRoot:h,worktreesDir:te,nodeModulesAgeMs:r.nodeModulesAgeMs,worktreesAgeMs:r.worktreesAgeMs,includeOrphans:r.includeOrphans,runIdFilter:r.runIdFilter,index:Ae,now:n.now},or=r.scanDependencyCaches?Dn(we):[];N("dependency",r.scanDependencyCaches?`${or.length} cache candidate(s) at ${h}`:"skipped (worktree-only sweep)");let sr=0;for(let O of or){if(f())break;sr+=1,sr%50===0&&N("dependency",`${sr}/${or.length} evaluated`);let S=P.resolve(O.path);if(l.has(S))continue;l.add(S);let m={...O,path:S},C=Bi(m,h,te);if(C){ce(u,m.path,C),c.push({...m,executed:!1,skipped:!0,skipReason:C});continue}let I=Qn(m,te),ne=Ae.get(P.resolve(I))??null,M=Nr({indexed:ne,includeOrphans:!0,nodeModulesAgeMs:r.nodeModulesAgeMs,ageMs:m.ageMs,worktreePath:I,activeWorktreePaths:o.activeWorktreePaths,diskPressure:r.diskPressure,gitStatusCache:a.gitStatusCache});if(M){ce(u,m.path,M),c.push({...m,executed:!1,skipped:!0,skipReason:M});continue}c.push(Hi(nr(m,r.accountBytes,r.byteAccountingEntryCap),r.execute))}if(r.scanDependencyCaches)for(let O of Wn(we)){if(f())break;let S=P.resolve(O.path);if(l.has(S))continue;l.add(S);let m={...O,path:S},C=Xe(m.path,h,te);if(C){ce(u,m.path,C),c.push({...m,executed:!1,skipped:!0,skipReason:C});continue}let I=Qn(m,te),ne=Ae.get(P.resolve(I))??null,M=Zt({indexed:ne,includeOrphans:!0,nodeModulesAgeMs:r.nodeModulesAgeMs,ageMs:m.ageMs,worktreePath:I,activeWorktreePaths:o.activeWorktreePaths,diskPressure:r.diskPressure,gitStatusCache:a.gitStatusCache});if(M){ce(u,m.path,M),c.push({...m,executed:!1,skipped:!0,skipReason:M});continue}c.push(Cn(nr(m,r.accountBytes,r.byteAccountingEntryCap),r.execute))}let ir=[...On(we),...Un(we)];N("worktrees",`${ir.length} candidate(s) at ${h}`);let Lr=new Set,ar=0;for(let O of ir){if(f())break;ar+=1,ar%50===0&&N("worktrees",`${ar}/${ir.length} evaluated`);let S=P.resolve(O.path);if(Lr.has(S))continue;Lr.add(S);let m={...O,path:S},C=Ae.get(P.resolve(m.path))??null,I=C?null:jn({worktreePath:m.path,harnessRoot:h,runId:m.runId,workerName:m.worker,now:n.now}),ne=Qt({indexed:C,worktreePath:P.resolve(m.path),includeOrphans:r.includeOrphans,worktreesAgeMs:r.worktreesAgeMs,terminalWorktreesAgeMs:r.terminalWorktreesAgeMs,ageMs:m.ageMs,orphanSafety:I,worktreeRemovalGuard:e.worktreeRemovalGuard,liveness:a,now:n.now,harnessRoot:h,writeSalvageEvidence:r.execute});if(ne){let{reason:M,detail:eo}=Ui(ne);ce(u,m.path,M,eo),c.push({...m,executed:!1,skipped:!0,skipReason:M});continue}c.push(wn(nr(m,r.accountBytes,r.byteAccountingEntryCap),r.execute))}if(!f()&&r.runDirectoriesAgeMs>=0)for(let O of dn({harnessRoot:h,worktreesDir:te,runDirectoriesAgeMs:r.runDirectoriesAgeMs,runIdFilter:r.runIdFilter,activeGuards:o,now:n.now})){if(f())break;let S=P.resolve(O.path);if(l.has(S))continue;l.add(S);let m={...O,path:S},C=m.runId??P.basename(S),I=ln({harnessRoot:h,runId:C,runPath:S,ageMs:m.ageMs,runDirectoriesAgeMs:r.runDirectoriesAgeMs,activeGuards:o});if(I){ce(u,m.path,I),c.push({...m,executed:!1,skipped:!0,skipReason:I});continue}c.push(An(nr(m,r.accountBytes,r.byteAccountingEntryCap),r.execute))}}let g=0,d=0,R=0,x=0,W=0,U=0;for(let h of c)h.bytes&&(g+=h.bytes),!h.skipped&&!h.executed&&h.bytes&&(R+=h.bytes),h.executed?(W+=1,x+=h.bytes??0,h.kind==="remove_run_directory"&&(d+=1)):h.skipped&&(U+=1,h.skipReason==="dry_run"&&h.bytes&&(R+=h.bytes));let ve=r.accountBytes?Gn({harnessRoot:n.harnessRoot,now:n.now,perRunEntryCap:r.storagePerRunEntryCap}):void 0;N("complete",`${c.length} action(s), ${U} skipped, ${W} removed`);let Ce=rn(c,u),Zn=qn({harnessRoot:n.harnessRoot,scanRoots:n.scanRoots,dryRun:!r.execute,execute:r.execute,nodeModulesAgeMs:r.nodeModulesAgeMs,worktreesAgeMs:r.worktreesAgeMs,includeOrphans:r.includeOrphans,diskPressure:r.diskPressure,diskGate:r.diskGate?{ok:r.diskGate.ok,path:r.diskGate.path,freeBytes:r.diskGate.freeBytes,usedPercent:r.diskGate.usedPercent,reason:r.diskGate.reason}:void 0,scannedAt:new Date(n.now).toISOString(),finalizedRuns:s,actions:c,skips:u,totals:{candidateBytes:g,reclaimableBytes:R,removedBytes:x,removedPaths:W,skippedPaths:U,skipReasons:Xn(c,u)},...ve?{storage:ve}:{},...Ce.length>0?{preservedLivePaths:Ce}:{},...d>0?{removedRunDirectories:d}:{}});return{harnessRoot:n.harnessRoot,scanRoots:n.scanRoots,dryRun:!r.execute,execute:r.execute,nodeModulesAgeMs:r.nodeModulesAgeMs,worktreesAgeMs:r.worktreesAgeMs,includeOrphans:r.includeOrphans,diskPressure:r.diskPressure,diskGate:r.diskGate?{ok:r.diskGate.ok,path:r.diskGate.path,freeBytes:r.diskGate.freeBytes,usedPercent:r.diskGate.usedPercent,reason:r.diskGate.reason}:void 0,scannedAt:new Date(n.now).toISOString(),finalizedRuns:s,actions:c,skips:u,totals:{candidateBytes:g,reclaimableBytes:R,removedBytes:x,removedPaths:W,skippedPaths:U,skipReasons:Xn(c,u)},...ve?{storage:ve}:{},...Ce.length>0?{preservedLivePaths:Ce}:{},...d>0?{removedRunDirectories:d}:{},compactSummary:Zn}}export{ze as DEFAULT_MAX_ACTIONS_PER_SWEEP,Mr as DEFAULT_NODE_MODULES_AGE_MS,Dr as DEFAULT_RUN_DIRECTORIES_AGE_MS,Ve as DEFAULT_WORKTREES_AGE_MS,Ki as runHarnessCleanup};
|