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.
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/work.d.ts +2 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +47 -4
- package/dist/core/harnesses/types.d.ts +5 -0
- package/dist/core/harnesses/types.d.ts.map +1 -1
- package/dist/core/work/index.d.ts +1 -1
- package/dist/core/work/index.d.ts.map +1 -1
- package/dist/core/work/index.js +1 -1
- package/dist/core/work/runner.d.ts.map +1 -1
- package/dist/core/work/runner.js +8 -1
- package/dist/core/work/state.d.ts +24 -1
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +108 -14
- package/dist/core/workflow/approvals.d.ts.map +1 -1
- package/dist/core/workflow/approvals.js +3 -6
- package/dist/core/workflow/attempt-context.d.ts.map +1 -1
- package/dist/core/workflow/attempt-context.js +38 -1
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +20 -19
- package/dist/core/workflow/journal.d.ts +30 -0
- package/dist/core/workflow/journal.d.ts.map +1 -0
- package/dist/core/workflow/journal.js +70 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +8 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +8 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +8 -0
- package/dist/core/workflow/types.d.ts +11 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.d.ts +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +9 -30
- package/dist/lib/browser/client.d.ts +50 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +103 -0
- package/dist/lib/exec.d.ts +4 -0
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +6 -1
- package/package.json +1 -1
- package/src/commands/browse.ts +33 -1
- package/src/commands/work.ts +80 -13
- package/src/core/harnesses/types.ts +5 -0
- package/src/core/work/index.ts +1 -0
- package/src/core/work/runner.ts +8 -0
- package/src/core/work/state.ts +177 -27
- package/src/core/workflow/approvals.ts +3 -6
- package/src/core/workflow/attempt-context.ts +47 -1
- package/src/core/workflow/engine.ts +48 -33
- package/src/core/workflow/journal.ts +90 -0
- package/src/core/workflow/spawn-claude.ts +8 -0
- package/src/core/workflow/spawn-codex.ts +8 -0
- package/src/core/workflow/spawn-cursor.ts +8 -0
- package/src/core/workflow/types.ts +12 -0
- package/src/core/workflow/workspaces/state.ts +13 -33
- package/src/lib/browser/client.ts +129 -0
- 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;
|
|
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"}
|
package/dist/commands/browse.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
package/dist/commands/work.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/commands/work.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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(
|
|
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;
|
|
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"}
|
package/dist/core/work/index.js
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/core/work/runner.js
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/work/state.ts"],"names":[],"mappings":"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"}
|
package/dist/core/work/state.js
CHANGED
|
@@ -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 =
|
|
339
|
+
const journalPath = workflowJournalPath(coordRoot, runId);
|
|
313
340
|
if (existsSync(journalPath)) {
|
|
314
341
|
let parked;
|
|
315
342
|
let resumed = false;
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
parked
|
|
320
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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) >
|
|
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":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
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
|
-
|
|
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,
|
|
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"}
|