harnery 0.27.0 → 0.28.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 (59) 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 +47 -4
  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/state.d.ts +1 -1
  34. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  35. package/dist/core/workflow/workspaces/state.js +9 -30
  36. package/dist/lib/browser/client.d.ts +50 -0
  37. package/dist/lib/browser/client.d.ts.map +1 -1
  38. package/dist/lib/browser/client.js +103 -0
  39. package/dist/lib/exec.d.ts +4 -0
  40. package/dist/lib/exec.d.ts.map +1 -1
  41. package/dist/lib/exec.js +6 -1
  42. package/package.json +1 -1
  43. package/src/commands/browse.ts +33 -1
  44. package/src/commands/work.ts +80 -13
  45. package/src/core/harnesses/types.ts +5 -0
  46. package/src/core/work/index.ts +1 -0
  47. package/src/core/work/runner.ts +8 -0
  48. package/src/core/work/state.ts +177 -27
  49. package/src/core/workflow/approvals.ts +3 -6
  50. package/src/core/workflow/attempt-context.ts +47 -1
  51. package/src/core/workflow/engine.ts +48 -33
  52. package/src/core/workflow/journal.ts +90 -0
  53. package/src/core/workflow/spawn-claude.ts +8 -0
  54. package/src/core/workflow/spawn-codex.ts +8 -0
  55. package/src/core/workflow/spawn-cursor.ts +8 -0
  56. package/src/core/workflow/types.ts +12 -0
  57. package/src/core/workflow/workspaces/state.ts +13 -33
  58. package/src/lib/browser/client.ts +129 -0
  59. package/src/lib/exec.ts +10 -1
