harnery 0.27.0 → 0.29.0

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 (62) hide show
  1. package/dist/commands/browse.d.ts.map +1 -1
  2. package/dist/commands/browse.js +27 -1
  3. package/dist/commands/work.d.ts +2 -0
  4. package/dist/commands/work.d.ts.map +1 -1
  5. package/dist/commands/work.js +74 -5
  6. package/dist/core/harnesses/types.d.ts +5 -0
  7. package/dist/core/harnesses/types.d.ts.map +1 -1
  8. package/dist/core/work/index.d.ts +1 -1
  9. package/dist/core/work/index.d.ts.map +1 -1
  10. package/dist/core/work/index.js +1 -1
  11. package/dist/core/work/runner.d.ts.map +1 -1
  12. package/dist/core/work/runner.js +8 -1
  13. package/dist/core/work/state.d.ts +24 -1
  14. package/dist/core/work/state.d.ts.map +1 -1
  15. package/dist/core/work/state.js +108 -14
  16. package/dist/core/workflow/approvals.d.ts.map +1 -1
  17. package/dist/core/workflow/approvals.js +3 -6
  18. package/dist/core/workflow/attempt-context.d.ts.map +1 -1
  19. package/dist/core/workflow/attempt-context.js +38 -1
  20. package/dist/core/workflow/engine.d.ts.map +1 -1
  21. package/dist/core/workflow/engine.js +20 -19
  22. package/dist/core/workflow/journal.d.ts +30 -0
  23. package/dist/core/workflow/journal.d.ts.map +1 -0
  24. package/dist/core/workflow/journal.js +70 -0
  25. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  26. package/dist/core/workflow/spawn-claude.js +8 -0
  27. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  28. package/dist/core/workflow/spawn-codex.js +8 -0
  29. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  30. package/dist/core/workflow/spawn-cursor.js +8 -0
  31. package/dist/core/workflow/types.d.ts +11 -0
  32. package/dist/core/workflow/types.d.ts.map +1 -1
  33. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  34. package/dist/core/workflow/workspaces/local-git.js +101 -20
  35. package/dist/core/workflow/workspaces/state.d.ts +1 -1
  36. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  37. package/dist/core/workflow/workspaces/state.js +9 -30
  38. package/dist/lib/browser/client.d.ts +50 -0
  39. package/dist/lib/browser/client.d.ts.map +1 -1
  40. package/dist/lib/browser/client.js +103 -0
  41. package/dist/lib/exec.d.ts +4 -0
  42. package/dist/lib/exec.d.ts.map +1 -1
  43. package/dist/lib/exec.js +6 -1
  44. package/package.json +1 -1
  45. package/src/commands/browse.ts +33 -1
  46. package/src/commands/work.ts +109 -14
  47. package/src/core/harnesses/types.ts +5 -0
  48. package/src/core/work/index.ts +1 -0
  49. package/src/core/work/runner.ts +8 -0
  50. package/src/core/work/state.ts +177 -27
  51. package/src/core/workflow/approvals.ts +3 -6
  52. package/src/core/workflow/attempt-context.ts +47 -1
  53. package/src/core/workflow/engine.ts +48 -33
  54. package/src/core/workflow/journal.ts +90 -0
  55. package/src/core/workflow/spawn-claude.ts +8 -0
  56. package/src/core/workflow/spawn-codex.ts +8 -0
  57. package/src/core/workflow/spawn-cursor.ts +8 -0
  58. package/src/core/workflow/types.ts +12 -0
  59. package/src/core/workflow/workspaces/local-git.ts +112 -22
  60. package/src/core/workflow/workspaces/state.ts +13 -33
  61. package/src/lib/browser/client.ts +129 -0
  62. package/src/lib/exec.ts +10 -1
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/state.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { canonicalJson, fsyncParentDirectory, readJsonRecord, stableDigest, writeImmutableJson, } from "../durable-record.js";
4
+ export { appendWorkflowJournalEvent } from "../journal.js";
4
5
  const JSON_LIMIT = 512 * 1024;
