@kynver-app/runtime 0.1.103 → 0.1.105

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