@@ -1 +1 @@
1
- {"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAgJ1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA6PN"}
1
+ {"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiJ1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAiQN"}
@@ -55,6 +55,7 @@ export function registerBrowseCommand(program, emitParam, context) {
55
55
  .option("--press <key>", "Press a key after navigation/fill (e.g., Enter)")
56
56
  .option("--wait-for <selector>", "Wait for this selector before capturing output")
57
57
  .option("--evaluate <js>", "Run JS in the page context after navigation; result printed to stdout")
58
+ .option("--capture-evaluate <js>", "In trio mode, evaluate JS inside the exact screenshot viewport immediately before capture; result is written as captureEval")
58
59
  .option("--batch <steps>", "Run multiple steps in one session, semicolon-separated. Each step is one of: " +
59
60
  "`click <selector>`, `fill <selector=>value>`, `press <key>`, `wait <selector|ms>`, `eval <js>`, `reload`. " +
60
61
  'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.')
@@ -312,6 +313,9 @@ async function runBrowse(url, opts, context) {
312
313
  devOverlay = await captureDevOverlay(browser.currentPage);
313
314
  }
314
315
  if (printMode) {
316
+ if (opts.captureEvaluate) {
317
+ throw new Error("--capture-evaluate requires trio mode so its result can be bound to a screenshot.");
318
+ }
315
319
  if (opts.baseline || opts.diff) {
316
320
  throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
317
321
  }
@@ -551,11 +555,28 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
551
555
  const skipScreenshot = opts.screenshot === false || opts.domOnly === true;
552
556
  let pngPath;
553
557
  let pngBytes;
558
+ let captureEvalResult;
559
+ let captureViewport;
560
+ let captureEvidence;
554
561
  if (!skipScreenshot) {
555
562
  pngPath = `${prefix}.png`;
556
- pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
563
+ if (opts.captureEvaluate) {
564
+ const captured = await browser.screenshotWithEvaluation(pngPath, opts.captureEvaluate, {
565
+ fullPage: opts.fullPage !== false,
566
+ });
567
+ pngBytes = captured.bytes;
568
+ captureEvalResult = captured.evaluation;
569
+ captureViewport = captured.viewport;
570
+ captureEvidence = captured.evidence;
571
+ }
572
+ else {
573
+ pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
574
+ }
557
575
  written.push(pngPath);
558
576
  }
577
+ else if (opts.captureEvaluate) {
578
+ throw new Error("--capture-evaluate requires a screenshot; remove --no-screenshot / --dom-only.");
579
+ }
559
580
  const htmlPath = `${prefix}.html`;
560
581
  const html = await browser.htmlContent(opts.selector);
561
582
  writeFileSync(htmlPath, html);
@@ -577,6 +598,11 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
577
598
  envelope.screenshotBytes = pngBytes;
578
599
  if (opts.evaluate)
579
600
  envelope.eval = evalResult;
601
+ if (opts.captureEvaluate) {
602
+ envelope.captureEval = captureEvalResult;
603
+ envelope.captureViewport = captureViewport;
604
+ envelope.captureEvidence = captureEvidence;
605
+ }
580
606
  if (opts.networkHar)
581
607
  envelope.har = resolve(opts.networkHar);
582
608
  if (visibility)
@@ -1,4 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext } from "../commander.js";
3
+ import { type WorkAttempt } from "../core/work/index.js";
3
4
  export declare function registerWorkCommand(program: Command, emit: EmitContext): void;
5
+ export declare function renderAttemptRow(attempt: WorkAttempt): string;
4
6
  //# sourceMappingURL=work.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuDnD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8F7E"}
1
+ {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAUnD,OAAO,EAUL,KAAK,WAAW,EAEjB,MAAM,uBAAuB,CAAC;AAsC/B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8F7E;AAgND,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAM7D"}
@@ -4,6 +4,7 @@ import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
4
4
  import { loadPolicyFile } from "../core/policy/index.js";
5
5
  import { acceptWorkItem, cancelWorkItem, createWorkItem, listWorkItems, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, runWorkItem, } from "../core/work/index.js";
6
6
  import { WorkflowParkedError } from "../core/workflow/index.js";
7
+ const MAX_ATTEMPT_JOURNAL_ERROR_RENDER = 240;
7
8
  export function registerWorkCommand(program, emit) {
8
9
  const registry = createBuiltinHarnessRegistry();
9
10
  const harnesses = registry.ids();
@@ -182,14 +183,43 @@ function registerGovernanceCommand(work, name, emit) {
182
183
  : "Explicitly reopen terminal or blocked work for a future attempt.")
183
184
  .option("--actor <name>", "Actor recorded in the governance receipt")
184
185
  .option("--reason <text>", "Bounded decision reason")
185
- .option("--json", "Emit the complete work record as JSON")
186
- .action((workId, opts) => {
186
+ .option("--json", "Emit the complete work record as JSON");
187
+ const command = work.commands.at(-1);
188
+ if (name === "reopen") {
189
+ command.option("--finding <text>", "Correction the next attempt must address (repeatable)", collectValue, []);
190
+ }
191
+ if (name === "accept") {
192
+ command.option("--dispose <id=outcome[:reason]>", "Dispose of an open finding: fixed, or deferred:<reason> (repeatable)", collectValue, []);
193
+ }
194
+ command.action((workId, opts) => {
187
195
  withWorkRoot(emit, (coordRoot) => {
188
196
  const fn = name === "accept" ? acceptWorkItem : name === "cancel" ? cancelWorkItem : reopenWorkItem;
189
- emitWork(fn(coordRoot, workId, opts), opts.json, emit, true);
197
+ emitWork(fn(coordRoot, workId, {
198
+ ...opts,
199
+ ...(opts.finding?.length ? { findings: opts.finding } : {}),
200
+ ...(opts.dispose?.length ? { dispositions: opts.dispose.map(parseDisposition) } : {}),
201
+ }), opts.json, emit, true);
190
202
  });
191
203
  });
192
204
  }
205
+ function collectValue(value, previous) {
206
+ return [...previous, value];
207
+ }
208
+ /** `<id>=fixed` or `<id>=deferred:<reason>`. Deferring without a reason is
209
+ * rejected downstream, so a lapsed finding cannot look like a decision. */
210
+ function parseDisposition(input) {
211
+ const split = input.indexOf("=");
212
+ if (split <= 0)
213
+ throw new Error(`invalid --dispose ${JSON.stringify(input)}; expected <id>=<outcome>`);
214
+ const id = input.slice(0, split).trim();
215
+ const rest = input.slice(split + 1).trim();
216
+ const [outcome, ...reasonParts] = rest.split(":");
217
+ if (outcome !== "fixed" && outcome !== "deferred") {
218
+ throw new Error(`invalid --dispose outcome ${JSON.stringify(outcome)}; expected fixed or deferred`);
219
+ }
220
+ const reason = reasonParts.join(":").trim();
221
+ return { id, outcome, ...(reason ? { reason } : {}) };
222
+ }
193
223
  function emitWork(record, json, emit, detail = false) {
194
224
  if (json) {
195
225
  emit.config({ format: "json" });
@@ -215,7 +245,7 @@ function emitWork(record, json, emit, detail = false) {
215
245
  lines.push("acceptance:", ...record.intent.acceptance.map((item) => ` - ${item}`));
216
246
  }
217
247
  if (projection.attempts.length) {
218
- lines.push("attempts:", ...projection.attempts.map((attempt) => ` ${attempt.number}. ${attempt.run_id} ${attempt.status}`));
248
+ lines.push("attempts:", ...projection.attempts.map(renderAttemptRow));
219
249
  }
220
250
  if (projection.approval_id)
221
251
  lines.push(`approval: ${projection.approval_id}`);
@@ -223,6 +253,19 @@ function emitWork(record, json, emit, detail = false) {
223
253
  lines.push(`proof: ${projection.proof_path}`);
224
254
  emit.text(`${lines.join("\n")}\n`);
225
255
  }
256
+ export function renderAttemptRow(attempt) {
257
+ const statusDetail = attempt.status === "journal_unreadable"
258
+ ? `: ${renderAttemptJournalError(attempt.journal_error)}`
259
+ : "";
260
+ return ` ${attempt.number}. ${attempt.run_id} ${attempt.status}${statusDetail}`;
261
+ }
262
+ function renderAttemptJournalError(error) {
263
+ const normalized = (error ?? "").replace(/\s+/g, " ").trim();
264
+ const value = normalized || "unknown journal read error";
265
+ if (value.length <= MAX_ATTEMPT_JOURNAL_ERROR_RENDER)
266
+ return value;
267
+ return `${value.slice(0, MAX_ATTEMPT_JOURNAL_ERROR_RENDER - 3).trimEnd()}...`;
268
+ }
226
269
  function renderWorkRow(record) {
227
270
  const projection = record.projection;
228
271
  return `${record.intent.id} ${projection.state.padEnd(17)} ${projection.attempts_used}/${record.intent.max_attempts} ${projection.next_action.padEnd(21)} ${record.intent.title}`;
@@ -56,6 +56,11 @@ export interface HarnessRawResult {
56
56
  exitCode: number;
57
57
  durationMs: number;
58
58
  resultFileText?: string;
59
+ /** The host killed this child for exceeding its timeout. A vendor CLI that
60
+ * handles the signal cleanly still exits 0, so exit code alone cannot tell a
61
+ * kill from a finish; without this a timed-out child reads as a success with
62
+ * an empty answer. */
63
+ timedOut?: boolean;
59
64
  }
60
65
  export interface HarnessBenchFixture {
61
66
  raw: HarnessRawResult;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,2TAoBhC,CAAC;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B;iFAC6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAEtF;;;0DAG0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C;uEACmE;IACnE,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED;oEACoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;wDAEwD;AACxD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACnF,eAAe,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,WAAW,CAAC;IACxD,OAAO,EAAE,mBAAmB,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,2TAoBhC,CAAC;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B;iFAC6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAEtF;;;0DAG0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C;uEACmE;IACnE,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED;oEACoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;0BAGsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;wDAEwD;AACxD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACnF,eAAe,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,WAAW,CAAC;IACxD,OAAO,EAAE,mBAAmB,CAAC;CAC9B"}
@@ -1,3 +1,3 @@
1
1
  export { type RunWorkItemInput, runWorkItem } from "./runner.js";
2
- export { acceptWorkItem, assertWorkId, type CreateWorkItemInput, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, type WorkAttempt, type WorkAttemptTrigger, type WorkEvent, type WorkEventType, type WorkIntent, type WorkNextAction, type WorkProjection, type WorkRecord, type WorkState, } from "./state.js";
2
+ export { acceptWorkItem, assertWorkId, type CreateWorkItemInput, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, openOperatorFindings, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, type WorkAttempt, type WorkAttemptTrigger, type WorkEvent, type WorkEventType, type WorkIntent, type WorkNextAction, type WorkProjection, type WorkRecord, type WorkState, } from "./state.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/work/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,mBAAmB,EACxB,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/work/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,mBAAmB,EACxB,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC"}
@@ -1,2 +1,2 @@
1
1
  export { runWorkItem } from "./runner.js";
2
- export { acceptWorkItem, assertWorkId, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, } from "./state.js";
2
+ export { acceptWorkItem, assertWorkId, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, openOperatorFindings, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, } from "./state.js";
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAGV,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CACV,UAAU,EACV,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa,GAAG,gBAAgB,CACxF,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAqF7E"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAGV,MAAM,sBAAsB,CAAC;AAiB9B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CACV,UAAU,EACV,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa,GAAG,gBAAgB,CACxF,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA4F7E"}
@@ -2,7 +2,7 @@ import { resolve } from "node:path";
2
2
  import { runWorkflow } from "../workflow/engine.js";
3
3
  import { readWorkflowProof } from "../workflow/proof.js";
4
4
  import { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "../workflow/types.js";
5
- import { acquireWorkLease, appendReconcileIfChanged, appendWorkEvent, assertWorkflowUnchanged, boundedActor, newWorkflowRunId, readWorkItemIgnoringLease, } from "./state.js";
5
+ import { acquireWorkLease, appendReconcileIfChanged, appendWorkEvent, assertWorkflowUnchanged, boundedActor, newWorkflowRunId, openOperatorFindings, readWorkItemIgnoringLease, } from "./state.js";
6
6
  export async function runWorkItem(input) {
7
7
  const coordRoot = resolve(input.coordRoot);
8
8
  const release = acquireWorkLease(coordRoot, input.workId);
@@ -49,11 +49,18 @@ export async function runWorkItem(input) {
49
49
  }
50
50
  prior = priorContext(coordRoot, latest);
51
51
  }
52
+ const open = openOperatorFindings(coordRoot, input.workId);
53
+ const findings = open.findings.map((finding) => ({
54
+ id: finding.id,
55
+ actor: open.actor,
56
+ statement: finding.statement,
57
+ }));
52
58
  const attemptContext = {
53
59
  schema_version: WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION,
54
60
  number: attempt,
55
61
  trigger,
56
62
  ...(prior ? { prior } : {}),
63
+ ...(findings.length === 0 ? {} : { findings }),
57
64
  };
58
65
  const runId = newWorkflowRunId();
59
66
  appendWorkEvent(coordRoot, input.workId, {
@@ -37,17 +37,32 @@ export interface WorkEvent {
37
37
  proof_sha256?: string;
38
38
  terminal_attestation_sha256?: string;
39
39
  accepted_unknowns?: string[];
40
+ /** Operator corrections raised with a reopen. Authored by a human against a
41
+ * prior attempt, carried into the next attempt, and open until disposed. */
42
+ findings?: WorkOperatorFinding[];
43
+ /** How an operator disposed of each open finding at acceptance. */
44
+ dispositions?: WorkFindingDisposition[];
40
45
  state?: WorkState;
41
46
  next_action?: WorkNextAction;
42
47
  }
48
+ export interface WorkOperatorFinding {
49
+ id: string;
50
+ statement: string;
51
+ }
52
+ export interface WorkFindingDisposition {
53
+ id: string;
54
+ outcome: "fixed" | "deferred";
55
+ reason?: string;
56
+ }
43
57
  export interface WorkAttempt {
44
58
  number: number;
45
59
  run_id: string;
46
60
  started_at: string;
47
61
  trigger?: WorkAttemptTrigger;
48
- status: "running" | "parked" | "succeeded" | "failed" | "lost";
62
+ status: "running" | "parked" | "succeeded" | "failed" | "lost" | "journal_unreadable";
49
63
  approval_id?: string;
50
64
  proof_path?: string;
65
+ journal_error?: string;
51
66
  }
52
67
  export interface WorkProjection {
53
68
  id: string;
@@ -90,6 +105,7 @@ export declare function acceptWorkItem(coordRoot: string, workId: string, input?
90
105
  actor?: string;
91
106
  reason?: string;
92
107
  acceptedUnknowns?: readonly string[];
108
+ dispositions?: readonly WorkFindingDisposition[];
93
109
  }): WorkRecord;
94
110
  export declare function cancelWorkItem(coordRoot: string, workId: string, input?: {
95
111
  actor?: string;
@@ -98,7 +114,14 @@ export declare function cancelWorkItem(coordRoot: string, workId: string, input?
98
114
  export declare function reopenWorkItem(coordRoot: string, workId: string, input?: {
99
115
  actor?: string;
100
116
  reason?: string;
117
+ findings?: readonly string[];
101
118
  }): WorkRecord;
119
+ /** Findings raised by the most recent reopen that no later acceptance disposed
120
+ * of. Empty when the work was never reopened with findings. */
121
+ export declare function openOperatorFindings(coordRoot: string, workId: string): {
122
+ actor: string;
123
+ findings: WorkOperatorFinding[];
124
+ };
102
125
  export declare function assertWorkId(workId: string): void;
103
126
  export declare function newWorkId(): string;
104
127
  export declare function newWorkflowRunId(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/work/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/work/state.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { hostname } from "node:os";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { readWorkflowApproval } from "../workflow/approvals.js";
6
6
  import { stableDigest } from "../workflow/durable-record.js";
7
+ import { WORKFLOW_JOURNAL_EVENT_BYTES, workflowJournalPath } from "../workflow/journal.js";
7
8
  import { readWorkflowProof } from "../workflow/proof.js";
8
9
  import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-state.js";
9
10
  import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.js";
@@ -142,6 +143,24 @@ export function cancelWorkItem(coordRoot, workId, input = {}) {
142
143
  export function reopenWorkItem(coordRoot, workId, input = {}) {
143
144
  return appendGovernanceEvent(coordRoot, workId, "work.reopened", ["blocked", "in_review", "succeeded", "cancelled"], input);
144
145
  }
146
+ /** Findings raised by the most recent reopen that no later acceptance disposed
147
+ * of. Empty when the work was never reopened with findings. */
148
+ export function openOperatorFindings(coordRoot, workId) {
149
+ const events = readWorkEvents(coordRoot, workId);
150
+ let actor = "operator";
151
+ let open = [];
152
+ for (const event of events) {
153
+ if (event.event === "work.reopened" && event.findings?.length) {
154
+ actor = event.actor;
155
+ open = event.findings.map((finding) => ({ ...finding }));
156
+ }
157
+ if (event.event === "work.accepted" && event.dispositions?.length) {
158
+ const disposed = new Set(event.dispositions.map((entry) => entry.id));
159
+ open = open.filter((finding) => !disposed.has(finding.id));
160
+ }
161
+ }
162
+ return { actor, findings: open };
163
+ }
145
164
  export function assertWorkId(workId) {
146
165
  if (!WORK_ID.test(workId))
147
166
  throw new Error(`invalid work id ${JSON.stringify(workId)}`);
@@ -248,6 +267,14 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
248
267
  };
249
268
  }
250
269
  const attemptsRemaining = intent.max_attempts - attempts.length;
270
+ if (latest.status === "journal_unreadable") {
271
+ return {
272
+ ...base,
273
+ state: "blocked",
274
+ reason: `workflow attempt ${latest.number} journal is unreadable: ${latest.journal_error ?? "unknown journal read error"}`,
275
+ next_action: attemptsRemaining > 0 ? "retry" : "none",
276
+ };
277
+ }
251
278
  return {
252
279
  ...base,
253
280
  state: "blocked",
@@ -309,22 +336,34 @@ function inspectAttempt(coordRoot, event, intent, priorRunId) {
309
336
  : "failed";
310
337
  return attempt;
311
338
  }
312
- const journalPath = join(coordRoot, ".harnery", "workflows", runId, "journal.jsonl");
339
+ const journalPath = workflowJournalPath(coordRoot, runId);
313
340
  if (existsSync(journalPath)) {
314
341
  let parked;
315
342
  let resumed = false;
316
- for (const line of readBoundedLines(journalPath, MAX_EVENTS_BYTES, "workflow journal")) {
317
- const value = parseObject(line, `workflow run ${runId} journal`);
318
- if (value.event === "run.parked" && typeof value.approval_id === "string") {
319
- parked = value.approval_id;
320
- resumed = false;
343
+ try {
344
+ for (const line of readBoundedLines(journalPath, MAX_EVENTS_BYTES, WORKFLOW_JOURNAL_EVENT_BYTES, "workflow journal")) {
345
+ const value = parseObject(line, `workflow run ${runId} journal`);
346
+ if (value.event === "run.parked" && typeof value.approval_id === "string") {
347
+ parked = value.approval_id;
348
+ resumed = false;
349
+ }
350
+ if (value.event === "run.resume")
351
+ resumed = true;
352
+ }
353
+ if (parked && !resumed) {
354
+ attempt.status = "parked";
355
+ attempt.approval_id = parked;
356
+ return attempt;
321
357
  }
322
- if (value.event === "run.resume")
323
- resumed = true;
324
358
  }
325
- if (parked && !resumed) {
326
- attempt.status = "parked";
327
- attempt.approval_id = parked;
359
+ catch (error) {
360
+ if (workflowResumeLeaseIsLive(coordRoot, runId)) {
361
+ attempt.status = "running";
362
+ }
363
+ else {
364
+ attempt.status = "journal_unreadable";
365
+ attempt.journal_error = boundedJournalError(error.message);
366
+ }
328
367
  return attempt;
329
368
  }
330
369
  }
@@ -354,11 +393,17 @@ function appendGovernanceEvent(coordRoot, workId, event, allowed, input) {
354
393
  const acceptance = event === "work.accepted"
355
394
  ? acceptanceAuthority(coordRoot, current, input.acceptedUnknowns ?? [])
356
395
  : undefined;
396
+ const dispositions = event === "work.accepted"
397
+ ? findingAuthority(coordRoot, workId, input.dispositions ?? [])
398
+ : undefined;
399
+ const findings = event === "work.reopened" ? normalizeRaisedFindings(input.findings) : undefined;
357
400
  appendWorkEventUnderLease(coordRoot, workId, {
358
401
  event,
359
402
  actor: boundedActor(input.actor),
360
403
  reason: boundedOptional(input.reason, "work reason", MAX_REASON),
361
404
  ...acceptance,
405
+ ...(findings ? { findings } : {}),
406
+ ...(dispositions ? { dispositions } : {}),
362
407
  });
363
408
  }
364
409
  finally {
@@ -367,6 +412,51 @@ function appendGovernanceEvent(coordRoot, workId, event, allowed, input) {
367
412
  }
368
413
  return reconcileWorkItem(coordRoot, workId, input.actor);
369
414
  }
415
+ /** Acceptance fails closed while an operator finding is open. The operator who
416
+ * raised it decides whether it was addressed; the team cannot mark its own. */
417
+ function findingAuthority(coordRoot, workId, dispositions) {
418
+ const open = openOperatorFindings(coordRoot, workId).findings;
419
+ if (open.length === 0) {
420
+ if (dispositions.length > 0) {
421
+ throw new Error(`work item ${workId} has no open operator finding to dispose`);
422
+ }
423
+ return [];
424
+ }
425
+ const byId = new Map(dispositions.map((entry) => [entry.id, entry]));
426
+ const undisposed = open.filter((finding) => !byId.has(finding.id));
427
+ if (undisposed.length > 0) {
428
+ throw new Error(`work item ${workId} cannot be accepted while operator findings are undisposed: ${undisposed
429
+ .map((finding) => finding.id)
430
+ .join(", ")}`);
431
+ }
432
+ const known = new Set(open.map((finding) => finding.id));
433
+ return dispositions.map((entry) => {
434
+ if (!known.has(entry.id)) {
435
+ throw new Error(`work item ${workId} has no open operator finding ${entry.id}`);
436
+ }
437
+ if (entry.outcome !== "fixed" && entry.outcome !== "deferred") {
438
+ throw new Error(`operator finding ${entry.id} disposition must be fixed or deferred`);
439
+ }
440
+ if (entry.outcome === "deferred" && !entry.reason?.trim()) {
441
+ throw new Error(`deferring operator finding ${entry.id} requires a reason`);
442
+ }
443
+ return {
444
+ id: entry.id,
445
+ outcome: entry.outcome,
446
+ ...(entry.reason ? { reason: boundedString(entry.reason, "disposition reason", 500) } : {}),
447
+ };
448
+ });
449
+ }
450
+ function normalizeRaisedFindings(statements) {
451
+ if (!statements || statements.length === 0)
452
+ return undefined;
453
+ if (statements.length > 20)
454
+ throw new Error("a reopen may raise at most 20 findings");
455
+ return statements.map((statement, index) => ({
456
+ id: `f${index + 1}`,
457
+ statement: boundedString(statement, `finding[${index}] statement`, 1_000),
458
+ }));
459
+ }
370
460
  function acceptanceAuthority(coordRoot, current, acceptedUnknowns) {
371
461
  const latest = current.projection.attempts.at(-1);
372
462
  if (!latest?.proof_path)
@@ -484,7 +574,7 @@ function readWorkEvents(coordRoot, workId) {
484
574
  const path = join(workDir(coordRoot, workId), "events.jsonl");
485
575
  if (!existsSync(path))
486
576
  return [];
487
- const events = readBoundedLines(path, MAX_EVENTS_BYTES, `work item ${workId} events`).map((line, index) => {
577
+ const events = readBoundedLines(path, MAX_EVENTS_BYTES, MAX_EVENT_BYTES, `work item ${workId} events`).map((line, index) => {
488
578
  const event = parseObject(line, `work item ${workId} event ${index + 1}`);
489
579
  validateWorkEvent(event, workId, index + 1);
490
580
  return event;
@@ -756,7 +846,7 @@ function writePrivateJson(path, value, maxBytes) {
756
846
  writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
757
847
  chmodSync(path, 0o600);
758
848
  }
759
- function readBoundedLines(path, maxBytes, field) {
849
+ function readBoundedLines(path, maxBytes, maxRecordBytes, field) {
760
850
  const size = statSync(path).size;
761
851
  if (size > maxBytes)
762
852
  throw new Error(`${field} exceeds ${maxBytes} bytes`);
@@ -767,11 +857,15 @@ function readBoundedLines(path, maxBytes, field) {
767
857
  if (lines.length > MAX_EVENTS)
768
858
  throw new Error(`${field} exceeds ${MAX_EVENTS} records`);
769
859
  for (const line of lines) {
770
- if (Buffer.byteLength(line) > MAX_EVENT_BYTES)
860
+ if (Buffer.byteLength(line) > maxRecordBytes)
771
861
  throw new Error(`${field} contains an oversized record`);
772
862
  }
773
863
  return lines;
774
864
  }
865
+ function boundedJournalError(message) {
866
+ const normalized = message.trim() || "unknown journal read error";
867
+ return normalized.length > MAX_REASON ? normalized.slice(0, MAX_REASON) : normalized;
868
+ }
775
869
  function parseObject(input, field) {
776
870
  try {
777
871
  const value = JSON.parse(input);
@@ -1 +1 @@
1
- {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/approvals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/approvals.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { chmodSync, existsSync, linkSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
+ import { appendWorkflowJournalEvent } from "./journal.js";
4
5
  export const WORKFLOW_APPROVAL_SCHEMA_VERSION = 1;
5
6
  const RECORD_LIMIT_BYTES = 128 * 1024;
6
7
  const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
@@ -136,19 +137,15 @@ function appendResolutionJournal(coordRoot, approval) {
136
137
  const journalPath = join(coordRoot, ".harnery", "workflows", approval.request.run_id, "journal.jsonl");
137
138
  if (!existsSync(journalPath) || !approval.decision)
138
139
  return;
139
- const event = {
140
- schema_version: 1,
141
- run_id: approval.request.run_id,
140
+ appendWorkflowJournalEvent(coordRoot, approval.request.run_id, "approval.resolved", {
142
141
  ts: approval.decision.decided_at,
143
- event: "approval.resolved",
144
142
  stage: "",
145
143
  approval_id: approval.request.id,
146
144
  decision_id: approval.request.decision_id,
147
145
  verdict: approval.decision.verdict,
148
146
  actor: approval.decision.actor,
149
147
  reason: approval.decision.reason ?? null,
150
- };
151
- writeFileSync(journalPath, `${JSON.stringify(event)}\n`, { encoding: "utf8", flag: "a" });
148
+ });
152
149
  }
153
150
  function approvalRequestDigest(policySha256, request, evaluation) {
154
151
  return createHash("sha256")
@@ -1 +1 @@
1
- {"version":3,"file":"attempt-context.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/attempt-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EAIvB,MAAM,YAAY,CAAC;AAoBpB,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CA8BtF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAU7F;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAOjG"}
1
+ {"version":3,"file":"attempt-context.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/attempt-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EAKvB,MAAM,YAAY,CAAC;AAyBpB,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAiCtF;AAmCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAc7F;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAOjG"}