@kynver-app/runtime 0.1.103 → 0.1.106

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 (47) 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 +171 -62
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +172 -63
  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/dist/worker-persona-catalog.js +2 -2
  46. package/dist/worker-persona-catalog.js.map +2 -2
  47. package/package.json +63 -3
@@ -0,0 +1,1577 @@
1
+ // src/heartbeat.ts
2
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3
+
4
+ // src/heartbeat-final-result.ts
5
+ function tryParseJsonObject(text) {
6
+ const trimmed = text.trim();
7
+ if (!trimmed.startsWith("{")) return null;
8
+ try {
9
+ const parsed = JSON.parse(trimmed);
10
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11
+ return parsed;
12
+ }
13
+ } catch {
14
+ return null;
15
+ }
16
+ return null;
17
+ }
18
+ function embeddedRecordFromProse(text) {
19
+ const trimmed = text.trim();
20
+ if (!trimmed) return null;
21
+ const direct = tryParseJsonObject(trimmed);
22
+ if (direct) return direct;
23
+ const candidates = [];
24
+ const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
25
+ let fenceMatch;
26
+ while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
27
+ const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
28
+ if (fromFence) candidates.push(fromFence);
29
+ }
30
+ const firstBrace = trimmed.indexOf("{");
31
+ const lastBrace = trimmed.lastIndexOf("}");
32
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
33
+ const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
34
+ if (slice) candidates.push(slice);
35
+ }
36
+ return candidates.length > 0 ? candidates[candidates.length - 1] : null;
37
+ }
38
+ function terminalFinalResultFromHeartbeatRow(row) {
39
+ const explicit = row.finalResult ?? row.final_result;
40
+ if (explicit !== void 0 && explicit !== null) {
41
+ if (typeof explicit === "string") {
42
+ const embedded2 = embeddedRecordFromProse(explicit);
43
+ return embedded2 ?? (explicit.trim() || null);
44
+ }
45
+ return explicit;
46
+ }
47
+ const summary = typeof row.summary === "string" ? row.summary.trim() : "";
48
+ if (!summary) return null;
49
+ const embedded = embeddedRecordFromProse(summary);
50
+ if (embedded) return embedded;
51
+ return summary;
52
+ }
53
+
54
+ // src/util.ts
55
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
56
+ function fail(message) {
57
+ console.error(message);
58
+ process.exit(1);
59
+ }
60
+ function safeJson(line) {
61
+ try {
62
+ return JSON.parse(line);
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ function fileSize(file) {
68
+ try {
69
+ return statSync(file).size;
70
+ } catch {
71
+ return 0;
72
+ }
73
+ }
74
+ function fileMtime(file) {
75
+ try {
76
+ return statSync(file).mtime.toISOString();
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function tailFile(file, lines) {
82
+ if (!existsSync(file)) return "";
83
+ const data = readFileSync(file, "utf8");
84
+ return data.split("\n").slice(-lines).join("\n");
85
+ }
86
+ function isPidAlive(pid) {
87
+ if (!pid) return false;
88
+ try {
89
+ process.kill(pid, 0);
90
+ return true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function latestIso(values) {
96
+ let best = null;
97
+ let bestMs = -Infinity;
98
+ for (const value of values) {
99
+ if (!value) continue;
100
+ const ms = Date.parse(value);
101
+ if (Number.isFinite(ms) && ms > bestMs) {
102
+ bestMs = ms;
103
+ best = value;
104
+ }
105
+ }
106
+ return best;
107
+ }
108
+ function secsAgo(ms) {
109
+ return Math.max(0, Math.round((Date.now() - ms) / 1e3));
110
+ }
111
+
112
+ // src/heartbeat.ts
113
+ var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
114
+ function isTerminalHeartbeatPhase(phase) {
115
+ return phase === "complete";
116
+ }
117
+ function terminalFinalResultFromHeartbeat(heartbeat) {
118
+ if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
119
+ if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
120
+ return heartbeat.terminalFinalResult;
121
+ }
122
+ const summary = heartbeat.lastHeartbeatSummary?.trim();
123
+ return summary || "completed";
124
+ }
125
+ function parseHeartbeat(file) {
126
+ const result = {
127
+ heartbeatCount: 0,
128
+ lastHeartbeatAt: null,
129
+ lastHeartbeatPhase: null,
130
+ lastHeartbeatSummary: null,
131
+ terminalFinalResult: null,
132
+ heartbeatBlocker: null,
133
+ timestampAnomalies: [],
134
+ lastBoxResourceSnapshot: null,
135
+ lastPrEvidence: []
136
+ };
137
+ if (!existsSync2(file)) return result;
138
+ const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
139
+ const clampedTo = new Date(maxFutureMs).toISOString();
140
+ const lines = readFileSync2(file, "utf8").split("\n").filter(Boolean);
141
+ for (const line of lines) {
142
+ const entry = safeJson(line);
143
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
144
+ const row = entry;
145
+ result.heartbeatCount++;
146
+ if (row.ts) {
147
+ const ts = String(row.ts);
148
+ const tsMs = Date.parse(ts);
149
+ if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
150
+ result.timestampAnomalies.push({
151
+ kind: "future_heartbeat_timestamp",
152
+ observedAt: ts,
153
+ clampedTo
154
+ });
155
+ } else {
156
+ result.lastHeartbeatAt = ts;
157
+ }
158
+ }
159
+ if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
160
+ if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
161
+ if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
162
+ result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
163
+ }
164
+ result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
165
+ if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
166
+ result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
167
+ }
168
+ if (Array.isArray(row.prEvidence)) {
169
+ result.lastPrEvidence = row.prEvidence.filter(
170
+ (entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
171
+ );
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+
177
+ // src/stream.ts
178
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
179
+
180
+ // src/repo-search.ts
181
+ var RG_BINARIES = /* @__PURE__ */ new Set(["rg", "ripgrep", "grep"]);
182
+ var RG_OPTS_WITH_VALUE = /* @__PURE__ */ new Set([
183
+ "-e",
184
+ "--regexp",
185
+ "-f",
186
+ "--file",
187
+ "-m",
188
+ "--max-count",
189
+ "-A",
190
+ "--after-context",
191
+ "-B",
192
+ "--before-context",
193
+ "-C",
194
+ "--context",
195
+ "-g",
196
+ "--glob",
197
+ "--iglob"
198
+ ]);
199
+ function binaryName(token) {
200
+ if (!token) return null;
201
+ const base = token.split("/").pop() ?? token;
202
+ return base.replace(/\.exe$/i, "").toLowerCase();
203
+ }
204
+ function splitShellWords(input) {
205
+ if (!input) return [];
206
+ const words = [];
207
+ let current = "";
208
+ let quote;
209
+ let escaped = false;
210
+ for (const char of input) {
211
+ if (escaped) {
212
+ current += char;
213
+ escaped = false;
214
+ continue;
215
+ }
216
+ if (char === "\\") {
217
+ escaped = true;
218
+ continue;
219
+ }
220
+ if (quote) {
221
+ if (char === quote) quote = void 0;
222
+ else current += char;
223
+ continue;
224
+ }
225
+ if (char === '"' || char === "'") {
226
+ quote = char;
227
+ continue;
228
+ }
229
+ if (/\s/.test(char)) {
230
+ if (current) {
231
+ words.push(current);
232
+ current = "";
233
+ }
234
+ continue;
235
+ }
236
+ current += char;
237
+ }
238
+ if (current) words.push(current);
239
+ return words;
240
+ }
241
+ function isRgArgv(argv) {
242
+ const bin = binaryName(argv[0]);
243
+ return Boolean(bin && RG_BINARIES.has(bin));
244
+ }
245
+ function isSingleFileSearchTarget(target) {
246
+ if (!target || target.includes("/") || target.includes("*")) return false;
247
+ return /\.(?:json|md|mjs|cjs|js|ts|tsx|yaml|yml)$/iu.test(target);
248
+ }
249
+ function isRgExcludeScopeTarget(target) {
250
+ if (!target) return false;
251
+ const t = target.trim();
252
+ return t.startsWith("!") && !t.includes("/") && !t.endsWith("/**");
253
+ }
254
+ function fixRgGlobToken(token) {
255
+ if (!token.startsWith("--glob=")) return token;
256
+ const value = token.slice("--glob=".length);
257
+ if (value.startsWith("!") && !value.includes("/") && !value.endsWith("/**")) {
258
+ return `--glob=${value}/**`;
259
+ }
260
+ return token;
261
+ }
262
+ function collectRgPositional(argv) {
263
+ const positional = [];
264
+ for (let i = 1; i < argv.length; i += 1) {
265
+ const token = argv[i];
266
+ if (!token) continue;
267
+ if (token === "--") {
268
+ positional.push(...argv.slice(i + 1));
269
+ break;
270
+ }
271
+ if (token.startsWith("-")) {
272
+ if (token.includes("=")) continue;
273
+ if (RG_OPTS_WITH_VALUE.has(token)) i += 1;
274
+ continue;
275
+ }
276
+ positional.push(token);
277
+ }
278
+ return positional;
279
+ }
280
+ function normalizeRgArgv(argv) {
281
+ let changed = false;
282
+ const out = argv.map((token) => {
283
+ const fixed = fixRgGlobToken(token);
284
+ if (fixed !== token) changed = true;
285
+ return fixed;
286
+ });
287
+ const positional = collectRgPositional(out);
288
+ if (positional.length === 2) {
289
+ const [pattern, target] = positional;
290
+ if (isSingleFileSearchTarget(target)) {
291
+ return { argv: [out[0] ?? "rg", "-g", target, pattern, "."], changed: true };
292
+ }
293
+ }
294
+ return { argv: out, changed };
295
+ }
296
+ function normalizeRepoSearchCommand(command) {
297
+ const trimmed = command.trim();
298
+ if (!trimmed) return { command: trimmed, changed: false };
299
+ const joiner = trimmed.includes("&&") ? " && " : trimmed.includes("||") ? " || " : "; ";
300
+ const stages = trimmed.split(/\s*(?:&&|\|\||;)\s*/u);
301
+ let changed = false;
302
+ const normalizedStages = stages.map((stage) => {
303
+ const argv = splitShellWords(stage.trim());
304
+ if (!argv.length || !isRgArgv(argv)) return stage;
305
+ const normalized = normalizeRgArgv(argv);
306
+ if (normalized.changed) {
307
+ changed = true;
308
+ return normalized.argv.join(" ");
309
+ }
310
+ return stage;
311
+ });
312
+ if (!changed) return { command: trimmed, changed: false };
313
+ return { command: normalizedStages.join(joiner), changed: true };
314
+ }
315
+ function extractSearchMeta(meta) {
316
+ if (!meta) return {};
317
+ const pipelineMatch = meta.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(|$)/iu);
318
+ if (pipelineMatch) {
319
+ return { pattern: pipelineMatch[1], target: pipelineMatch[2]?.trim() };
320
+ }
321
+ const match = meta.match(/^search\s+"(.+)"\s+in\s+(.+)$/iu);
322
+ if (!match) return {};
323
+ return { pattern: match[1], target: match[2]?.trim() };
324
+ }
325
+ function classifyRepoSearchMeta(meta) {
326
+ const { pattern, target } = extractSearchMeta(meta);
327
+ if (!pattern) return { kind: "not_repo_search" };
328
+ if (isRgExcludeScopeTarget(target)) {
329
+ return { kind: "rg_exclude_syntax", pattern, target };
330
+ }
331
+ if (isSingleFileSearchTarget(target)) {
332
+ return { kind: "bad_scope", pattern, target };
333
+ }
334
+ return { kind: "not_repo_search", pattern, target };
335
+ }
336
+ function metaToNormalizedRgCommand(meta) {
337
+ const { pattern, target } = extractSearchMeta(meta);
338
+ if (!pattern) return null;
339
+ if (isRgExcludeScopeTarget(target)) {
340
+ const glob = `${target.trim()}/**`;
341
+ return {
342
+ command: `rg "${pattern}" -g '${glob}' .`,
343
+ changed: true
344
+ };
345
+ }
346
+ if (target && isSingleFileSearchTarget(target)) {
347
+ return {
348
+ command: `rg -g ${target} "${pattern}" .`,
349
+ changed: true
350
+ };
351
+ }
352
+ return null;
353
+ }
354
+ function formatRepoSearchGuidance(ctx) {
355
+ if (ctx.kind === "bad_scope" && ctx.pattern?.includes("agent-os-land-pr") && ctx.target === "package.json") {
356
+ 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.";
357
+ }
358
+ if (ctx.kind === "bad_scope" && ctx.pattern && ctx.target) {
359
+ return `Use \`rg -g '${ctx.target}' ${ctx.pattern} .\` from the repo root instead of treating ${ctx.target} as a folder.`;
360
+ }
361
+ if (ctx.kind === "rg_exclude_syntax" && ctx.pattern) {
362
+ const glob = ctx.target ? `${ctx.target.trim()}/**` : "!node_modules/**";
363
+ 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).`;
364
+ }
365
+ if (ctx.kind === "no_matches" && ctx.pattern) {
366
+ return `No matches for "${ctx.pattern}". Try a broader pattern, drop overly short tokens, or search from the repo root with \`rg "${ctx.pattern}" .\`.`;
367
+ }
368
+ return null;
369
+ }
370
+ function extractSearchMetaFromToolLine(line) {
371
+ const match = line.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(agent\)|\s*failed|$)/iu);
372
+ if (!match) return null;
373
+ return `search "${match[1]}" in ${match[2]?.trim()}`;
374
+ }
375
+ function diagnoseRepoSearchFailure(input) {
376
+ const meta = input.meta?.trim() || (input.command ? extractSearchMetaFromToolLine(input.command) : null) || null;
377
+ if (meta) {
378
+ const ctx = classifyRepoSearchMeta(meta);
379
+ const guidance = formatRepoSearchGuidance(ctx);
380
+ if (guidance) return guidance;
381
+ const normalized = metaToNormalizedRgCommand(meta);
382
+ if (normalized?.changed) {
383
+ return `Repo search used an invalid scope. Retry with: \`${normalized.command}\`.`;
384
+ }
385
+ }
386
+ if (input.command && /\b(rg|ripgrep)\b/i.test(input.command)) {
387
+ const normalized = normalizeRepoSearchCommand(input.command);
388
+ if (normalized.changed) {
389
+ return `Ripgrep scope may be invalid. Retry with: \`${normalized.command}\`.`;
390
+ }
391
+ if (input.exitCode === 1) {
392
+ return "Ripgrep returned no matches (exit 1). Try a broader pattern or search from the repo root.";
393
+ }
394
+ }
395
+ return null;
396
+ }
397
+
398
+ // src/shell-command-outcome.ts
399
+ var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
400
+ var RG_CMD_RE = /\b(rg|ripgrep)\b/i;
401
+ var RG_REAL_ERROR_RE = /\b(error|invalid|unknown|panic|not found)\b/i;
402
+ function tidy(text, max = 200) {
403
+ const one = text.replace(/\s+/g, " ").trim();
404
+ return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
405
+ }
406
+ function extractJsonObject(text) {
407
+ const trimmed = text.trim();
408
+ if (!trimmed) return null;
409
+ if (trimmed.startsWith("{")) {
410
+ try {
411
+ return JSON.parse(trimmed);
412
+ } catch {
413
+ }
414
+ }
415
+ const start = trimmed.indexOf("{");
416
+ const end = trimmed.lastIndexOf("}");
417
+ if (start >= 0 && end > start) {
418
+ try {
419
+ return JSON.parse(trimmed.slice(start, end + 1));
420
+ } catch {
421
+ return null;
422
+ }
423
+ }
424
+ return null;
425
+ }
426
+ function isRecord(value) {
427
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
428
+ }
429
+ function summarizeNpmAuditReport(report) {
430
+ const meta = report.metadata;
431
+ if (!isRecord(meta)) return null;
432
+ const vuln = meta.vulnerabilities;
433
+ if (!isRecord(vuln)) return null;
434
+ const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
435
+ const summary = {
436
+ info: num("info"),
437
+ low: num("low"),
438
+ moderate: num("moderate"),
439
+ high: num("high"),
440
+ critical: num("critical"),
441
+ total: num("total")
442
+ };
443
+ if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
444
+ return null;
445
+ }
446
+ return summary;
447
+ }
448
+ function formatAuditSummaryLine(audit) {
449
+ const parts = [];
450
+ if (audit.critical) parts.push(`${audit.critical} critical`);
451
+ if (audit.high) parts.push(`${audit.high} high`);
452
+ if (audit.moderate) parts.push(`${audit.moderate} moderate`);
453
+ if (audit.low) parts.push(`${audit.low} low`);
454
+ if (audit.info) parts.push(`${audit.info} info`);
455
+ const breakdown = parts.length ? parts.join(", ") : "see report";
456
+ return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
457
+ }
458
+ function npmAuditFailureReason(report, stderr) {
459
+ const err = report.error;
460
+ if (isRecord(err)) {
461
+ const summary = typeof err.summary === "string" ? err.summary.trim() : "";
462
+ const code = typeof err.code === "string" ? err.code.trim() : "";
463
+ if (summary) return code ? `${code}: ${summary}` : summary;
464
+ if (code) return code;
465
+ }
466
+ const detail = typeof report.message === "string" ? report.message.trim() : "";
467
+ if (detail) return detail;
468
+ const errTail = stderr.trim();
469
+ if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
470
+ return "npm audit failed";
471
+ }
472
+ function classifyNpmAuditOutcome(input) {
473
+ const combined = `${input.stdout}
474
+ ${input.stderr}`.trim();
475
+ const parsed = extractJsonObject(combined);
476
+ if (!parsed || !isRecord(parsed)) {
477
+ const tail = tidy(combined || `exit ${input.exitCode}`, 180);
478
+ return {
479
+ kind: "command_failure",
480
+ exitCode: input.exitCode,
481
+ summary: `npm audit failed (invalid or missing JSON): ${tail}`,
482
+ parseError: "invalid_json"
483
+ };
484
+ }
485
+ if (isRecord(parsed.error)) {
486
+ return {
487
+ kind: "command_failure",
488
+ exitCode: input.exitCode,
489
+ summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
490
+ };
491
+ }
492
+ const audit = summarizeNpmAuditReport(parsed);
493
+ if (!audit) {
494
+ return {
495
+ kind: "command_failure",
496
+ exitCode: input.exitCode,
497
+ summary: "npm audit failed: JSON response missing vulnerability metadata",
498
+ parseError: "missing_metadata"
499
+ };
500
+ }
501
+ if (input.exitCode === 0 && audit.total === 0) {
502
+ return {
503
+ kind: "success",
504
+ exitCode: 0,
505
+ summary: "npm audit: no vulnerabilities reported",
506
+ audit
507
+ };
508
+ }
509
+ return {
510
+ kind: "audit_findings",
511
+ exitCode: input.exitCode,
512
+ summary: formatAuditSummaryLine(audit),
513
+ audit
514
+ };
515
+ }
516
+ function isNpmAuditCommand(command) {
517
+ return NPM_AUDIT_RE.test(command);
518
+ }
519
+ function isRgCommand(command) {
520
+ return RG_CMD_RE.test(command);
521
+ }
522
+ function classifyRgOutcome(input) {
523
+ const diagnosis = diagnoseRepoSearchFailure({
524
+ command: input.command,
525
+ exitCode: input.exitCode
526
+ });
527
+ if (input.exitCode === 0) {
528
+ return {
529
+ kind: "success",
530
+ exitCode: 0,
531
+ summary: "ripgrep finished (exit 0)"
532
+ };
533
+ }
534
+ if (input.exitCode === 1) {
535
+ const errText = (input.stderr || input.interleaved).trim();
536
+ if (errText && RG_REAL_ERROR_RE.test(errText)) {
537
+ const tail2 = tidy(errText, 160);
538
+ return {
539
+ kind: "command_failure",
540
+ exitCode: 1,
541
+ summary: diagnosis ?? `ripgrep failed (exit 1): ${tail2}`
542
+ };
543
+ }
544
+ const normalized = normalizeRepoSearchCommand(input.command);
545
+ const retry = normalized.changed && !diagnosis ? ` Retry with: \`${normalized.command}\`.` : "";
546
+ return {
547
+ kind: "search_no_matches",
548
+ exitCode: 1,
549
+ summary: diagnosis ?? `ripgrep: no matches (exit 1).${retry} Try a broader pattern or search from the repo root.`
550
+ };
551
+ }
552
+ const tail = tidy(input.interleaved || input.stdout || input.stderr || `exit ${input.exitCode}`, 180);
553
+ return {
554
+ kind: "command_failure",
555
+ exitCode: input.exitCode,
556
+ summary: diagnosis ?? `ripgrep failed (exit ${input.exitCode}): ${tail}`
557
+ };
558
+ }
559
+ function classifyShellCommandOutcome(input) {
560
+ const stdout = input.stdout ?? "";
561
+ const stderr = input.stderr ?? "";
562
+ const interleaved = input.interleavedOutput ?? "";
563
+ if (isNpmAuditCommand(input.command)) {
564
+ const body = stdout.trim() || interleaved.trim() || stderr.trim();
565
+ return classifyNpmAuditOutcome({
566
+ exitCode: input.exitCode,
567
+ stdout: body,
568
+ stderr
569
+ });
570
+ }
571
+ if (isRgCommand(input.command)) {
572
+ return classifyRgOutcome({
573
+ command: input.command,
574
+ exitCode: input.exitCode,
575
+ stdout,
576
+ stderr,
577
+ interleaved
578
+ });
579
+ }
580
+ const searchDiagnosis = diagnoseRepoSearchFailure({
581
+ command: input.command,
582
+ exitCode: input.exitCode
583
+ });
584
+ if (searchDiagnosis && input.exitCode !== 0) {
585
+ return {
586
+ kind: "command_failure",
587
+ exitCode: input.exitCode,
588
+ summary: searchDiagnosis
589
+ };
590
+ }
591
+ if (input.exitCode === 0) {
592
+ return {
593
+ kind: "success",
594
+ exitCode: 0,
595
+ summary: `command succeeded (exit 0)`
596
+ };
597
+ }
598
+ const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
599
+ return {
600
+ kind: "command_failure",
601
+ exitCode: input.exitCode,
602
+ summary: `command failed (exit ${input.exitCode}): ${tail}`
603
+ };
604
+ }
605
+
606
+ // src/stream.ts
607
+ function eventTimestampIso(event) {
608
+ const tsMs = event.timestamp_ms;
609
+ return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
610
+ }
611
+ function cursorToolNameFromCall(toolCall) {
612
+ if (!toolCall) return null;
613
+ for (const key of Object.keys(toolCall)) {
614
+ if (key.endsWith("ToolCall")) {
615
+ const stem = key.slice(0, -"ToolCall".length);
616
+ return stem.length ? stem : key;
617
+ }
618
+ }
619
+ return null;
620
+ }
621
+ function recordStreamResult(result, event) {
622
+ result.finalResult = event.result || event.subtype || event.terminal_reason || "completed";
623
+ if (event.is_error) {
624
+ result.error = String(event.result || event.api_error_status || "stream result error");
625
+ }
626
+ }
627
+ function shellPayloadFromCursorEvent(event) {
628
+ if (event.type !== "tool_call" || event.subtype !== "completed") return null;
629
+ const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
630
+ const shell = toolCall?.shellToolCall;
631
+ if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
632
+ const shellObj = shell;
633
+ const args = shellObj.args;
634
+ const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
635
+ const result = shellObj.result;
636
+ if (!result || typeof result !== "object" || Array.isArray(result)) return null;
637
+ const body = result.success ?? result.failure;
638
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
639
+ const row = body;
640
+ const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
641
+ return {
642
+ command,
643
+ exitCode,
644
+ stdout: typeof row.stdout === "string" ? row.stdout : "",
645
+ stderr: typeof row.stderr === "string" ? row.stderr : "",
646
+ interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
647
+ };
648
+ }
649
+ function applyShellOutcome(parsed, outcome) {
650
+ if (outcome.kind === "success" || outcome.kind === "search_no_matches") return;
651
+ parsed.lastShellOutcome = outcome;
652
+ }
653
+ function parseHarnessStream(file) {
654
+ const result = {
655
+ firstEventAt: null,
656
+ lastEventAt: null,
657
+ currentTool: null,
658
+ finalResult: null,
659
+ error: null,
660
+ lastShellOutcome: null
661
+ };
662
+ if (!existsSync3(file)) return result;
663
+ const lines = readFileSync3(file, "utf8").split("\n").filter(Boolean);
664
+ for (const line of lines) {
665
+ const event = safeJson(line);
666
+ if (!event) continue;
667
+ const ts = eventTimestampIso(event);
668
+ if (ts) {
669
+ result.firstEventAt ||= ts;
670
+ result.lastEventAt = ts;
671
+ }
672
+ if (event.type === "stream_event" && event.event && typeof event.event === "object" && event.event.type === "content_block_start") {
673
+ const block = event.event.content_block;
674
+ if (block?.type === "tool_use") result.currentTool = String(block.name || "tool");
675
+ }
676
+ if (event.type === "assistant" && event.message && typeof event.message === "object") {
677
+ const content = event.message.content;
678
+ if (Array.isArray(content)) {
679
+ const tool = content.find((item) => item?.type === "tool_use");
680
+ if (tool) result.currentTool = String(tool.name || result.currentTool);
681
+ }
682
+ }
683
+ if (event.type === "tool_call" && event.subtype === "started") {
684
+ const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
685
+ const name = cursorToolNameFromCall(toolCall);
686
+ if (name) result.currentTool = name;
687
+ }
688
+ const shell = shellPayloadFromCursorEvent(event);
689
+ if (shell) {
690
+ applyShellOutcome(
691
+ result,
692
+ classifyShellCommandOutcome({
693
+ command: shell.command,
694
+ exitCode: shell.exitCode,
695
+ stdout: shell.stdout,
696
+ stderr: shell.stderr,
697
+ interleavedOutput: shell.interleaved
698
+ })
699
+ );
700
+ }
701
+ if (event.type === "result") {
702
+ recordStreamResult(result, event);
703
+ }
704
+ }
705
+ return result;
706
+ }
707
+
708
+ // src/exit-classify.ts
709
+ var FAILURE_PATTERNS = [
710
+ {
711
+ test: /\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,
712
+ label: "provider rejected the requested model"
713
+ },
714
+ {
715
+ test: /\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,
716
+ label: "provider rejected the requested model"
717
+ },
718
+ {
719
+ test: /\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,
720
+ label: "provider rejected the requested model"
721
+ },
722
+ {
723
+ test: /model preflight failed/i,
724
+ label: "model/provider preflight failed"
725
+ },
726
+ {
727
+ test: /\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,
728
+ label: "provider CLI is missing or not on PATH"
729
+ },
730
+ {
731
+ test: /\bfailed to spawn\b/i,
732
+ label: "provider failed to spawn the worker process"
733
+ },
734
+ {
735
+ test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
736
+ label: "provider authentication failed"
737
+ }
738
+ ];
739
+ function tidy2(errorText, max = 240) {
740
+ const oneLine2 = errorText.replace(/\s+/g, " ").trim();
741
+ return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
742
+ }
743
+ function classifyExitFailure(errorText) {
744
+ const text = (errorText ?? "").trim();
745
+ if (!text) return null;
746
+ for (const pattern of FAILURE_PATTERNS) {
747
+ if (pattern.test.test(text)) {
748
+ return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
749
+ }
750
+ }
751
+ return null;
752
+ }
753
+
754
+ // src/exited-salvage.ts
755
+ function trimOrNull(value) {
756
+ if (typeof value !== "string") return null;
757
+ const trimmed = value.trim();
758
+ return trimmed.length ? trimmed : null;
759
+ }
760
+ function hasFinalResult(value) {
761
+ if (value === void 0 || value === null) return false;
762
+ if (typeof value === "string") return value.trim().length > 0;
763
+ if (typeof value === "boolean") return value;
764
+ if (Array.isArray(value)) return value.length > 0;
765
+ if (typeof value === "object") return Object.keys(value).length > 0;
766
+ return true;
767
+ }
768
+ function committedHeadFromAncestry(ancestry) {
769
+ if (!ancestry?.checked) return null;
770
+ if (ancestry.headIsAncestorOfBase !== false) return null;
771
+ return trimOrNull(ancestry.head);
772
+ }
773
+ function buildAttentionReason(kind, uncommittedCount, headCommit) {
774
+ const parts = ["exited_with_changes_salvage"];
775
+ if (kind === "uncommitted" || kind === "both") {
776
+ parts.push(
777
+ `${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
778
+ );
779
+ }
780
+ if ((kind === "committed_ahead" || kind === "both") && headCommit) {
781
+ const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
782
+ parts.push(`commit ${sha} ahead of base with no final result`);
783
+ }
784
+ parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
785
+ return parts.join(": ");
786
+ }
787
+ function assessExitedWorkerSalvage(input) {
788
+ if (input.alive || hasFinalResult(input.finalResult)) return null;
789
+ const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
790
+ const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
791
+ const hasUncommitted = uncommittedCount > 0;
792
+ const hasCommittedAhead = Boolean(headCommit);
793
+ if (!hasUncommitted && !hasCommittedAhead) {
794
+ return {
795
+ kind: "none",
796
+ salvageable: false,
797
+ uncommittedCount: 0,
798
+ headCommit: null,
799
+ attentionReason: "process exited without a final result"
800
+ };
801
+ }
802
+ const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
803
+ return {
804
+ kind,
805
+ salvageable: true,
806
+ uncommittedCount,
807
+ headCommit,
808
+ attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
809
+ };
810
+ }
811
+
812
+ // src/git.ts
813
+ import { spawnSync } from "node:child_process";
814
+
815
+ // src/worker-env.ts
816
+ var FORBIDDEN_WORKER_ENV_KEYS = [
817
+ "ANTHROPIC_API_KEY",
818
+ "ANALYST_API_KEY",
819
+ "RECRUITER_API_KEY",
820
+ "AUTH_SECRET",
821
+ "NEXTAUTH_SECRET",
822
+ "DATABASE_URL",
823
+ "PRODUCTION_DATABASE_URL",
824
+ "KYNVER_PRODUCTION_DATABASE_URL",
825
+ "REDIS_URL",
826
+ "GOOGLE_CLIENT_SECRET",
827
+ "GITHUB_CLIENT_SECRET",
828
+ "KYNVER_API_KEY",
829
+ "KYNVER_SERVICE_SECRET",
830
+ "KYNVER_RUNTIME_SECRET",
831
+ "KYNVER_CRON_SECRET",
832
+ "OPENCLAW_CRON_SECRET",
833
+ "QSTASH_TOKEN",
834
+ "QSTASH_CURRENT_SIGNING_KEY",
835
+ "QSTASH_NEXT_SIGNING_KEY",
836
+ "TOOL_SECRETS_KEK",
837
+ "TOOL_EXECUTOR_DISPATCH_SECRET",
838
+ "CLOUDFLARE_API_TOKEN",
839
+ "STRIPE_SECRET_KEY",
840
+ "STRIPE_WEBHOOK_SECRET",
841
+ "STRIPE_IDENTITY_WEBHOOK_SECRET",
842
+ "VOYAGE_API_KEY",
843
+ "PERPLEXITY_API_KEY",
844
+ "FRED_API_KEY",
845
+ "FMP_API_KEY",
846
+ "CURSOR_API_KEY"
847
+ ];
848
+ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
849
+
850
+ // src/git.ts
851
+ function git(cwd, args, options = {}) {
852
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
853
+ if (res.status !== 0 && !options.allowFailure) {
854
+ const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
855
+ if (options.throwError) throw new Error(message);
856
+ fail(message);
857
+ }
858
+ return res.stdout || "";
859
+ }
860
+ function gitStatusShort(worktreePath) {
861
+ return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
862
+ }
863
+ function gitCapture(cwd, args) {
864
+ try {
865
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
866
+ return {
867
+ status: res.status,
868
+ stdout: res.stdout || "",
869
+ stderr: res.stderr || "",
870
+ error: res.error ? res.error.message : null
871
+ };
872
+ } catch (error) {
873
+ return {
874
+ status: null,
875
+ stdout: "",
876
+ stderr: "",
877
+ error: error.message
878
+ };
879
+ }
880
+ }
881
+ function gitIsAncestor(cwd, ancestor, descendant) {
882
+ const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
883
+ if (res.status === 0) return { isAncestor: true, error: null };
884
+ if (res.status === 1) return { isAncestor: false, error: null };
885
+ return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
886
+ }
887
+ function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
888
+ const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
889
+ const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
890
+ const pinnedBaseCommit = options.baseCommit?.trim() || null;
891
+ if (!worktreePath) {
892
+ return unknownAncestry(baseLabel, "missing worktree path");
893
+ }
894
+ const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
895
+ if (head.status !== 0) {
896
+ return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
897
+ }
898
+ let baseSha;
899
+ if (pinnedBaseCommit) {
900
+ baseSha = pinnedBaseCommit;
901
+ } else {
902
+ const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
903
+ if (baseHead.status !== 0) {
904
+ return unknownAncestry(
905
+ baseLabel,
906
+ baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
907
+ head.stdout.trim()
908
+ );
909
+ }
910
+ baseSha = baseHead.stdout.trim();
911
+ }
912
+ const headSha = head.stdout.trim();
913
+ if (headSha === baseSha) {
914
+ return {
915
+ checked: true,
916
+ base: baseLabel,
917
+ head: headSha,
918
+ baseHead: baseSha,
919
+ baseIsAncestorOfHead: true,
920
+ headIsAncestorOfBase: true,
921
+ relation: "synced"
922
+ };
923
+ }
924
+ const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
925
+ const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
926
+ const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
927
+ if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
928
+ return {
929
+ checked: false,
930
+ base: baseLabel,
931
+ head: headSha,
932
+ baseHead: baseSha,
933
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
934
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
935
+ relation: "unknown",
936
+ ...error ? { error } : {}
937
+ };
938
+ }
939
+ const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
940
+ return {
941
+ checked: true,
942
+ base: baseLabel,
943
+ head: headSha,
944
+ baseHead: baseSha,
945
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
946
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
947
+ relation,
948
+ ...error ? { error } : {}
949
+ };
950
+ }
951
+ function unknownAncestry(base, error, head = null) {
952
+ return {
953
+ checked: false,
954
+ base,
955
+ head,
956
+ baseHead: null,
957
+ baseIsAncestorOfHead: null,
958
+ headIsAncestorOfBase: null,
959
+ relation: "unknown",
960
+ error
961
+ };
962
+ }
963
+
964
+ // src/landing-gate.ts
965
+ function trimOrNull2(value) {
966
+ if (typeof value !== "string") return null;
967
+ const trimmed = value.trim();
968
+ return trimmed.length ? trimmed : null;
969
+ }
970
+ function hasFinalResult2(value) {
971
+ if (value === void 0 || value === null) return false;
972
+ if (typeof value === "string") return value.trim().length > 0;
973
+ if (typeof value === "boolean") return value;
974
+ if (Array.isArray(value)) return value.length > 0;
975
+ if (typeof value === "object") return Object.keys(value).length > 0;
976
+ return true;
977
+ }
978
+ function hasCommittedLandingRef(snapshot) {
979
+ if (trimOrNull2(snapshot.headCommit)) return true;
980
+ if (trimOrNull2(snapshot.prUrl)) return true;
981
+ if (trimOrNull2(snapshot.artifactBundlePath)) return true;
982
+ if (trimOrNull2(snapshot.patchPath)) return true;
983
+ const ancestry = snapshot.gitAncestry;
984
+ if (ancestry?.checked && ancestry.headIsAncestorOfBase === false && trimOrNull2(ancestry.head)) {
985
+ return true;
986
+ }
987
+ return false;
988
+ }
989
+ function assessWorkerLanding(snapshot) {
990
+ if (!hasFinalResult2(snapshot.finalResult)) return { blocked: false };
991
+ if (snapshot.changedFiles.length === 0) return { blocked: false };
992
+ if (!hasCommittedLandingRef(snapshot)) {
993
+ return {
994
+ blocked: true,
995
+ reason: "dirty_worktree_no_pr",
996
+ detail: `Worktree has ${snapshot.changedFiles.length} uncommitted change(s) with no commit or PR; commit, open a PR, or discard before landing`
997
+ };
998
+ }
999
+ return {
1000
+ blocked: true,
1001
+ detail: `Worktree has ${snapshot.changedFiles.length} uncommitted change(s); commit or discard before landing`
1002
+ };
1003
+ }
1004
+ function landingAttentionReason(verdict) {
1005
+ if (!verdict.blocked) return void 0;
1006
+ return verdict.detail ?? verdict.reason ?? "dirty_worktree_no_pr";
1007
+ }
1008
+
1009
+ // src/worker-final-result-embed.ts
1010
+ function tryParseJsonObject2(text) {
1011
+ const trimmed = text.trim();
1012
+ if (!trimmed.startsWith("{")) return null;
1013
+ try {
1014
+ const parsed = JSON.parse(trimmed);
1015
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1016
+ return parsed;
1017
+ }
1018
+ } catch {
1019
+ return null;
1020
+ }
1021
+ return null;
1022
+ }
1023
+ function reconciliationEntryCount(record) {
1024
+ const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation ?? record.targetPrs ?? record.target_prs;
1025
+ return Array.isArray(raw) ? raw.length : 0;
1026
+ }
1027
+ function extractEmbeddedWorkerFinalResultRecord(value) {
1028
+ const trimmed = value.trim();
1029
+ if (!trimmed) return null;
1030
+ const direct = tryParseJsonObject2(trimmed);
1031
+ if (direct) return direct;
1032
+ const candidates = [];
1033
+ const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
1034
+ let fenceMatch;
1035
+ while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
1036
+ const fromFence = tryParseJsonObject2(fenceMatch[1] ?? "");
1037
+ if (fromFence) candidates.push(fromFence);
1038
+ }
1039
+ const firstBrace = trimmed.indexOf("{");
1040
+ const lastBrace = trimmed.lastIndexOf("}");
1041
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
1042
+ const slice = tryParseJsonObject2(trimmed.slice(firstBrace, lastBrace + 1));
1043
+ if (slice) candidates.push(slice);
1044
+ }
1045
+ if (candidates.length === 0) return null;
1046
+ let best = candidates[candidates.length - 1];
1047
+ let bestScore = reconciliationEntryCount(best);
1048
+ for (const candidate of candidates) {
1049
+ const score = reconciliationEntryCount(candidate);
1050
+ if (score > bestScore) {
1051
+ best = candidate;
1052
+ bestScore = score;
1053
+ }
1054
+ }
1055
+ return best;
1056
+ }
1057
+
1058
+ // src/landing-contract-gate.ts
1059
+ function trimOrNull3(value) {
1060
+ if (typeof value !== "string") return null;
1061
+ const t = value.trim();
1062
+ return t.length ? t : null;
1063
+ }
1064
+ function hasFinalResult3(value) {
1065
+ if (value === void 0 || value === null) return false;
1066
+ if (typeof value === "string") return value.trim().length > 0;
1067
+ if (typeof value === "object") return Object.keys(value).length > 0;
1068
+ return true;
1069
+ }
1070
+ function normalizePrUrl(url) {
1071
+ const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
1072
+ if (!m) return trimOrNull3(url);
1073
+ return `https://github.com/${m[1]}/pull/${m[2]}`;
1074
+ }
1075
+ function prUrlSetKey(url) {
1076
+ const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
1077
+ if (!m) return url.trim().toLowerCase();
1078
+ return `${m[1].toLowerCase()}/pull/${m[2]}`;
1079
+ }
1080
+ function parseReconciliation(finalResult) {
1081
+ let record = null;
1082
+ if (typeof finalResult === "string") {
1083
+ const embedded = extractEmbeddedWorkerFinalResultRecord(finalResult);
1084
+ if (embedded) record = embedded;
1085
+ } else if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
1086
+ record = finalResult;
1087
+ }
1088
+ if (!record) return [];
1089
+ const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
1090
+ if (!Array.isArray(raw)) return [];
1091
+ const out = [];
1092
+ for (const item of raw) {
1093
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
1094
+ const row = item;
1095
+ const prUrl = normalizePrUrl(String(row.prUrl ?? row.pr_url ?? ""));
1096
+ const outcome = trimOrNull3(row.outcome);
1097
+ if (!prUrl || outcome !== "merged" && outcome !== "skipped" && outcome !== "blocked") continue;
1098
+ out.push({
1099
+ prUrl,
1100
+ outcome,
1101
+ mergeCommit: trimOrNull3(row.mergeCommit ?? row.merge_commit),
1102
+ reason: trimOrNull3(row.reason)
1103
+ });
1104
+ }
1105
+ return out;
1106
+ }
1107
+ function workerAttachedPrUrls(snapshot, finalResult) {
1108
+ const urls = [];
1109
+ const fromSnapshot = normalizePrUrl(trimOrNull3(snapshot.prUrl) ?? "");
1110
+ if (fromSnapshot) urls.push(fromSnapshot);
1111
+ if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
1112
+ const record = finalResult;
1113
+ const pr = normalizePrUrl(String(record.prUrl ?? record.pr_url ?? ""));
1114
+ if (pr) urls.push(pr);
1115
+ }
1116
+ return [...new Set(urls)];
1117
+ }
1118
+ function assessWorkerLandingContract(input) {
1119
+ const { contract, snapshot } = input;
1120
+ const finalResult = input.finalResult ?? snapshot.finalResult;
1121
+ if (!contract.landingOnly && contract.targetPrUrls.length === 0 && !contract.repairEnforceOriginalPr) {
1122
+ return { blocked: false };
1123
+ }
1124
+ if (!hasFinalResult3(finalResult)) {
1125
+ const requiresEarly = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
1126
+ if (requiresEarly && contract.targetPrUrls.length > 0) {
1127
+ return {
1128
+ blocked: true,
1129
+ reason: "missing_target_pr_reconciliation",
1130
+ detail: `Final result required to reconcile target PR(s): ${contract.targetPrUrls.join(", ")}`
1131
+ };
1132
+ }
1133
+ return { blocked: false };
1134
+ }
1135
+ const requiresTargetPrReconciliation = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
1136
+ if (!requiresTargetPrReconciliation && !contract.repairEnforceOriginalPr) {
1137
+ return { blocked: false };
1138
+ }
1139
+ const repairTarget = contract.repairEnforceOriginalPr ? normalizePrUrl(trimOrNull3(contract.targetPrUrl) ?? "") ?? (contract.targetPrUrls.length === 1 ? normalizePrUrl(contract.targetPrUrls[0]) : null) : null;
1140
+ if (repairTarget) {
1141
+ const workerPrs = workerAttachedPrUrls(snapshot, finalResult);
1142
+ const supersedes = finalResult && typeof finalResult === "object" && !Array.isArray(finalResult) && finalResult.supersedesOriginalTargetPr === true;
1143
+ if (!supersedes) {
1144
+ for (const pr of workerPrs) {
1145
+ if (pr !== repairTarget) {
1146
+ return {
1147
+ blocked: true,
1148
+ reason: "duplicate_repair_pr",
1149
+ detail: `Repair worker opened or attached PR ${pr} instead of canonical target ${repairTarget}`
1150
+ };
1151
+ }
1152
+ }
1153
+ }
1154
+ const reconciliation2 = parseReconciliation(finalResult);
1155
+ const entry = reconciliation2.find((r) => r.prUrl === repairTarget);
1156
+ if (!entry || entry.outcome !== "merged" && !(entry.reason?.trim() && (entry.outcome === "skipped" || entry.outcome === "blocked"))) {
1157
+ return {
1158
+ blocked: true,
1159
+ reason: "missing_repair_target_reconciliation",
1160
+ detail: `Repair worker must reconcile target PR ${repairTarget}`
1161
+ };
1162
+ }
1163
+ }
1164
+ const reconciliation = parseReconciliation(finalResult);
1165
+ const byUrl = new Map(
1166
+ reconciliation.map((r) => [prUrlSetKey(r.prUrl), r])
1167
+ );
1168
+ const targetSet = new Set(
1169
+ contract.targetPrUrls.map((u) => prUrlSetKey(normalizePrUrl(u) ?? u)).filter(Boolean)
1170
+ );
1171
+ const attachedPrs = workerAttachedPrUrls(snapshot, finalResult);
1172
+ if (contract.landingOnly) {
1173
+ for (const pr of attachedPrs) {
1174
+ if (targetSet.size > 0 && !targetSet.has(prUrlSetKey(pr))) {
1175
+ return {
1176
+ blocked: true,
1177
+ reason: "unrelated_implementation_pr",
1178
+ detail: `Landing-only worker attached unrelated PR ${pr}`
1179
+ };
1180
+ }
1181
+ if (targetSet.size === 0) {
1182
+ return {
1183
+ blocked: true,
1184
+ reason: "unrelated_implementation_pr",
1185
+ detail: "Landing-only worker must not open new implementation PRs"
1186
+ };
1187
+ }
1188
+ }
1189
+ }
1190
+ if (contract.targetPrUrls.length === 0) return { blocked: false };
1191
+ const missing = [];
1192
+ for (const target of contract.targetPrUrls) {
1193
+ const key = prUrlSetKey(normalizePrUrl(target) ?? target);
1194
+ const entry = byUrl.get(key);
1195
+ if (!entry) {
1196
+ missing.push(key);
1197
+ continue;
1198
+ }
1199
+ if (entry.outcome !== "merged" && !entry.reason?.trim()) {
1200
+ missing.push(key);
1201
+ }
1202
+ }
1203
+ if (missing.length > 0) {
1204
+ return {
1205
+ blocked: true,
1206
+ reason: missing.every((u) => byUrl.has(u)) ? "incomplete_target_pr_landing" : "missing_target_pr_reconciliation",
1207
+ detail: `Target PR reconciliation incomplete: ${missing.join(", ")}`
1208
+ };
1209
+ }
1210
+ return { blocked: false };
1211
+ }
1212
+ function landingContractAttentionReason(verdict) {
1213
+ if (!verdict.blocked) return void 0;
1214
+ return verdict.detail ?? verdict.reason;
1215
+ }
1216
+
1217
+ // src/status.ts
1218
+ var NO_START_MS = 18e4;
1219
+ var STALE_MS = 6e5;
1220
+ function computeAttention(input) {
1221
+ const now = Date.now();
1222
+ if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
1223
+ return { state: "blocked", reason: input.completionBlocker };
1224
+ }
1225
+ if (input.finalResult) {
1226
+ if (input.localOnly && hasMergedTargetPrReconciliation(input.finalResult)) {
1227
+ return { state: "done", reason: "local-only worker superseded by merged PR" };
1228
+ }
1229
+ const landingSnapshot = {
1230
+ finalResult: input.finalResult,
1231
+ changedFiles: input.changedFiles ?? [],
1232
+ gitAncestry: input.gitAncestry ?? null,
1233
+ prUrl: input.prUrl ?? null
1234
+ };
1235
+ const landing = assessWorkerLanding(landingSnapshot);
1236
+ if (landing.blocked) {
1237
+ const detail = landingAttentionReason(landing);
1238
+ return {
1239
+ state: "needs_attention",
1240
+ reason: landing.reason ? `landing blocked (${landing.reason}): ${detail}` : `landing blocked: ${detail}`
1241
+ };
1242
+ }
1243
+ if (input.landingContract) {
1244
+ const contractVerdict = assessWorkerLandingContract({
1245
+ contract: input.landingContract,
1246
+ snapshot: landingSnapshot,
1247
+ finalResult: input.finalResult
1248
+ });
1249
+ const contractDetail = landingContractAttentionReason(contractVerdict);
1250
+ if (contractDetail) {
1251
+ return {
1252
+ state: "needs_attention",
1253
+ reason: contractVerdict.reason ? `landing contract (${contractVerdict.reason}): ${contractDetail}` : `landing contract: ${contractDetail}`
1254
+ };
1255
+ }
1256
+ }
1257
+ return { state: "done", reason: "final result recorded" };
1258
+ }
1259
+ if (!input.alive) {
1260
+ if (isAbandonedEmptyWorker(input)) {
1261
+ return { state: "done", reason: "empty abandoned worker record" };
1262
+ }
1263
+ const classified = classifyExitFailure(input.error);
1264
+ if (classified) return { state: "blocked", reason: classified.reason };
1265
+ const salvage = assessExitedWorkerSalvage({
1266
+ alive: false,
1267
+ finalResult: null,
1268
+ changedFiles: input.changedFiles,
1269
+ gitAncestry: input.gitAncestry
1270
+ });
1271
+ if (salvage?.salvageable) {
1272
+ const tail2 = input.error?.trim();
1273
+ return {
1274
+ state: "needs_attention",
1275
+ reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
1276
+ };
1277
+ }
1278
+ const tail = input.error?.trim();
1279
+ return {
1280
+ state: "needs_attention",
1281
+ reason: tail ? `process exited without a final result: ${tail}` : salvage?.attentionReason ?? "process exited without a final result"
1282
+ };
1283
+ }
1284
+ if (input.heartbeatBlocker) {
1285
+ return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
1286
+ }
1287
+ const startMs = input.startedAt ? Date.parse(input.startedAt) : NaN;
1288
+ if (!input.firstEventAt && input.stdoutBytes === 0 && input.heartbeatBytes === 0 && Number.isFinite(startMs) && now - startMs > NO_START_MS) {
1289
+ return { state: "needs_attention", reason: `no first stream event ${secsAgo(startMs)}s after start` };
1290
+ }
1291
+ const actMs = input.lastActivityAt ? Date.parse(input.lastActivityAt) : NaN;
1292
+ if (Number.isFinite(actMs) && now - actMs > STALE_MS) {
1293
+ return { state: "stale", reason: `no log/event/heartbeat activity for ${secsAgo(actMs)}s` };
1294
+ }
1295
+ return { state: "ok", reason: "recent activity" };
1296
+ }
1297
+ function isSkippedTerminalCompletionBlocker(reason) {
1298
+ const text = reason?.trim();
1299
+ if (!text) return false;
1300
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
1301
+ }
1302
+ function isAbandonedEmptyWorker(input) {
1303
+ if (input.finalResult) return false;
1304
+ if (input.taskId || input.agentOsId) return false;
1305
+ if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
1306
+ if (input.error?.trim()) return false;
1307
+ if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
1308
+ return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
1309
+ }
1310
+ function hasMergedTargetPrReconciliation(value) {
1311
+ let record = null;
1312
+ if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
1313
+ else if (value && typeof value === "object" && !Array.isArray(value)) record = value;
1314
+ if (!record) return false;
1315
+ const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
1316
+ if (!Array.isArray(raw)) return false;
1317
+ return raw.some((item) => {
1318
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
1319
+ return String(item.outcome ?? "").trim() === "merged";
1320
+ });
1321
+ }
1322
+ function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
1323
+ const ackSnapshot = worker.completionSnapshot?.finalResult;
1324
+ if (worker.completionAckSource === "local-pr-merged-reconcile" && ackSnapshot !== void 0 && ackSnapshot !== null) {
1325
+ return ackSnapshot;
1326
+ }
1327
+ if (parsedFinalResult) return parsedFinalResult;
1328
+ if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
1329
+ return terminalFinalResultFromHeartbeat(heartbeat);
1330
+ }
1331
+ function computeWorkerStatus(worker, options = {}) {
1332
+ const parsed = parseHarnessStream(worker.stdoutPath);
1333
+ const heartbeat = parseHeartbeat(worker.heartbeatPath);
1334
+ const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
1335
+ const finalResult = resolveFinalResult(worker, parsed.finalResult, heartbeat);
1336
+ const alive = completionAcknowledged ? false : isPidAlive(worker.pid);
1337
+ const stdoutBytes = fileSize(worker.stdoutPath);
1338
+ const stderrBytes = fileSize(worker.stderrPath);
1339
+ const heartbeatBytes = fileSize(worker.heartbeatPath);
1340
+ const changedFiles = gitStatusShort(worker.worktreePath);
1341
+ const gitAncestry = computeGitAncestry(worker.worktreePath, {
1342
+ base: options.base,
1343
+ baseCommit: options.baseCommit
1344
+ });
1345
+ const lastActivityAt = latestIso([
1346
+ parsed.lastEventAt,
1347
+ heartbeat.lastHeartbeatAt,
1348
+ fileMtime(worker.stdoutPath),
1349
+ fileMtime(worker.stderrPath),
1350
+ fileMtime(worker.heartbeatPath)
1351
+ ]);
1352
+ const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
1353
+ const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
1354
+ const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
1355
+ const landingContract = worker.repairTargetPrUrl ? {
1356
+ landingOnly: false,
1357
+ targetPrUrls: [worker.repairTargetPrUrl],
1358
+ targetPrUrl: worker.repairTargetPrUrl,
1359
+ repairEnforceOriginalPr: true
1360
+ } : null;
1361
+ const attention = computeAttention({
1362
+ alive,
1363
+ finalResult,
1364
+ firstEventAt: parsed.firstEventAt,
1365
+ stdoutBytes,
1366
+ stderrBytes,
1367
+ heartbeatBytes,
1368
+ lastActivityAt,
1369
+ heartbeatBlocker: heartbeat.heartbeatBlocker,
1370
+ startedAt: worker.startedAt,
1371
+ error,
1372
+ changedFiles,
1373
+ gitAncestry,
1374
+ completionBlocker: effectiveCompletionBlocker,
1375
+ landingContract,
1376
+ prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
1377
+ localOnly: worker.localOnly === true,
1378
+ taskId: worker.taskId ?? null,
1379
+ agentOsId: worker.agentOsId ?? null,
1380
+ reconcileReason: worker.reconcileReason ?? null
1381
+ });
1382
+ const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
1383
+ return {
1384
+ runId: worker.runId,
1385
+ worker: worker.name,
1386
+ pid: worker.pid,
1387
+ alive,
1388
+ status: workerStatusLabel,
1389
+ attention,
1390
+ branch: worker.branch,
1391
+ worktreePath: worker.worktreePath,
1392
+ ownedPaths: worker.ownedPaths,
1393
+ stdoutBytes,
1394
+ stderrBytes,
1395
+ heartbeatBytes,
1396
+ firstEventAt: parsed.firstEventAt,
1397
+ lastEventAt: parsed.lastEventAt,
1398
+ lastActivityAt,
1399
+ currentTool: completionAcknowledged ? null : parsed.currentTool,
1400
+ heartbeatCount: heartbeat.heartbeatCount,
1401
+ lastHeartbeatAt: heartbeat.lastHeartbeatAt,
1402
+ lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
1403
+ lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
1404
+ heartbeatBlocker: heartbeat.heartbeatBlocker,
1405
+ timestampAnomalies: heartbeat.timestampAnomalies,
1406
+ finalResult,
1407
+ error,
1408
+ changedFiles,
1409
+ gitAncestry,
1410
+ instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
1411
+ instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
1412
+ };
1413
+ }
1414
+ function isFinishedWorkerStatus(status) {
1415
+ if (status.finalResult) return true;
1416
+ if (status.alive === false) return true;
1417
+ if (status.status === "exited" || status.status === "done") return true;
1418
+ return false;
1419
+ }
1420
+ function isLandingBlockedWorkerStatus(status) {
1421
+ if (!status.finalResult) return false;
1422
+ return status.attention.state === "needs_attention" || status.attention.state === "blocked";
1423
+ }
1424
+
1425
+ // src/harness-lease-owner.ts
1426
+ var HARNESS_LEASE_PREFIX = "kynver-harness:";
1427
+ var RUNNER_MARKER = "@runner:";
1428
+ function trimOrNull4(value) {
1429
+ if (!value?.trim()) return null;
1430
+ return value.trim();
1431
+ }
1432
+ function parseHarnessLeaseRunId(leaseOwner) {
1433
+ const owner = trimOrNull4(leaseOwner);
1434
+ if (!owner?.startsWith(HARNESS_LEASE_PREFIX)) return null;
1435
+ const body = owner.slice(HARNESS_LEASE_PREFIX.length);
1436
+ const atRunner = body.indexOf(RUNNER_MARKER);
1437
+ if (atRunner >= 0) return body.slice(0, atRunner).trim() || null;
1438
+ return body.trim() || null;
1439
+ }
1440
+ function harnessLeaseOwnerMatchesRun(leaseOwner, runId) {
1441
+ const expectedRun = trimOrNull4(runId);
1442
+ if (!expectedRun) return false;
1443
+ return parseHarnessLeaseRunId(leaseOwner) === expectedRun;
1444
+ }
1445
+
1446
+ // src/monitor/monitor.classify.ts
1447
+ function classifyWorkerHealth(input) {
1448
+ const { worker, status, taskLease } = input;
1449
+ const leaseOwner = taskLease?.leaseOwner ?? null;
1450
+ if (worker.dispatched && taskLease) {
1451
+ if (taskLease.status === "running" && leaseOwner && !harnessLeaseOwnerMatchesRun(leaseOwner, worker.runId)) {
1452
+ return {
1453
+ health: "orphaned",
1454
+ reason: `task lease held by ${leaseOwner}, expected harness run ${worker.runId}`
1455
+ };
1456
+ }
1457
+ if (taskLease.status === "running" && !status.alive && !status.finalResult) {
1458
+ return {
1459
+ health: "orphaned",
1460
+ reason: "board task running but worker process is not alive"
1461
+ };
1462
+ }
1463
+ }
1464
+ if (worker.status === "running" && !status.alive && !status.finalResult) {
1465
+ return {
1466
+ health: "orphaned",
1467
+ reason: "worker.json still running but process is dead"
1468
+ };
1469
+ }
1470
+ if (status.attention.state === "stale") {
1471
+ return { health: "stale", reason: status.attention.reason };
1472
+ }
1473
+ const hbMs = status.lastHeartbeatAt ? Date.parse(status.lastHeartbeatAt) : NaN;
1474
+ if (status.alive && Number.isFinite(hbMs) && Date.now() - hbMs > STALE_MS) {
1475
+ return {
1476
+ health: "stale",
1477
+ reason: `heartbeat older than ${Math.floor(STALE_MS / 1e3)}s`
1478
+ };
1479
+ }
1480
+ if (status.alive && worker.pid && !isPidAlive(worker.pid)) {
1481
+ return { health: "orphaned", reason: "pid recorded but process is not alive" };
1482
+ }
1483
+ if (taskLease?.status === "running" && !status.alive && status.finalResult) {
1484
+ return {
1485
+ health: "healthy",
1486
+ reason: "finished worker awaiting completion replay"
1487
+ };
1488
+ }
1489
+ return {
1490
+ health: "healthy",
1491
+ reason: status.attention.reason || "worker within expected lifecycle bounds"
1492
+ };
1493
+ }
1494
+
1495
+ // src/paths.ts
1496
+ import { homedir as homedir2 } from "node:os";
1497
+ import path2 from "node:path";
1498
+
1499
+ // src/config.ts
1500
+ import { homedir, totalmem } from "node:os";
1501
+ import path from "node:path";
1502
+
1503
+ // src/wsl-host.ts
1504
+ var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
1505
+ var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
1506
+
1507
+ // src/disk-gate.ts
1508
+ var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
1509
+ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
1510
+
1511
+ // src/resource-gate.ts
1512
+ var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
1513
+ var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
1514
+
1515
+ // src/config.ts
1516
+ var CONFIG_DIR = path.join(homedir(), ".kynver");
1517
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
1518
+ var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials");
1519
+ var SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
1520
+ var SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
1521
+
1522
+ // src/paths.ts
1523
+ var LEGACY_ROOT = path2.join(homedir2(), ".openclaw", "harness");
1524
+
1525
+ // src/completion-ack.ts
1526
+ function hasCompletionAck(worker) {
1527
+ return Boolean(worker.completionReportedAt?.trim());
1528
+ }
1529
+
1530
+ // src/monitor/monitor.terminal.ts
1531
+ function assessAutoCompleteEligibility(input) {
1532
+ const { worker, status } = input;
1533
+ const blockers = [];
1534
+ if (worker.localOnly) {
1535
+ blockers.push("local-only worker (no board linkage)");
1536
+ }
1537
+ if (!worker.agentOsId || !worker.taskId) {
1538
+ blockers.push("missing agentOsId/taskId linkage");
1539
+ }
1540
+ if (hasCompletionAck(worker)) {
1541
+ blockers.push("completion already acknowledged");
1542
+ }
1543
+ if (worker.completionBlocker) {
1544
+ blockers.push(worker.completionBlocker);
1545
+ }
1546
+ if (status.heartbeatBlocker && status.alive) {
1547
+ blockers.push(`worker heartbeat blocker: ${status.heartbeatBlocker}`);
1548
+ }
1549
+ if (status.attention.state === "blocked") {
1550
+ blockers.push(status.attention.reason || "worker attention blocked");
1551
+ }
1552
+ if (isLandingBlockedWorkerStatus(status)) {
1553
+ blockers.push(status.attention.reason || "landing gate blocked");
1554
+ }
1555
+ const terminalVerified = isFinishedWorkerStatus(status);
1556
+ let terminalReason;
1557
+ if (terminalVerified) {
1558
+ if (status.finalResult) terminalReason = "final_result";
1559
+ else if (!status.alive) terminalReason = "process_exited";
1560
+ else terminalReason = "terminal_status";
1561
+ } else {
1562
+ blockers.push("worker has not reached a terminal condition");
1563
+ }
1564
+ const eligible = terminalVerified && blockers.length === 0;
1565
+ return {
1566
+ eligible,
1567
+ terminalVerified,
1568
+ terminalReason,
1569
+ blockers
1570
+ };
1571
+ }
1572
+ export {
1573
+ assessAutoCompleteEligibility,
1574
+ classifyWorkerHealth,
1575
+ computeWorkerStatus
1576
+ };
1577
+ //# sourceMappingURL=monitor.js.map