5
- const EVENT_LIMIT = 32 * 1024;
6
+ const WORKSPACE_EVENT_BYTES = 32 * 1024;
6
7
  const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
7
8
  export function workflowRunDir(coordRoot, runId) {
8
9
  assertId(runId, "run id");
@@ -62,7 +63,9 @@ export function readWorkspaceBinding(coordRoot, providerId, bindingId) {
62
63
  return existsSync(path) ? readJsonRecord(path, "workspace binding") : undefined;
63
64
  }
64
65
  export function appendWorkspaceEvent(coordRoot, claim, event, data = {}) {
65
- if (!event || event.length > 100 || Buffer.byteLength(JSON.stringify(data)) > EVENT_LIMIT / 2) {
66
+ if (!event ||
67
+ event.length > 100 ||
68
+ Buffer.byteLength(JSON.stringify(data)) > WORKSPACE_EVENT_BYTES / 2) {
66
69
  throw new Error("workspace provider event is invalid or too large");
67
70
  }
68
71
  const path = workspaceEventsPath(coordRoot, claim.provider_id, claim.binding_id);
@@ -85,7 +88,7 @@ export function appendWorkspaceEvent(coordRoot, claim, event, data = {}) {
85
88
  record_sha256: stableDigest(basis),
86
89
  };
87
90
  const line = `${canonicalJson(record)}\n`;
88
- if (Buffer.byteLength(line) > EVENT_LIMIT)
91
+ if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES)
89
92
  throw new Error("workspace provider event is too large");
90
93
  const fd = openSync(path, "a", 0o600);
91
94
  try {
@@ -113,7 +116,7 @@ export function readWorkspaceEvents(coordRoot, providerId, bindingId) {
113
116
  .split("\n")
114
117
  .filter(Boolean)
115
118
  .map((line, index) => {
116
- if (Buffer.byteLength(line) > EVENT_LIMIT) {
119
+ if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES) {
117
120
  throw new Error(`workspace event ${index + 1} is too large`);
118
121
  }
119
122
  let record;
@@ -148,30 +151,6 @@ export function readWorkspaceEvents(coordRoot, providerId, bindingId) {
148
151
  function recordsDigestPlaceholder() {
149
152
  return null;
150
153
  }
151
- export function appendWorkflowJournalEvent(coordRoot, runId, event, data) {
152
- const path = join(workflowRunDir(coordRoot, runId), "journal.jsonl");
153
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
154
- const existed = existsSync(path);
155
- const line = `${JSON.stringify({
156
- schema_version: 1,
157
- run_id: runId,
158
- ts: new Date().toISOString(),
159
- event,
160
- ...data,
161
- })}\n`;
162
- if (Buffer.byteLength(line) > EVENT_LIMIT)
163
- throw new Error("workflow journal event is too large");
164
- const fd = openSync(path, "a", 0o600);
165
- try {
166
- writeFileSync(fd, line, "utf8");
167
- fsyncSync(fd);
168
- }
169
- finally {
170
- closeSync(fd);
171
- }
172
- if (!existed)
173
- fsyncParentDirectory(path);
174
- }
175
154
  export function appendCleanupAttempt(coordRoot, runId, input) {
176
155
  const path = join(workflowRunDir(coordRoot, runId), "cleanup", "attempts.jsonl");
177
156
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
@@ -187,7 +166,7 @@ export function appendCleanupAttempt(coordRoot, runId, input) {
187
166
  record_sha256: stableDigest(basis),
188
167
  };
189
168
  const line = `${canonicalJson(attempt)}\n`;
190
- if (Buffer.byteLength(line) > EVENT_LIMIT) {
169
+ if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES) {
191
170
  throw new Error("workspace cleanup attempt is too large");
192
171
  }
193
172
  const fd = openSync(path, "a", 0o600);
@@ -230,7 +209,7 @@ export function readCleanupAttempts(coordRoot, runId) {
230
209
  }
231
210
  function appendChainedRecord(path, record, label) {
232
211
  const line = `${canonicalJson(record)}\n`;
233
- if (Buffer.byteLength(line) > EVENT_LIMIT) {
212
+ if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES) {
234
213
  throw new Error(`${label} is too large`);
235
214
  }
236
215
  const fd = openSync(path, "a", 0o600);
@@ -134,6 +134,56 @@ export declare class Browser {
134
134
  screenshot(path: string, opts?: {
135
135
  fullPage?: boolean;
136
136
  }): Promise<number>;
137
+ /**
138
+ * Capture a full-page screenshot from an explicit capture viewport and
139
+ * evaluate the caller's final evidence expression immediately before the
140
+ * pixels are written. Playwright normally manages the full-page viewport
141
+ * internally, which leaves callers unable to inspect fixed/sticky geometry
142
+ * in the state the PNG actually renders.
143
+ */
144
+ screenshotWithEvaluation<T = unknown>(path: string, evaluation: string, opts?: {
145
+ fullPage?: boolean;
146
+ }): Promise<{
147
+ bytes: number;
148
+ evaluation: T;
149
+ viewport: {
150
+ width: number;
151
+ height: number;
152
+ };
153
+ evidence: {
154
+ converged: boolean;
155
+ reason: string;
156
+ passes: number;
157
+ max_passes: number;
158
+ max_dimension: number;
159
+ max_pixels: number;
160
+ original_viewport: {
161
+ width: number;
162
+ height: number;
163
+ };
164
+ evaluated_viewport: {
165
+ width: number;
166
+ height: number;
167
+ };
168
+ document_extent_before_evaluation: {
169
+ width: number;
170
+ height: number;
171
+ };
172
+ document_extent_after_evaluation: {
173
+ width: number;
174
+ height: number;
175
+ };
176
+ document_extent_after_screenshot: {
177
+ width: number;
178
+ height: number;
179
+ };
180
+ screenshot: {
181
+ width: number;
182
+ height: number;
183
+ bytes: number;
184
+ };
185
+ };
186
+ }>;
137
187
  /**
138
188
  * Plain-text snapshot of the document body. Suitable as a coarse "what's
139
189
  * on screen" signal for LLM iteration loops. For richer extraction, use
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/browser/client.ts"],"names":[],"mappings":"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"}
@@ -155,6 +155,109 @@ export class Browser {
155
155
  const buf = await page.screenshot({ path, fullPage: opts.fullPage ?? true, type: "png" });
156
156
  return buf.length;
157
157
  }
158
+ /**
159
+ * Capture a full-page screenshot from an explicit capture viewport and
160
+ * evaluate the caller's final evidence expression immediately before the
161
+ * pixels are written. Playwright normally manages the full-page viewport
162
+ * internally, which leaves callers unable to inspect fixed/sticky geometry
163
+ * in the state the PNG actually renders.
164
+ */
165
+ async screenshotWithEvaluation(path, evaluation, opts = {}) {
166
+ const page = this.currentPage;
167
+ const fullPage = opts.fullPage ?? true;
168
+ const maxPasses = 4;
169
+ const maxDimension = 32_000;
170
+ const maxPixels = 128_000_000;
171
+ const documentExtent = () => page.evaluate(() => ({
172
+ width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
173
+ height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
174
+ }));
175
+ const originalViewport = page.viewportSize();
176
+ if (!originalViewport) {
177
+ throw new Error("Capture-state evaluation requires a page with an explicit viewport.");
178
+ }
179
+ let captureViewport = originalViewport;
180
+ let passes = fullPage ? 0 : 1;
181
+ let extentBeforeEvaluation = await documentExtent();
182
+ let bounded = true;
183
+ if (fullPage) {
184
+ for (let pass = 0; pass < maxPasses; pass += 1) {
185
+ passes = pass + 1;
186
+ const nextViewport = {
187
+ width: originalViewport.width,
188
+ height: Math.max(originalViewport.height, Math.ceil(extentBeforeEvaluation.height)),
189
+ };
190
+ bounded =
191
+ nextViewport.width <= maxDimension &&
192
+ nextViewport.height <= maxDimension &&
193
+ nextViewport.width * nextViewport.height <= maxPixels;
194
+ if (!bounded)
195
+ break;
196
+ if (nextViewport.width === captureViewport.width &&
197
+ nextViewport.height === captureViewport.height &&
198
+ extentBeforeEvaluation.width <= captureViewport.width &&
199
+ extentBeforeEvaluation.height === captureViewport.height) {
200
+ break;
201
+ }
202
+ captureViewport = nextViewport;
203
+ await page.setViewportSize(captureViewport);
204
+ extentBeforeEvaluation = await documentExtent();
205
+ }
206
+ }
207
+ try {
208
+ const evaluationResult = await page.evaluate(evaluation);
209
+ const extentAfterEvaluation = await documentExtent();
210
+ const buf = await page.screenshot({ path, fullPage: fullPage && bounded, type: "png" });
211
+ const extentAfterScreenshot = await documentExtent();
212
+ const screenshot = {
213
+ width: buf.readUInt32BE(16),
214
+ height: buf.readUInt32BE(20),
215
+ bytes: buf.length,
216
+ };
217
+ const exact = fullPage
218
+ ? bounded &&
219
+ captureViewport.width === extentBeforeEvaluation.width &&
220
+ captureViewport.height === extentBeforeEvaluation.height &&
221
+ captureViewport.width === extentAfterEvaluation.width &&
222
+ captureViewport.height === extentAfterEvaluation.height &&
223
+ captureViewport.width === extentAfterScreenshot.width &&
224
+ captureViewport.height === extentAfterScreenshot.height &&
225
+ captureViewport.width === screenshot.width &&
226
+ captureViewport.height === screenshot.height
227
+ : captureViewport.width === screenshot.width &&
228
+ captureViewport.height === screenshot.height;
229
+ const reason = !bounded
230
+ ? "capture_bounds_exceeded"
231
+ : exact
232
+ ? "capture_viewport_converged"
233
+ : "capture_viewport_non_convergent";
234
+ return {
235
+ bytes: buf.length,
236
+ evaluation: evaluationResult,
237
+ viewport: captureViewport,
238
+ evidence: {
239
+ converged: exact,
240
+ reason,
241
+ passes,
242
+ max_passes: maxPasses,
243
+ max_dimension: maxDimension,
244
+ max_pixels: maxPixels,
245
+ original_viewport: originalViewport,
246
+ evaluated_viewport: captureViewport,
247
+ document_extent_before_evaluation: extentBeforeEvaluation,
248
+ document_extent_after_evaluation: extentAfterEvaluation,
249
+ document_extent_after_screenshot: extentAfterScreenshot,
250
+ screenshot,
251
+ },
252
+ };
253
+ }
254
+ finally {
255
+ if (captureViewport.width !== originalViewport.width ||
256
+ captureViewport.height !== originalViewport.height) {
257
+ await page.setViewportSize(originalViewport);
258
+ }
259
+ }
260
+ }
158
261
  /**
159
262
  * Plain-text snapshot of the document body. Suitable as a coarse "what's
160
263
  * on screen" signal for LLM iteration loops. For richer extraction, use
@@ -10,6 +10,10 @@ export interface ExecResult {
10
10
  stdout: string;
11
11
  stderr: string;
12
12
  exitCode: number;
13
+ /** True when this process killed the child for exceeding `opts.timeout`. A
14
+ * child that handles the signal cleanly still exits 0, so the exit code alone
15
+ * cannot distinguish a kill from an ordinary finish. */
16
+ timedOut?: boolean;
13
17
  }
14
18
  export interface ExecOpts {
15
19
  cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/lib/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,QAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAmC5E;AAED,sEAAsE;AACtE,wBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhG"}
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/lib/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB;;4DAEwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,QAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAwC5E;AAED,sEAAsE;AACtE,wBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhG"}
package/dist/lib/exec.js CHANGED
@@ -29,7 +29,11 @@ export function exec(cmd, opts = {}) {
29
29
  proc.stdout?.on("data", (d) => out.push(d));
30
30
  proc.stderr?.on("data", (d) => err.push(d));
31
31
  const timeout = opts.timeout ?? 30_000;
32
- const timer = setTimeout(() => proc.kill(), timeout);
32
+ let timedOut = false;
33
+ const timer = setTimeout(() => {
34
+ timedOut = true;
35
+ proc.kill();
36
+ }, timeout);
33
37
  const finish = (exitCode, errOverride) => {
34
38
  clearTimeout(timer);
35
39
  const stdout = Buffer.concat(out).toString("utf-8");
@@ -39,6 +43,7 @@ export function exec(cmd, opts = {}) {
39
43
  stdout: shouldTrim ? stdout.trim() : stdout.replace(/\n$/, ""),
40
44
  stderr: shouldTrim ? stderr.trim() : stderr.replace(/\n$/, ""),
41
45
  exitCode,
46
+ ...(timedOut ? { timedOut: true } : {}),
42
47
  });
43
48
  };
44
49
  // ENOENT (binary not found) and similar spawn failures surface here rather
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
@@ -67,6 +67,7 @@ interface BrowseOpts {
67
67
  press?: string;
68
68
  waitFor?: string;
69
69
  evaluate?: string;
70
+ captureEvaluate?: string;
70
71
  batch?: string;
71
72
  networkHar?: string;
72
73
  login?: boolean;
@@ -179,6 +180,10 @@ export function registerBrowseCommand(
179
180
  "--evaluate <js>",
180
181
  "Run JS in the page context after navigation; result printed to stdout",
181
182
  )
183
+ .option(
184
+ "--capture-evaluate <js>",
185
+ "In trio mode, evaluate JS inside the exact screenshot viewport immediately before capture; result is written as captureEval",
186
+ )
182
187
  .option(
183
188
  "--batch <steps>",
184
189
  "Run multiple steps in one session, semicolon-separated. Each step is one of: " +
@@ -601,6 +606,11 @@ async function runBrowse(
601
606
  }
602
607
 
603
608
  if (printMode) {
609
+ if (opts.captureEvaluate) {
610
+ throw new Error(
611
+ "--capture-evaluate requires trio mode so its result can be bound to a screenshot.",
612
+ );
613
+ }
604
614
  if (opts.baseline || opts.diff) {
605
615
  throw new Error(
606
616
  "--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.",
@@ -911,10 +921,27 @@ async function runTrioMode(
911
921
  const skipScreenshot = opts.screenshot === false || opts.domOnly === true;
912
922
  let pngPath: string | undefined;
913
923
  let pngBytes: number | undefined;
924
+ let captureEvalResult: unknown;
925
+ let captureViewport: { width: number; height: number } | undefined;
926
+ let captureEvidence: unknown;
914
927
  if (!skipScreenshot) {
915
928
  pngPath = `${prefix}.png`;
916
- pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
929
+ if (opts.captureEvaluate) {
930
+ const captured = await browser.screenshotWithEvaluation(pngPath, opts.captureEvaluate, {
931
+ fullPage: opts.fullPage !== false,
932
+ });
933
+ pngBytes = captured.bytes;
934
+ captureEvalResult = captured.evaluation;
935
+ captureViewport = captured.viewport;
936
+ captureEvidence = captured.evidence;
937
+ } else {
938
+ pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
939
+ }
917
940
  written.push(pngPath);
941
+ } else if (opts.captureEvaluate) {
942
+ throw new Error(
943
+ "--capture-evaluate requires a screenshot; remove --no-screenshot / --dom-only.",
944
+ );
918
945
  }
919
946
 
920
947
  const htmlPath = `${prefix}.html`;
@@ -937,6 +964,11 @@ async function runTrioMode(
937
964
  };
938
965
  if (pngBytes !== undefined) envelope.screenshotBytes = pngBytes;
939
966
  if (opts.evaluate) envelope.eval = evalResult;
967
+ if (opts.captureEvaluate) {
968
+ envelope.captureEval = captureEvalResult;
969
+ envelope.captureViewport = captureViewport;
970
+ envelope.captureEvidence = captureEvidence;
971
+ }
940
972
  if (opts.networkHar) envelope.har = resolve(opts.networkHar);
941
973
  if (visibility) envelope.visibility = visibility;
942
974
  if (widths) envelope.width = widths;
@@ -1,3 +1,4 @@
1
+ import { resolve } from "node:path";
1
2
  import type { Command } from "commander";
2
3
  import type { EmitContext } from "../commander.ts";
3
4
  import { workflowSubscriptionOnly } from "../core/config.ts";
@@ -19,6 +20,7 @@ import {
19
20
  reconcileWorkItem,
20
21
  reopenWorkItem,
21
22
  runWorkItem,
23
+ type WorkAttempt,
22
24
  type WorkRecord,
23
25
  } from "../core/work/index.ts";
24
26
  import { WorkflowParkedError } from "../core/workflow/index.ts";
@@ -43,6 +45,7 @@ interface WorkRunOpts {
43
45
  allowApiBilling?: boolean;
44
46
  policy?: string;
45
47
  isolation?: PolicyIsolation;
48
+ workspaceRoot?: string;
46
49
  approvalTo?: string;
47
50
  actor?: string;
48
51
  json?: boolean;
@@ -52,8 +55,12 @@ interface GovernanceOpts {
52
55
  actor?: string;
53
56
  reason?: string;
54
57
  json?: boolean;
58
+ finding?: string[];
59
+ dispose?: string[];
55
60
  }
56
61
 
62
+ const MAX_ATTEMPT_JOURNAL_ERROR_RENDER = 240;
63
+
57
64
  export function registerWorkCommand(program: Command, emit: EmitContext): void {
58
65
  const registry = createBuiltinHarnessRegistry();
59
66
  const harnesses = registry.ids();
@@ -173,6 +180,7 @@ function registerRunCommand(
173
180
  .option("--allow-api-billing", "Permit API-key override billing")
174
181
  .option("--policy <file>", "Host policy JSON/JSONC")
175
182
  .option("--isolation <mode>", "shared | worktree | sandbox | remote")
183
+ .option("--workspace-root <dir>", "Writable root the isolated workspace is allocated under")
176
184
  .option("--approval-to <address>", "Address durable ASK requests")
177
185
  .option("--actor <name>", "Actor recorded in attempt receipts")
178
186
  .option("--json", "Emit the workflow report or parked result as JSON")
@@ -181,7 +189,21 @@ function registerRunCommand(
181
189
  if (opts.harness && !registry.get(opts.harness)) {
182
190
  throw new Error(`unknown harness ${JSON.stringify(opts.harness)}`);
183
191
  }
192
+ if (opts.workspaceRoot && opts.isolation !== "worktree") {
193
+ throw new Error("--workspace-root requires --isolation worktree");
194
+ }
184
195
  try {
196
+ // An isolated attempt needs a provider AND the writable root it may
197
+ // allocate under; the engine refuses to guess either. Without them a
198
+ // worktree request degrades to shared, which the proof records as
199
+ // requested vs effective isolation and the reporting below surfaces.
200
+ const { createLocalGitWorktreeProvider } = await import("../core/workflow/index.ts");
201
+ const workspace = opts.workspaceRoot
202
+ ? {
203
+ provider: createLocalGitWorktreeProvider({ coordRoot }),
204
+ writableRoots: [resolve(opts.workspaceRoot)],
205
+ }
206
+ : undefined;
185
207
  const report = await runWorkItem({
186
208
  coordRoot,
187
209
  workId,
@@ -204,6 +226,7 @@ function registerRunCommand(
204
226
  approvalMode: "park",
205
227
  approvalAddressee: opts.approvalTo,
206
228
  isolation: opts.isolation,
229
+ workspace,
207
230
  networkAccess: "enabled",
208
231
  },
209
232
  });
@@ -211,8 +234,18 @@ function registerRunCommand(
211
234
  emit.config({ format: "json" });
212
235
  emit.data(report);
213
236
  } else {
237
+ // A requested isolation the run could not honour is recorded in the
238
+ // proof as requested vs effective, which nobody reads on a green run.
239
+ // Say it here instead, so a silent downgrade to shared cannot pass for
240
+ // an isolated attempt.
241
+ const degraded =
242
+ opts.isolation && opts.isolation !== "shared" && !report.workspaceBinding
243
+ ? `note: ran shared; ${opts.isolation} isolation was requested but not allocated` +
244
+ `${opts.workspaceRoot ? "" : " (no --workspace-root given)"}\n`
245
+ : "";
214
246
  emit.text(
215
- `work ${workId}: run ${report.runId} finished\nproof: ${report.proofPath}\n` +
247
+ `work ${workId}: run ${report.runId} finished\n${degraded}` +
248
+ `proof: ${report.proofPath}\n` +
216
249
  `next: harn work reconcile ${workId}\n`,
217
250
  );
218
251
  }
@@ -257,14 +290,66 @@ function registerGovernanceCommand(
257
290
  )
258
291
  .option("--actor <name>", "Actor recorded in the governance receipt")
259
292
  .option("--reason <text>", "Bounded decision reason")
260
- .option("--json", "Emit the complete work record as JSON")
261
- .action((workId: string, opts: GovernanceOpts) => {
262
- withWorkRoot(emit, (coordRoot) => {
263
- const fn =
264
- name === "accept" ? acceptWorkItem : name === "cancel" ? cancelWorkItem : reopenWorkItem;
265
- emitWork(fn(coordRoot, workId, opts), opts.json, emit, true);
266
- });
293
+ .option("--json", "Emit the complete work record as JSON");
294
+ const command = work.commands.at(-1) as Command;
295
+ if (name === "reopen") {
296
+ command.option(
297
+ "--finding <text>",
298
+ "Correction the next attempt must address (repeatable)",
299
+ collectValue,
300
+ [] as string[],
301
+ );
302
+ }
303
+ if (name === "accept") {
304
+ command.option(
305
+ "--dispose <id=outcome[:reason]>",
306
+ "Dispose of an open finding: fixed, or deferred:<reason> (repeatable)",
307
+ collectValue,
308
+ [] as string[],
309
+ );
310
+ }
311
+ command.action((workId: string, opts: GovernanceOpts) => {
312
+ withWorkRoot(emit, (coordRoot) => {
313
+ const fn =
314
+ name === "accept" ? acceptWorkItem : name === "cancel" ? cancelWorkItem : reopenWorkItem;
315
+ emitWork(
316
+ fn(coordRoot, workId, {
317
+ ...opts,
318
+ ...(opts.finding?.length ? { findings: opts.finding } : {}),
319
+ ...(opts.dispose?.length ? { dispositions: opts.dispose.map(parseDisposition) } : {}),
320
+ }),
321
+ opts.json,
322
+ emit,
323
+ true,
324
+ );
267
325
  });
326
+ });
327
+ }
328
+
329
+ function collectValue(value: string, previous: string[]): string[] {
330
+ return [...previous, value];
331
+ }
332
+
333
+ /** `<id>=fixed` or `<id>=deferred:<reason>`. Deferring without a reason is
334
+ * rejected downstream, so a lapsed finding cannot look like a decision. */
335
+ function parseDisposition(input: string): {
336
+ id: string;
337
+ outcome: "fixed" | "deferred";
338
+ reason?: string;
339
+ } {
340
+ const split = input.indexOf("=");
341
+ if (split <= 0)
342
+ throw new Error(`invalid --dispose ${JSON.stringify(input)}; expected <id>=<outcome>`);
343
+ const id = input.slice(0, split).trim();
344
+ const rest = input.slice(split + 1).trim();
345
+ const [outcome, ...reasonParts] = rest.split(":");
346
+ if (outcome !== "fixed" && outcome !== "deferred") {
347
+ throw new Error(
348
+ `invalid --dispose outcome ${JSON.stringify(outcome)}; expected fixed or deferred`,
349
+ );
350
+ }
351
+ const reason = reasonParts.join(":").trim();
352
+ return { id, outcome, ...(reason ? { reason } : {}) };
268
353
  }
269
354
 
270
355
  function emitWork(
@@ -297,18 +382,28 @@ function emitWork(
297
382
  lines.push("acceptance:", ...record.intent.acceptance.map((item) => ` - ${item}`));
298
383
  }
299
384
  if (projection.attempts.length) {
300
- lines.push(
301
- "attempts:",
302
- ...projection.attempts.map(
303
- (attempt) => ` ${attempt.number}. ${attempt.run_id} ${attempt.status}`,
304
- ),
305
- );
385
+ lines.push("attempts:", ...projection.attempts.map(renderAttemptRow));
306
386
  }
307
387
  if (projection.approval_id) lines.push(`approval: ${projection.approval_id}`);
308
388
  if (projection.proof_path) lines.push(`proof: ${projection.proof_path}`);
309
389
  emit.text(`${lines.join("\n")}\n`);
310
390
  }
311
391
 
392
+ export function renderAttemptRow(attempt: WorkAttempt): string {
393
+ const statusDetail =
394
+ attempt.status === "journal_unreadable"
395
+ ? `: ${renderAttemptJournalError(attempt.journal_error)}`
396
+ : "";
397
+ return ` ${attempt.number}. ${attempt.run_id} ${attempt.status}${statusDetail}`;
398
+ }
399
+
400
+ function renderAttemptJournalError(error: string | undefined): string {
401
+ const normalized = (error ?? "").replace(/\s+/g, " ").trim();
402
+ const value = normalized || "unknown journal read error";
403
+ if (value.length <= MAX_ATTEMPT_JOURNAL_ERROR_RENDER) return value;
404
+ return `${value.slice(0, MAX_ATTEMPT_JOURNAL_ERROR_RENDER - 3).trimEnd()}...`;
405
+ }
406
+
312
407
  function renderWorkRow(record: WorkRecord): string {
313
408
  const projection = record.projection;
314
409
  return `${record.intent.id} ${projection.state.padEnd(17)} ${projection.attempts_used}/${record.intent.max_attempts} ${projection.next_action.padEnd(21)} ${record.intent.title}`;
@@ -83,6 +83,11 @@ export interface HarnessRawResult {
83
83
  exitCode: number;
84
84
  durationMs: number;
85
85
  resultFileText?: string;
86
+ /** The host killed this child for exceeding its timeout. A vendor CLI that
87
+ * handles the signal cleanly still exits 0, so exit code alone cannot tell a
88
+ * kill from a finish; without this a timed-out child reads as a success with
89
+ * an empty answer. */
90
+ timedOut?: boolean;
86
91
  }
87
92
 
88
93
  export interface HarnessBenchFixture {
@@ -7,6 +7,7 @@ export {
7
7
  createWorkItem,
8
8
  listWorkItems,
9
9
  newWorkId,
10
+ openOperatorFindings,
10
11
  readWorkItem,
11
12
  reconcileAllWorkItems,
12
13
  reconcileWorkItem,