harnery 0.20.0 → 0.21.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/README.md +1 -1
- package/dist/core/work/runner.d.ts +1 -1
- package/dist/core/work/runner.d.ts.map +1 -1
- package/dist/core/work/runner.js +8 -0
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +24 -4
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +29 -3
- package/dist/core/workflow/index.d.ts +2 -2
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/proof.d.ts +2 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +8 -1
- package/dist/core/workflow/run-state.d.ts +2 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +5 -0
- package/dist/core/workflow/types.d.ts +16 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +1 -0
- package/dist/core/workflow/work-context.d.ts +5 -0
- package/dist/core/workflow/work-context.d.ts.map +1 -0
- package/dist/core/workflow/work-context.js +58 -0
- package/package.json +1 -1
- package/src/core/work/runner.ts +9 -1
- package/src/core/work/state.ts +35 -4
- package/src/core/workflow/engine.ts +33 -2
- package/src/core/workflow/index.ts +5 -1
- package/src/core/workflow/proof.ts +12 -1
- package/src/core/workflow/run-state.ts +7 -1
- package/src/core/workflow/types.ts +17 -0
- package/src/core/workflow/work-context.ts +66 -0
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
|
|
23
23
|
- **Cross-machine presence:** sessions on every machine that shares a repo see each other, labeled by machine. Zero-config over the repo's own git remote (presence refs), with an optional live-socket upgrade via an end-to-end-encrypted relay — the public `relay.harnery.com`, or self-host with `harn relay serve` / the bundled Cloudflare Worker (`relay/worker/`). The relay sees only ciphertext in opaquely-named rooms.
|
|
24
24
|
- **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages. An optional host-owned policy gates dispatch and declared external mutations with `ALLOW`, `ASK`, and `DENY`; CLI `ASK` requests park durably for an explicit approve/deny and same-run resume, while library callers remain fail-closed by default.
|
|
25
|
-
- **Durable work above attempts:** `harn work` preserves an objective across bounded workflow attempts, dependencies, approval parks, failures, and review.
|
|
25
|
+
- **Durable work above attempts:** `harn work` preserves an objective across bounded workflow attempts, dependencies, approval parks, failures, and review. Reusable workflow code receives the exact work ID, title, objective, and acceptance criteria as frozen typed context, preserved in the run manifest and proof. The one-shot reconciler derives state from append-only local evidence and reports the next explicit action without silently spawning, retrying, or accepting work.
|
|
26
26
|
- **Bounded goal supervision:** `harn supervisor` freezes a mission or existing root graph, specialist team, and safety limits, then runs scheduling cycles until reviewed mission success, human attention, no progress, or budget exhaustion. Its optional planner can create the first milestone, reassess accepted milestone proof, or recover a terminal graph using only a frozen workflow-template catalog. Plans can require independent bounded specialist review before ordinary approval or auto-apply authority, old generations remain immutable evidence, and all attempts still consume the goal budget. Use explicit foreground ticks or enroll goals in the optional heartbeat-backed background service.
|
|
27
27
|
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
|
|
28
28
|
- **Backup + sync:** `harn backup` snapshots `.harnery/` via [restic](https://restic.net/); `harn sync` keeps a curated subset live across machines via [rclone](https://rclone.org/) (Google Drive or any rclone remote).
|
|
@@ -2,7 +2,7 @@ import type { EngineOpts, RunReport } from "../workflow/types.js";
|
|
|
2
2
|
export interface RunWorkItemInput {
|
|
3
3
|
coordRoot: string;
|
|
4
4
|
workId: string;
|
|
5
|
-
engine: Omit<EngineOpts, "coordRoot" | "runId" | "resumeRunId" | "workItemId">;
|
|
5
|
+
engine: Omit<EngineOpts, "coordRoot" | "runId" | "resumeRunId" | "workItemId" | "workContext">;
|
|
6
6
|
retry?: boolean;
|
|
7
7
|
actor?: string;
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAYlE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa,CAAC,CAAC;IAC/F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAkE7E"}
|
package/dist/core/work/runner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { runWorkflow } from "../workflow/engine.js";
|
|
3
|
+
import { WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION } from "../workflow/types.js";
|
|
3
4
|
import { acquireWorkLease, appendReconcileIfChanged, appendWorkEvent, assertWorkflowUnchanged, boundedActor, newWorkflowRunId, readWorkItemIgnoringLease, } from "./state.js";
|
|
4
5
|
export async function runWorkItem(input) {
|
|
5
6
|
const coordRoot = resolve(input.coordRoot);
|
|
@@ -49,6 +50,13 @@ export async function runWorkItem(input) {
|
|
|
49
50
|
coordRoot,
|
|
50
51
|
runId,
|
|
51
52
|
workItemId: input.workId,
|
|
53
|
+
workContext: {
|
|
54
|
+
schema_version: WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION,
|
|
55
|
+
id: record.intent.id,
|
|
56
|
+
title: record.intent.title,
|
|
57
|
+
objective: record.intent.objective,
|
|
58
|
+
acceptance: [...record.intent.acceptance],
|
|
59
|
+
},
|
|
52
60
|
});
|
|
53
61
|
}
|
|
54
62
|
finally {
|
|
@@ -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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"}
|
package/dist/core/work/state.js
CHANGED
|
@@ -156,7 +156,7 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
|
|
|
156
156
|
.find((event) => ["work.accepted", "work.cancelled", "work.reopened"].includes(event.event));
|
|
157
157
|
const terminal = latestGovernance?.event === "work.accepted" || latestGovernance?.event === "work.cancelled";
|
|
158
158
|
const attemptEvents = events.filter((event) => event.event === "attempt.started");
|
|
159
|
-
const attempts = attemptEvents.map((event) => inspectAttempt(coordRoot, event));
|
|
159
|
+
const attempts = attemptEvents.map((event) => inspectAttempt(coordRoot, event, intent));
|
|
160
160
|
const base = {
|
|
161
161
|
id: intent.id,
|
|
162
162
|
title: intent.title,
|
|
@@ -181,7 +181,7 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
|
|
|
181
181
|
const reopenSeq = latestGovernance?.event === "work.reopened" ? latestGovernance.seq : 0;
|
|
182
182
|
const currentAttempts = attemptEvents
|
|
183
183
|
.filter((event) => event.seq > reopenSeq)
|
|
184
|
-
.map((event) => inspectAttempt(coordRoot, event));
|
|
184
|
+
.map((event) => inspectAttempt(coordRoot, event, intent));
|
|
185
185
|
const unresolved = intent.dependencies.filter((dependency) => {
|
|
186
186
|
try {
|
|
187
187
|
return readWorkItem(coordRoot, dependency).projection.state !== "succeeded";
|
|
@@ -257,7 +257,7 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
|
|
|
257
257
|
next_action: attemptsRemaining > 0 ? "retry" : "none",
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
|
-
function inspectAttempt(coordRoot, event) {
|
|
260
|
+
function inspectAttempt(coordRoot, event, intent) {
|
|
261
261
|
const runId = event.run_id;
|
|
262
262
|
const attempt = {
|
|
263
263
|
number: event.attempt,
|
|
@@ -267,17 +267,28 @@ function inspectAttempt(coordRoot, event) {
|
|
|
267
267
|
};
|
|
268
268
|
const proofPath = join(coordRoot, ".harnery", "workflows", runId, "proof.json");
|
|
269
269
|
const manifestPath = join(coordRoot, ".harnery", "workflows", runId, "run.json");
|
|
270
|
+
let manifest;
|
|
270
271
|
if (existsSync(manifestPath)) {
|
|
271
|
-
|
|
272
|
+
manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
272
273
|
if (manifest.work_item_id !== event.work_id) {
|
|
273
274
|
throw new Error(`workflow run ${runId} does not belong to work item ${event.work_id}`);
|
|
274
275
|
}
|
|
276
|
+
if (manifest.work_context !== undefined &&
|
|
277
|
+
JSON.stringify(manifest.work_context) !== JSON.stringify(workContextForIntent(intent))) {
|
|
278
|
+
throw new Error(`workflow run ${runId} work context does not match work item ${event.work_id}`);
|
|
279
|
+
}
|
|
275
280
|
}
|
|
276
281
|
if (existsSync(proofPath)) {
|
|
277
282
|
if (!existsSync(manifestPath)) {
|
|
278
283
|
throw new Error(`workflow run ${runId} has proof without a work-linked manifest`);
|
|
279
284
|
}
|
|
280
285
|
const proof = readWorkflowProof(coordRoot, runId);
|
|
286
|
+
if (proof.run.work_item_id !== event.work_id) {
|
|
287
|
+
throw new Error(`workflow proof ${runId} does not belong to work item ${event.work_id}`);
|
|
288
|
+
}
|
|
289
|
+
if (JSON.stringify(proof.run.work_context) !== JSON.stringify(manifest?.work_context)) {
|
|
290
|
+
throw new Error(`workflow proof ${runId} work context does not match its run manifest`);
|
|
291
|
+
}
|
|
281
292
|
attempt.proof_path = proofPath;
|
|
282
293
|
attempt.status =
|
|
283
294
|
proof.run.status === "succeeded" &&
|
|
@@ -310,6 +321,15 @@ function inspectAttempt(coordRoot, event) {
|
|
|
310
321
|
attempt.status = "running";
|
|
311
322
|
return attempt;
|
|
312
323
|
}
|
|
324
|
+
function workContextForIntent(intent) {
|
|
325
|
+
return {
|
|
326
|
+
schema_version: 1,
|
|
327
|
+
id: intent.id,
|
|
328
|
+
title: intent.title,
|
|
329
|
+
objective: intent.objective,
|
|
330
|
+
acceptance: [...intent.acceptance],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
313
333
|
function appendGovernanceEvent(coordRoot, workId, event, allowed, input) {
|
|
314
334
|
const release = acquireWorkLease(coordRoot, workId);
|
|
315
335
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqCH,OAAO,KAAK,EAEV,UAAU,EAEV,SAAS,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqCH,OAAO,KAAK,EAEV,UAAU,EAEV,SAAS,EASV,MAAM,YAAY,CAAC;AAapB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAOpF;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAyD1F"}
|
|
@@ -37,6 +37,7 @@ import { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowS
|
|
|
37
37
|
import { normalizeWorkflowSpecialists, resolveSpecialistAssignment } from "./specialists.js";
|
|
38
38
|
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
39
39
|
import { parseStageOutput, validateAgainstSchema } from "./validate.js";
|
|
40
|
+
import { freezeWorkflowWorkContext } from "./work-context.js";
|
|
40
41
|
const DEFAULT_MAX_AGENTS = 50;
|
|
41
42
|
const DEFAULT_CONCURRENCY = 4;
|
|
42
43
|
const DEFAULT_MAX_ATTEMPTS = 2;
|
|
@@ -75,6 +76,16 @@ export async function runWorkflow(scriptPath, opts) {
|
|
|
75
76
|
if (opts.workItemId && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(opts.workItemId)) {
|
|
76
77
|
throw new Error(`invalid work item id ${JSON.stringify(opts.workItemId)}`);
|
|
77
78
|
}
|
|
79
|
+
if (opts.resumeRunId && opts.workContext !== undefined) {
|
|
80
|
+
throw new Error("parked workflow resume uses the frozen manifest work context");
|
|
81
|
+
}
|
|
82
|
+
const requestedWorkContext = opts.workContext === undefined ? undefined : freezeWorkflowWorkContext(opts.workContext);
|
|
83
|
+
if (requestedWorkContext && !opts.workItemId) {
|
|
84
|
+
throw new Error("workflow work context requires a work item id");
|
|
85
|
+
}
|
|
86
|
+
if (requestedWorkContext && requestedWorkContext.id !== opts.workItemId) {
|
|
87
|
+
throw new Error(`workflow work context ${requestedWorkContext.id} does not match work item ${opts.workItemId}`);
|
|
88
|
+
}
|
|
78
89
|
if (opts.resumeRunId && opts.resumeFrom) {
|
|
79
90
|
throw new Error("resumeRunId and resumeFrom are mutually exclusive");
|
|
80
91
|
}
|
|
@@ -86,6 +97,9 @@ export async function runWorkflow(scriptPath, opts) {
|
|
|
86
97
|
resumeState.manifest.work_item_id !== opts.workItemId) {
|
|
87
98
|
throw new Error(`workflow run ${opts.resumeRunId} belongs to a different work item`);
|
|
88
99
|
}
|
|
100
|
+
const workContext = resumeState?.manifest.work_context
|
|
101
|
+
? freezeWorkflowWorkContext(resumeState.manifest.work_context)
|
|
102
|
+
: requestedWorkContext;
|
|
89
103
|
const absScript = isAbsolute(scriptPath) ? scriptPath : resolve(process.cwd(), scriptPath);
|
|
90
104
|
if (resumeState) {
|
|
91
105
|
if (resolve(resumeState.manifest.script.path) !== resolve(absScript)) {
|
|
@@ -102,13 +116,13 @@ export async function runWorkflow(scriptPath, opts) {
|
|
|
102
116
|
// the run between the optimistic read and lease acquisition.
|
|
103
117
|
assertWorkflowRunResumable(opts.coordRoot, resumeState.manifest.run_id);
|
|
104
118
|
}
|
|
105
|
-
return await executeWorkflow(scriptPath, absScript, opts, resumeState);
|
|
119
|
+
return await executeWorkflow(scriptPath, absScript, opts, resumeState, workContext);
|
|
106
120
|
}
|
|
107
121
|
finally {
|
|
108
122
|
releaseResumeLease?.();
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
|
-
async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
125
|
+
async function executeWorkflow(scriptPath, absScript, opts, resumeState, workContext) {
|
|
112
126
|
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(absScript).href)));
|
|
113
127
|
if (typeof mod.default !== "function") {
|
|
114
128
|
throw new Error(`${scriptPath}: workflow script must \`export default async (ctx) => …\``);
|
|
@@ -149,6 +163,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
149
163
|
schema_version: 1,
|
|
150
164
|
run_id: runId,
|
|
151
165
|
work_item_id: opts.workItemId,
|
|
166
|
+
work_context: workContext,
|
|
152
167
|
name,
|
|
153
168
|
started_at: startedAt,
|
|
154
169
|
script: { path: absScript, sha256: workflowScriptDigest(absScript) },
|
|
@@ -668,7 +683,15 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
668
683
|
current_cost_usd: round4(costUsd),
|
|
669
684
|
});
|
|
670
685
|
};
|
|
671
|
-
const ctx = {
|
|
686
|
+
const ctx = {
|
|
687
|
+
work: workContext,
|
|
688
|
+
agent,
|
|
689
|
+
parallel,
|
|
690
|
+
stage,
|
|
691
|
+
log,
|
|
692
|
+
evidence,
|
|
693
|
+
authorize,
|
|
694
|
+
};
|
|
672
695
|
try {
|
|
673
696
|
if (resumeState) {
|
|
674
697
|
journal("run.resume", {
|
|
@@ -681,6 +704,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
681
704
|
journal("run.start", {
|
|
682
705
|
name,
|
|
683
706
|
work_item_id: opts.workItemId ?? null,
|
|
707
|
+
work_context: workContext ?? null,
|
|
684
708
|
script: absScript,
|
|
685
709
|
objective: meta.objective ?? null,
|
|
686
710
|
acceptance: meta.acceptance,
|
|
@@ -707,6 +731,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
707
731
|
proof = buildWorkflowProof({
|
|
708
732
|
runId,
|
|
709
733
|
workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
|
|
734
|
+
workContext,
|
|
710
735
|
meta,
|
|
711
736
|
status: "succeeded",
|
|
712
737
|
startedAt,
|
|
@@ -771,6 +796,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
771
796
|
const proof = buildWorkflowProof({
|
|
772
797
|
runId,
|
|
773
798
|
workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
|
|
799
|
+
workContext,
|
|
774
800
|
meta,
|
|
775
801
|
status: "failed",
|
|
776
802
|
startedAt,
|
|
@@ -4,6 +4,6 @@ export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js"
|
|
|
4
4
|
export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
|
|
5
5
|
export type { WorkflowRunManifest } from "./run-state.js";
|
|
6
6
|
export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
|
|
7
|
-
export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, } from "./types.js";
|
|
8
|
-
export { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
7
|
+
export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, WorkflowWorkContext, } from "./types.js";
|
|
8
|
+
export { WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,oCAAoC,EACpC,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,oCAAoC,EACpC,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,6BAA6B,EAC7B,oCAAoC,GACrC,MAAM,YAAY,CAAC"}
|
|
@@ -2,4 +2,4 @@ export { assertWorkflowRunId, createWorkflowApproval, listWorkflowApprovals, rea
|
|
|
2
2
|
export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js";
|
|
3
3
|
export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
|
|
4
4
|
export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
|
|
5
|
-
export { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
5
|
+
export { WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RepoSnapshot } from "../context/index.js";
|
|
2
2
|
import type { NormalizedPolicy, PolicyDecision, PolicyIsolation, PolicyNetworkAccess } from "../policy/index.js";
|
|
3
|
-
import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof } from "./types.js";
|
|
3
|
+
import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowWorkContext } from "./types.js";
|
|
4
4
|
export interface NormalizedWorkflowMeta {
|
|
5
5
|
name: string;
|
|
6
6
|
description?: string;
|
|
@@ -10,6 +10,7 @@ export interface NormalizedWorkflowMeta {
|
|
|
10
10
|
export interface BuildWorkflowProofInput {
|
|
11
11
|
runId: string;
|
|
12
12
|
workItemId?: string;
|
|
13
|
+
workContext?: Readonly<WorkflowWorkContext>;
|
|
13
14
|
meta: NormalizedWorkflowMeta;
|
|
14
15
|
status: "succeeded" | "failed";
|
|
15
16
|
startedAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EAEZ,aAAa,
|
|
1
|
+
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EAEZ,aAAa,EAIb,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAgBpB,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IAC5C,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC,CAAC;IAClF,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;QACrC,SAAS,EAAE,eAAe,CAAC;QAC3B,aAAa,EAAE,mBAAmB,CAAC;KACpC,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GACnB,sBAAsB,CAqCxB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,KAAK,EAAE,qBAAqB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,sBAAsB,CA6BzB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,EAAE,sBAAsB,EAAE,GACjC;IAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAuB9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAQjF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,aAAa,CA4ChF;AAgCD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAe3E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAyBjF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAoChE"}
|
|
@@ -3,6 +3,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, u
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { policyDigest } from "../policy/index.js";
|
|
5
5
|
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
6
|
+
import { isCanonicalWorkflowWorkContext } from "./work-context.js";
|
|
6
7
|
const MAX_ACCEPTANCE_CRITERIA = 50;
|
|
7
8
|
const MAX_EVIDENCE_RECORDS = 200;
|
|
8
9
|
const MAX_PACKET_BYTES = 512 * 1024;
|
|
@@ -127,6 +128,7 @@ export function buildWorkflowProof(input) {
|
|
|
127
128
|
started_at: input.startedAt,
|
|
128
129
|
ended_at: input.endedAt,
|
|
129
130
|
duration_ms: input.durationMs,
|
|
131
|
+
work_context: input.workContext,
|
|
130
132
|
objective: input.meta.objective,
|
|
131
133
|
error: clippedOptional(input.error, MAX_SUMMARY_CHARS),
|
|
132
134
|
result: input.result === undefined ? undefined : digestResult(input.result),
|
|
@@ -209,7 +211,12 @@ export function readWorkflowProof(coordRoot, runId) {
|
|
|
209
211
|
catch (error) {
|
|
210
212
|
throw new Error(`cannot parse workflow proof at ${path}: ${error.message}`);
|
|
211
213
|
}
|
|
212
|
-
if (proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION ||
|
|
214
|
+
if (proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION ||
|
|
215
|
+
proof.run?.id !== runId ||
|
|
216
|
+
(proof.run.work_context !== undefined &&
|
|
217
|
+
(!proof.run.work_item_id ||
|
|
218
|
+
proof.run.work_context.id !== proof.run.work_item_id ||
|
|
219
|
+
!isCanonicalWorkflowWorkContext(proof.run.work_context)))) {
|
|
213
220
|
throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`);
|
|
214
221
|
}
|
|
215
222
|
return proof;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { RepoSnapshot } from "../context/index.js";
|
|
2
2
|
import { type NormalizedPolicy, type PolicyIsolation, type PolicyNetworkAccess } from "../policy/index.js";
|
|
3
|
-
import type { WorkflowSpecialistProfile } from "./types.js";
|
|
3
|
+
import type { WorkflowSpecialistProfile, WorkflowWorkContext } from "./types.js";
|
|
4
4
|
export declare const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION: 1;
|
|
5
5
|
export interface WorkflowRunManifest {
|
|
6
6
|
schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
|
|
7
7
|
run_id: string;
|
|
8
8
|
work_item_id?: string;
|
|
9
|
+
work_context?: WorkflowWorkContext;
|
|
9
10
|
name: string;
|
|
10
11
|
started_at: string;
|
|
11
12
|
script: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/run-state.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/run-state.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGjF,eAAO,MAAM,oCAAoC,EAAG,CAAU,CAAC;AAK/D,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,oCAAoC,CAAC;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iBAAiB,EAAE,YAAY,CAAC;IAChC,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;QAC/B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,SAAS,EAAE,eAAe,CAAC;QAC3B,cAAc,EAAE,mBAAmB,CAAC;QACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;KACzD,CAAC;CACH;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,GAAG,MAAM,CAmBrF;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAoC7F;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ;IAAE,QAAQ,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA4BvD;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CASjF;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,CA0CvF"}
|
|
@@ -5,6 +5,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
5
5
|
import { normalizePolicy, policyDigest, } from "../policy/index.js";
|
|
6
6
|
import { assertWorkflowRunId, readWorkflowApproval } from "./approvals.js";
|
|
7
7
|
import { normalizeWorkflowSpecialists } from "./specialists.js";
|
|
8
|
+
import { isCanonicalWorkflowWorkContext } from "./work-context.js";
|
|
8
9
|
export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 1;
|
|
9
10
|
const MANIFEST_LIMIT_BYTES = 256 * 1024;
|
|
10
11
|
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
@@ -53,6 +54,10 @@ export function readWorkflowRunManifest(coordRoot, runId) {
|
|
|
53
54
|
manifest.run_id !== runId ||
|
|
54
55
|
(manifest.work_item_id !== undefined &&
|
|
55
56
|
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(manifest.work_item_id)) ||
|
|
57
|
+
(manifest.work_context !== undefined &&
|
|
58
|
+
(!manifest.work_item_id ||
|
|
59
|
+
manifest.work_context.id !== manifest.work_item_id ||
|
|
60
|
+
!isCanonicalWorkflowWorkContext(manifest.work_context))) ||
|
|
56
61
|
typeof manifest.name !== "string" ||
|
|
57
62
|
manifest.name.length === 0 ||
|
|
58
63
|
manifest.name.length > 200 ||
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { DispatchCostEstimator, ExternalMutationRequest, NormalizedPolicy, PolicyAskResolver, PolicyDecision, PolicyIsolation, PolicyNetworkAccess, PolicySpec } from "../policy/index.js";
|
|
10
10
|
import type { BillingMode, BillingProber } from "./billing.js";
|
|
11
11
|
export declare const WORKFLOW_PROOF_SCHEMA_VERSION: 1;
|
|
12
|
+
export declare const WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION: 1;
|
|
12
13
|
export type EvidenceKind = "test" | "command" | "artifact" | "change" | "review" | "observation";
|
|
13
14
|
export type EvidenceStatus = "passed" | "failed" | "observed" | "unknown";
|
|
14
15
|
export type EvidenceSource = "workflow" | "engine";
|
|
@@ -126,6 +127,7 @@ export interface WorkflowProof {
|
|
|
126
127
|
started_at: string;
|
|
127
128
|
ended_at: string;
|
|
128
129
|
duration_ms: number;
|
|
130
|
+
work_context?: WorkflowWorkContext;
|
|
129
131
|
objective?: string;
|
|
130
132
|
error?: string;
|
|
131
133
|
result?: ResultDigest;
|
|
@@ -260,6 +262,9 @@ export type Spawner = (req: SpawnRequest) => Promise<SpawnResult>;
|
|
|
260
262
|
/** The API surface injected into a workflow script's default export. Explicit
|
|
261
263
|
* injection (no ambient globals): keeps scripts portable and unit-testable. */
|
|
262
264
|
export interface WorkflowContext {
|
|
265
|
+
/** Frozen durable-work assignment for work-linked runs. Standalone and
|
|
266
|
+
* legacy-resumed workflows have no work context. */
|
|
267
|
+
work?: Readonly<WorkflowWorkContext>;
|
|
263
268
|
/** Spawn one subagent; resolves to validated JSON (schema) or reply text. */
|
|
264
269
|
agent: (prompt: string, opts?: AgentOpts) => Promise<unknown>;
|
|
265
270
|
/** Run thunks with bounded concurrency; a rejected thunk resolves to null. */
|
|
@@ -284,6 +289,14 @@ export interface WorkflowModule {
|
|
|
284
289
|
meta?: WorkflowMeta;
|
|
285
290
|
default: (ctx: WorkflowContext) => Promise<unknown>;
|
|
286
291
|
}
|
|
292
|
+
/** Minimal immutable durable-work input exposed to reusable workflow code. */
|
|
293
|
+
export interface WorkflowWorkContext {
|
|
294
|
+
readonly schema_version: typeof WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION;
|
|
295
|
+
readonly id: string;
|
|
296
|
+
readonly title: string;
|
|
297
|
+
readonly objective: string;
|
|
298
|
+
readonly acceptance: readonly string[];
|
|
299
|
+
}
|
|
287
300
|
export interface EngineOpts {
|
|
288
301
|
/** Repo root whose .harnery/ receives the run journal. */
|
|
289
302
|
coordRoot: string;
|
|
@@ -306,6 +319,9 @@ export interface EngineOpts {
|
|
|
306
319
|
runId?: string;
|
|
307
320
|
/** Durable objective this execution attempt belongs to. */
|
|
308
321
|
workItemId?: string;
|
|
322
|
+
/** Frozen assignment data supplied by a durable-work host. Requires the
|
|
323
|
+
* matching `workItemId`; parked resume always uses the manifest copy. */
|
|
324
|
+
workContext?: WorkflowWorkContext;
|
|
309
325
|
/** Total-agent ceiling for the run (default 50): the runaway backstop. */
|
|
310
326
|
maxAgents?: number;
|
|
311
327
|
/** Concurrent-subagent cap for parallel() (default 4). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE/D,eAAO,MAAM,6BAA6B,EAAG,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE/D,eAAO,MAAM,6BAA6B,EAAG,CAAU,CAAC;AACxD,eAAO,MAAM,oCAAoC,EAAG,CAAU,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAC;AACjG,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,CAAC;AACnD,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,QAAQ,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAiB,SAAQ,mBAAmB;IAC3D,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,0EAA0E;IAC1E,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,KAAK,EAAE;QACL,cAAc,EAAE,OAAO,CAAC;QACxB,YAAY,EAAE,OAAO,CAAC;QACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,mBAAmB,EAAE,MAAM,EAAE,CAAC;QAC9B,oBAAoB,EAAE,MAAM,EAAE,CAAC;QAC/B,kFAAkF;QAClF,UAAU,EAAE,OAAO,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,EAAE;QACb,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;QAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,2BAA2B,GAC3B,iCAAiC,GACjC,uBAAuB,GACvB,0BAA0B,GAC1B,6BAA6B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,OAAO,6BAA6B,CAAC;IACrD,GAAG,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;QACX,wEAAwE;QACxE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,iBAAiB,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,mBAAmB,CAAC;QACnC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,YAAY,CAAC;KACvB,CAAC;IACF,UAAU,EAAE;QACV,QAAQ,EAAE,gBAAgB,EAAE,CAAC;QAC7B,OAAO,EAAE,iBAAiB,CAAC;KAC5B,CAAC;IACF,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,oBAAoB,CAAC;IACjC,SAAS,EAAE,uBAAuB,EAAE,CAAC;IACrC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,SAAS,EAAE;QACT,OAAO,EAAE;YACP,IAAI,EAAE,eAAe,CAAC;YACtB,MAAM,EAAE,MAAM,CAAC;YACf,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,CAAC,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,eAAe,CAAC;IAC3B,cAAc,EAAE,mBAAmB,CAAC;IACpC,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,uBAAuB,CAAC,eAAe,CAAC,CAAC;CACxD;AAED,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC3D,oEAAoE;IACpE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,SAAS;IACxB;gFAC4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;8EAE0E;IAC1E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;uDACmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;sDACkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;kBAEc;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;kCAEkC;AAClC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC;8EAC8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ;2EACuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;8DAC0D;IAC1D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;8EAC8E;AAC9E,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAElE;+EAC+E;AAC/E,MAAM,WAAW,eAAe;IAC9B;wDACoD;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACrC,6EAA6E;IAC7E,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,8EAA8E;IAC9E,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC3E,+DAA+D;IAC/D,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,2CAA2C;IAC3C,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qFAAqF;IACrF,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,MAAM,CAAC;IACnD,0EAA0E;IAC1E,SAAS,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,cAAc,EAAE,OAAO,oCAAoC,CAAC;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB;iDAC6C;IAC7C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC;IAC7D,kFAAkF;IAClF,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,sEAAsE;IACtE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAClE;;6EAEyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;8DAE0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;6EACyE;IACzE,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;+BAE2B;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;iCAG6B;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B;mDAC+C;IAC/C,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC,CAAC;IACvF,mFAAmF;IACnF,MAAM,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACvC,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,4FAA4F;IAC5F,oBAAoB,CAAC,EAAE,qBAAqB,CAAC;IAC7C,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,uEAAuE;IACvE,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;gFAE4E;IAC5E,6BAA6B,EAAE,MAAM,CAAC;IACtC,6EAA6E;IAC7E,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IAC5D,6DAA6D;IAC7D,MAAM,CAAC,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;CACzC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WorkflowWorkContext } from "./types.js";
|
|
2
|
+
export declare function normalizeWorkflowWorkContext(value: unknown): WorkflowWorkContext;
|
|
3
|
+
export declare function freezeWorkflowWorkContext(value: unknown): Readonly<WorkflowWorkContext>;
|
|
4
|
+
export declare function isCanonicalWorkflowWorkContext(value: unknown): value is WorkflowWorkContext;
|
|
5
|
+
//# sourceMappingURL=work-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-context.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/work-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAUtD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CA+BhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAIvF;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAO3F"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION } from "./types.js";
|
|
2
|
+
const WORK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
3
|
+
const MAX_TITLE = 200;
|
|
4
|
+
const MAX_OBJECTIVE = 4_000;
|
|
5
|
+
const MAX_ACCEPTANCE = 50;
|
|
6
|
+
const MAX_ACCEPTANCE_ITEM = 500;
|
|
7
|
+
const FIELDS = new Set(["schema_version", "id", "title", "objective", "acceptance"]);
|
|
8
|
+
export function normalizeWorkflowWorkContext(value) {
|
|
9
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
10
|
+
throw new Error("workflow work context must be an object");
|
|
11
|
+
}
|
|
12
|
+
const input = value;
|
|
13
|
+
const extra = Object.keys(input).filter((key) => !FIELDS.has(key));
|
|
14
|
+
if (extra.length > 0) {
|
|
15
|
+
throw new Error(`workflow work context has unknown field ${JSON.stringify(extra[0])}`);
|
|
16
|
+
}
|
|
17
|
+
if (input.schema_version !== WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION) {
|
|
18
|
+
throw new Error("workflow work context has an unsupported schema");
|
|
19
|
+
}
|
|
20
|
+
const id = requiredString(input.id, "workflow work context id", 100);
|
|
21
|
+
if (!WORK_ID.test(id)) {
|
|
22
|
+
throw new Error(`invalid workflow work context id ${JSON.stringify(id)}`);
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(input.acceptance) || input.acceptance.length > MAX_ACCEPTANCE) {
|
|
25
|
+
throw new Error(`workflow work context acceptance must contain at most ${MAX_ACCEPTANCE} criteria`);
|
|
26
|
+
}
|
|
27
|
+
const acceptance = input.acceptance.map((criterion, index) => requiredString(criterion, `workflow work context acceptance[${index}]`, MAX_ACCEPTANCE_ITEM));
|
|
28
|
+
return {
|
|
29
|
+
schema_version: WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION,
|
|
30
|
+
id,
|
|
31
|
+
title: requiredString(input.title, "workflow work context title", MAX_TITLE),
|
|
32
|
+
objective: requiredString(input.objective, "workflow work context objective", MAX_OBJECTIVE),
|
|
33
|
+
acceptance,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function freezeWorkflowWorkContext(value) {
|
|
37
|
+
const normalized = normalizeWorkflowWorkContext(value);
|
|
38
|
+
Object.freeze(normalized.acceptance);
|
|
39
|
+
return Object.freeze(normalized);
|
|
40
|
+
}
|
|
41
|
+
export function isCanonicalWorkflowWorkContext(value) {
|
|
42
|
+
try {
|
|
43
|
+
const normalized = normalizeWorkflowWorkContext(value);
|
|
44
|
+
return JSON.stringify(normalized) === JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function requiredString(value, field, max) {
|
|
51
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
52
|
+
throw new Error(`${field} is required`);
|
|
53
|
+
}
|
|
54
|
+
const normalized = value.trim();
|
|
55
|
+
if (normalized.length > max)
|
|
56
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
package/package.json
CHANGED
package/src/core/work/runner.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { runWorkflow } from "../workflow/engine.ts";
|
|
3
3
|
import type { EngineOpts, RunReport } from "../workflow/types.ts";
|
|
4
|
+
import { WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION } from "../workflow/types.ts";
|
|
4
5
|
import {
|
|
5
6
|
acquireWorkLease,
|
|
6
7
|
appendReconcileIfChanged,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
export interface RunWorkItemInput {
|
|
15
16
|
coordRoot: string;
|
|
16
17
|
workId: string;
|
|
17
|
-
engine: Omit<EngineOpts, "coordRoot" | "runId" | "resumeRunId" | "workItemId">;
|
|
18
|
+
engine: Omit<EngineOpts, "coordRoot" | "runId" | "resumeRunId" | "workItemId" | "workContext">;
|
|
18
19
|
retry?: boolean;
|
|
19
20
|
actor?: string;
|
|
20
21
|
}
|
|
@@ -69,6 +70,13 @@ export async function runWorkItem(input: RunWorkItemInput): Promise<RunReport> {
|
|
|
69
70
|
coordRoot,
|
|
70
71
|
runId,
|
|
71
72
|
workItemId: input.workId,
|
|
73
|
+
workContext: {
|
|
74
|
+
schema_version: WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION,
|
|
75
|
+
id: record.intent.id,
|
|
76
|
+
title: record.intent.title,
|
|
77
|
+
objective: record.intent.objective,
|
|
78
|
+
acceptance: [...record.intent.acceptance],
|
|
79
|
+
},
|
|
72
80
|
});
|
|
73
81
|
} finally {
|
|
74
82
|
try {
|
package/src/core/work/state.ts
CHANGED
|
@@ -317,7 +317,7 @@ function deriveWorkProjection(
|
|
|
317
317
|
const terminal =
|
|
318
318
|
latestGovernance?.event === "work.accepted" || latestGovernance?.event === "work.cancelled";
|
|
319
319
|
const attemptEvents = events.filter((event) => event.event === "attempt.started");
|
|
320
|
-
const attempts = attemptEvents.map((event) => inspectAttempt(coordRoot, event));
|
|
320
|
+
const attempts = attemptEvents.map((event) => inspectAttempt(coordRoot, event, intent));
|
|
321
321
|
const base = {
|
|
322
322
|
id: intent.id,
|
|
323
323
|
title: intent.title,
|
|
@@ -342,7 +342,7 @@ function deriveWorkProjection(
|
|
|
342
342
|
const reopenSeq = latestGovernance?.event === "work.reopened" ? latestGovernance.seq : 0;
|
|
343
343
|
const currentAttempts = attemptEvents
|
|
344
344
|
.filter((event) => event.seq > reopenSeq)
|
|
345
|
-
.map((event) => inspectAttempt(coordRoot, event));
|
|
345
|
+
.map((event) => inspectAttempt(coordRoot, event, intent));
|
|
346
346
|
const unresolved = intent.dependencies.filter((dependency) => {
|
|
347
347
|
try {
|
|
348
348
|
return readWorkItem(coordRoot, dependency).projection.state !== "succeeded";
|
|
@@ -419,7 +419,7 @@ function deriveWorkProjection(
|
|
|
419
419
|
};
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
function inspectAttempt(coordRoot: string, event: WorkEvent): WorkAttempt {
|
|
422
|
+
function inspectAttempt(coordRoot: string, event: WorkEvent, intent: WorkIntent): WorkAttempt {
|
|
423
423
|
const runId = event.run_id as string;
|
|
424
424
|
const attempt: WorkAttempt = {
|
|
425
425
|
number: event.attempt as number,
|
|
@@ -429,17 +429,32 @@ function inspectAttempt(coordRoot: string, event: WorkEvent): WorkAttempt {
|
|
|
429
429
|
};
|
|
430
430
|
const proofPath = join(coordRoot, ".harnery", "workflows", runId, "proof.json");
|
|
431
431
|
const manifestPath = join(coordRoot, ".harnery", "workflows", runId, "run.json");
|
|
432
|
+
let manifest: ReturnType<typeof readWorkflowRunManifest> | undefined;
|
|
432
433
|
if (existsSync(manifestPath)) {
|
|
433
|
-
|
|
434
|
+
manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
434
435
|
if (manifest.work_item_id !== event.work_id) {
|
|
435
436
|
throw new Error(`workflow run ${runId} does not belong to work item ${event.work_id}`);
|
|
436
437
|
}
|
|
438
|
+
if (
|
|
439
|
+
manifest.work_context !== undefined &&
|
|
440
|
+
JSON.stringify(manifest.work_context) !== JSON.stringify(workContextForIntent(intent))
|
|
441
|
+
) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`workflow run ${runId} work context does not match work item ${event.work_id}`,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
437
446
|
}
|
|
438
447
|
if (existsSync(proofPath)) {
|
|
439
448
|
if (!existsSync(manifestPath)) {
|
|
440
449
|
throw new Error(`workflow run ${runId} has proof without a work-linked manifest`);
|
|
441
450
|
}
|
|
442
451
|
const proof = readWorkflowProof(coordRoot, runId);
|
|
452
|
+
if (proof.run.work_item_id !== event.work_id) {
|
|
453
|
+
throw new Error(`workflow proof ${runId} does not belong to work item ${event.work_id}`);
|
|
454
|
+
}
|
|
455
|
+
if (JSON.stringify(proof.run.work_context) !== JSON.stringify(manifest?.work_context)) {
|
|
456
|
+
throw new Error(`workflow proof ${runId} work context does not match its run manifest`);
|
|
457
|
+
}
|
|
443
458
|
attempt.proof_path = proofPath;
|
|
444
459
|
attempt.status =
|
|
445
460
|
proof.run.status === "succeeded" &&
|
|
@@ -471,6 +486,22 @@ function inspectAttempt(coordRoot: string, event: WorkEvent): WorkAttempt {
|
|
|
471
486
|
return attempt;
|
|
472
487
|
}
|
|
473
488
|
|
|
489
|
+
function workContextForIntent(intent: WorkIntent): {
|
|
490
|
+
schema_version: 1;
|
|
491
|
+
id: string;
|
|
492
|
+
title: string;
|
|
493
|
+
objective: string;
|
|
494
|
+
acceptance: string[];
|
|
495
|
+
} {
|
|
496
|
+
return {
|
|
497
|
+
schema_version: 1,
|
|
498
|
+
id: intent.id,
|
|
499
|
+
title: intent.title,
|
|
500
|
+
objective: intent.objective,
|
|
501
|
+
acceptance: [...intent.acceptance],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
474
505
|
function appendGovernanceEvent(
|
|
475
506
|
coordRoot: string,
|
|
476
507
|
workId: string,
|
|
@@ -64,9 +64,11 @@ import type {
|
|
|
64
64
|
WorkflowEvidenceRecord,
|
|
65
65
|
WorkflowModule,
|
|
66
66
|
WorkflowProof,
|
|
67
|
+
WorkflowWorkContext,
|
|
67
68
|
} from "./types.ts";
|
|
68
69
|
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.ts";
|
|
69
70
|
import { parseStageOutput, validateAgainstSchema } from "./validate.ts";
|
|
71
|
+
import { freezeWorkflowWorkContext } from "./work-context.ts";
|
|
70
72
|
|
|
71
73
|
const DEFAULT_MAX_AGENTS = 50;
|
|
72
74
|
const DEFAULT_CONCURRENCY = 4;
|
|
@@ -110,6 +112,19 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
|
|
|
110
112
|
if (opts.workItemId && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(opts.workItemId)) {
|
|
111
113
|
throw new Error(`invalid work item id ${JSON.stringify(opts.workItemId)}`);
|
|
112
114
|
}
|
|
115
|
+
if (opts.resumeRunId && opts.workContext !== undefined) {
|
|
116
|
+
throw new Error("parked workflow resume uses the frozen manifest work context");
|
|
117
|
+
}
|
|
118
|
+
const requestedWorkContext =
|
|
119
|
+
opts.workContext === undefined ? undefined : freezeWorkflowWorkContext(opts.workContext);
|
|
120
|
+
if (requestedWorkContext && !opts.workItemId) {
|
|
121
|
+
throw new Error("workflow work context requires a work item id");
|
|
122
|
+
}
|
|
123
|
+
if (requestedWorkContext && requestedWorkContext.id !== opts.workItemId) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`workflow work context ${requestedWorkContext.id} does not match work item ${opts.workItemId}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
113
128
|
if (opts.resumeRunId && opts.resumeFrom) {
|
|
114
129
|
throw new Error("resumeRunId and resumeFrom are mutually exclusive");
|
|
115
130
|
}
|
|
@@ -123,6 +138,9 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
|
|
|
123
138
|
) {
|
|
124
139
|
throw new Error(`workflow run ${opts.resumeRunId} belongs to a different work item`);
|
|
125
140
|
}
|
|
141
|
+
const workContext = resumeState?.manifest.work_context
|
|
142
|
+
? freezeWorkflowWorkContext(resumeState.manifest.work_context)
|
|
143
|
+
: requestedWorkContext;
|
|
126
144
|
const absScript = isAbsolute(scriptPath) ? scriptPath : resolve(process.cwd(), scriptPath);
|
|
127
145
|
if (resumeState) {
|
|
128
146
|
if (resolve(resumeState.manifest.script.path) !== resolve(absScript)) {
|
|
@@ -139,7 +157,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
|
|
|
139
157
|
// the run between the optimistic read and lease acquisition.
|
|
140
158
|
assertWorkflowRunResumable(opts.coordRoot, resumeState.manifest.run_id);
|
|
141
159
|
}
|
|
142
|
-
return await executeWorkflow(scriptPath, absScript, opts, resumeState);
|
|
160
|
+
return await executeWorkflow(scriptPath, absScript, opts, resumeState, workContext);
|
|
143
161
|
} finally {
|
|
144
162
|
releaseResumeLease?.();
|
|
145
163
|
}
|
|
@@ -150,6 +168,7 @@ async function executeWorkflow(
|
|
|
150
168
|
absScript: string,
|
|
151
169
|
opts: EngineOpts,
|
|
152
170
|
resumeState: ReturnType<typeof assertWorkflowRunResumable> | undefined,
|
|
171
|
+
workContext: Readonly<WorkflowWorkContext> | undefined,
|
|
153
172
|
): Promise<RunReport> {
|
|
154
173
|
const mod = (await import(pathToFileURL(absScript).href)) as WorkflowModule;
|
|
155
174
|
if (typeof mod.default !== "function") {
|
|
@@ -198,6 +217,7 @@ async function executeWorkflow(
|
|
|
198
217
|
schema_version: 1,
|
|
199
218
|
run_id: runId,
|
|
200
219
|
work_item_id: opts.workItemId,
|
|
220
|
+
work_context: workContext,
|
|
201
221
|
name,
|
|
202
222
|
started_at: startedAt,
|
|
203
223
|
script: { path: absScript, sha256: workflowScriptDigest(absScript) },
|
|
@@ -762,7 +782,15 @@ async function executeWorkflow(
|
|
|
762
782
|
});
|
|
763
783
|
};
|
|
764
784
|
|
|
765
|
-
const ctx: WorkflowContext = {
|
|
785
|
+
const ctx: WorkflowContext = {
|
|
786
|
+
work: workContext,
|
|
787
|
+
agent,
|
|
788
|
+
parallel,
|
|
789
|
+
stage,
|
|
790
|
+
log,
|
|
791
|
+
evidence,
|
|
792
|
+
authorize,
|
|
793
|
+
};
|
|
766
794
|
try {
|
|
767
795
|
if (resumeState) {
|
|
768
796
|
journal("run.resume", {
|
|
@@ -774,6 +802,7 @@ async function executeWorkflow(
|
|
|
774
802
|
journal("run.start", {
|
|
775
803
|
name,
|
|
776
804
|
work_item_id: opts.workItemId ?? null,
|
|
805
|
+
work_context: workContext ?? null,
|
|
777
806
|
script: absScript,
|
|
778
807
|
objective: meta.objective ?? null,
|
|
779
808
|
acceptance: meta.acceptance,
|
|
@@ -800,6 +829,7 @@ async function executeWorkflow(
|
|
|
800
829
|
proof = buildWorkflowProof({
|
|
801
830
|
runId,
|
|
802
831
|
workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
|
|
832
|
+
workContext,
|
|
803
833
|
meta,
|
|
804
834
|
status: "succeeded",
|
|
805
835
|
startedAt,
|
|
@@ -866,6 +896,7 @@ async function executeWorkflow(
|
|
|
866
896
|
const proof = buildWorkflowProof({
|
|
867
897
|
runId,
|
|
868
898
|
workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
|
|
899
|
+
workContext,
|
|
869
900
|
meta,
|
|
870
901
|
status: "failed",
|
|
871
902
|
startedAt,
|
|
@@ -71,5 +71,9 @@ export type {
|
|
|
71
71
|
WorkflowRepoSnapshot,
|
|
72
72
|
WorkflowRunStatus,
|
|
73
73
|
WorkflowSpecialistProfile,
|
|
74
|
+
WorkflowWorkContext,
|
|
75
|
+
} from "./types.ts";
|
|
76
|
+
export {
|
|
77
|
+
WORKFLOW_PROOF_SCHEMA_VERSION,
|
|
78
|
+
WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION,
|
|
74
79
|
} from "./types.ts";
|
|
75
|
-
export { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.ts";
|
|
@@ -34,8 +34,10 @@ import type {
|
|
|
34
34
|
WorkflowProofUnknown,
|
|
35
35
|
WorkflowRepoEvidence,
|
|
36
36
|
WorkflowRepoSnapshot,
|
|
37
|
+
WorkflowWorkContext,
|
|
37
38
|
} from "./types.ts";
|
|
38
39
|
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.ts";
|
|
40
|
+
import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
|
|
39
41
|
|
|
40
42
|
const MAX_ACCEPTANCE_CRITERIA = 50;
|
|
41
43
|
const MAX_EVIDENCE_RECORDS = 200;
|
|
@@ -59,6 +61,7 @@ export interface NormalizedWorkflowMeta {
|
|
|
59
61
|
export interface BuildWorkflowProofInput {
|
|
60
62
|
runId: string;
|
|
61
63
|
workItemId?: string;
|
|
64
|
+
workContext?: Readonly<WorkflowWorkContext>;
|
|
62
65
|
meta: NormalizedWorkflowMeta;
|
|
63
66
|
status: "succeeded" | "failed";
|
|
64
67
|
startedAt: string;
|
|
@@ -221,6 +224,7 @@ export function buildWorkflowProof(input: BuildWorkflowProofInput): WorkflowProo
|
|
|
221
224
|
started_at: input.startedAt,
|
|
222
225
|
ended_at: input.endedAt,
|
|
223
226
|
duration_ms: input.durationMs,
|
|
227
|
+
work_context: input.workContext,
|
|
224
228
|
objective: input.meta.objective,
|
|
225
229
|
error: clippedOptional(input.error, MAX_SUMMARY_CHARS),
|
|
226
230
|
result: input.result === undefined ? undefined : digestResult(input.result),
|
|
@@ -303,7 +307,14 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
|
|
|
303
307
|
} catch (error) {
|
|
304
308
|
throw new Error(`cannot parse workflow proof at ${path}: ${(error as Error).message}`);
|
|
305
309
|
}
|
|
306
|
-
if (
|
|
310
|
+
if (
|
|
311
|
+
proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION ||
|
|
312
|
+
proof.run?.id !== runId ||
|
|
313
|
+
(proof.run.work_context !== undefined &&
|
|
314
|
+
(!proof.run.work_item_id ||
|
|
315
|
+
proof.run.work_context.id !== proof.run.work_item_id ||
|
|
316
|
+
!isCanonicalWorkflowWorkContext(proof.run.work_context)))
|
|
317
|
+
) {
|
|
307
318
|
throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`);
|
|
308
319
|
}
|
|
309
320
|
return proof;
|
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
} from "../policy/index.ts";
|
|
24
24
|
import { assertWorkflowRunId, readWorkflowApproval } from "./approvals.ts";
|
|
25
25
|
import { normalizeWorkflowSpecialists } from "./specialists.ts";
|
|
26
|
-
import type { WorkflowSpecialistProfile } from "./types.ts";
|
|
26
|
+
import type { WorkflowSpecialistProfile, WorkflowWorkContext } from "./types.ts";
|
|
27
|
+
import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
|
|
27
28
|
|
|
28
29
|
export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 1 as const;
|
|
29
30
|
|
|
@@ -34,6 +35,7 @@ export interface WorkflowRunManifest {
|
|
|
34
35
|
schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
|
|
35
36
|
run_id: string;
|
|
36
37
|
work_item_id?: string;
|
|
38
|
+
work_context?: WorkflowWorkContext;
|
|
37
39
|
name: string;
|
|
38
40
|
started_at: string;
|
|
39
41
|
script: { path: string; sha256: string };
|
|
@@ -103,6 +105,10 @@ export function readWorkflowRunManifest(coordRoot: string, runId: string): Workf
|
|
|
103
105
|
manifest.run_id !== runId ||
|
|
104
106
|
(manifest.work_item_id !== undefined &&
|
|
105
107
|
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(manifest.work_item_id)) ||
|
|
108
|
+
(manifest.work_context !== undefined &&
|
|
109
|
+
(!manifest.work_item_id ||
|
|
110
|
+
manifest.work_context.id !== manifest.work_item_id ||
|
|
111
|
+
!isCanonicalWorkflowWorkContext(manifest.work_context))) ||
|
|
106
112
|
typeof manifest.name !== "string" ||
|
|
107
113
|
manifest.name.length === 0 ||
|
|
108
114
|
manifest.name.length > 200 ||
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
import type { BillingMode, BillingProber } from "./billing.ts";
|
|
21
21
|
|
|
22
22
|
export const WORKFLOW_PROOF_SCHEMA_VERSION = 1 as const;
|
|
23
|
+
export const WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION = 1 as const;
|
|
23
24
|
|
|
24
25
|
export type EvidenceKind = "test" | "command" | "artifact" | "change" | "review" | "observation";
|
|
25
26
|
export type EvidenceStatus = "passed" | "failed" | "observed" | "unknown";
|
|
@@ -155,6 +156,7 @@ export interface WorkflowProof {
|
|
|
155
156
|
started_at: string;
|
|
156
157
|
ended_at: string;
|
|
157
158
|
duration_ms: number;
|
|
159
|
+
work_context?: WorkflowWorkContext;
|
|
158
160
|
objective?: string;
|
|
159
161
|
error?: string;
|
|
160
162
|
result?: ResultDigest;
|
|
@@ -299,6 +301,9 @@ export type Spawner = (req: SpawnRequest) => Promise<SpawnResult>;
|
|
|
299
301
|
/** The API surface injected into a workflow script's default export. Explicit
|
|
300
302
|
* injection (no ambient globals): keeps scripts portable and unit-testable. */
|
|
301
303
|
export interface WorkflowContext {
|
|
304
|
+
/** Frozen durable-work assignment for work-linked runs. Standalone and
|
|
305
|
+
* legacy-resumed workflows have no work context. */
|
|
306
|
+
work?: Readonly<WorkflowWorkContext>;
|
|
302
307
|
/** Spawn one subagent; resolves to validated JSON (schema) or reply text. */
|
|
303
308
|
agent: (prompt: string, opts?: AgentOpts) => Promise<unknown>;
|
|
304
309
|
/** Run thunks with bounded concurrency; a rejected thunk resolves to null. */
|
|
@@ -326,6 +331,15 @@ export interface WorkflowModule {
|
|
|
326
331
|
default: (ctx: WorkflowContext) => Promise<unknown>;
|
|
327
332
|
}
|
|
328
333
|
|
|
334
|
+
/** Minimal immutable durable-work input exposed to reusable workflow code. */
|
|
335
|
+
export interface WorkflowWorkContext {
|
|
336
|
+
readonly schema_version: typeof WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION;
|
|
337
|
+
readonly id: string;
|
|
338
|
+
readonly title: string;
|
|
339
|
+
readonly objective: string;
|
|
340
|
+
readonly acceptance: readonly string[];
|
|
341
|
+
}
|
|
342
|
+
|
|
329
343
|
export interface EngineOpts {
|
|
330
344
|
/** Repo root whose .harnery/ receives the run journal. */
|
|
331
345
|
coordRoot: string;
|
|
@@ -348,6 +362,9 @@ export interface EngineOpts {
|
|
|
348
362
|
runId?: string;
|
|
349
363
|
/** Durable objective this execution attempt belongs to. */
|
|
350
364
|
workItemId?: string;
|
|
365
|
+
/** Frozen assignment data supplied by a durable-work host. Requires the
|
|
366
|
+
* matching `workItemId`; parked resume always uses the manifest copy. */
|
|
367
|
+
workContext?: WorkflowWorkContext;
|
|
351
368
|
/** Total-agent ceiling for the run (default 50): the runaway backstop. */
|
|
352
369
|
maxAgents?: number;
|
|
353
370
|
/** Concurrent-subagent cap for parallel() (default 4). */
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { WorkflowWorkContext } from "./types.ts";
|
|
2
|
+
import { WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const WORK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
5
|
+
const MAX_TITLE = 200;
|
|
6
|
+
const MAX_OBJECTIVE = 4_000;
|
|
7
|
+
const MAX_ACCEPTANCE = 50;
|
|
8
|
+
const MAX_ACCEPTANCE_ITEM = 500;
|
|
9
|
+
const FIELDS = new Set(["schema_version", "id", "title", "objective", "acceptance"]);
|
|
10
|
+
|
|
11
|
+
export function normalizeWorkflowWorkContext(value: unknown): WorkflowWorkContext {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
13
|
+
throw new Error("workflow work context must be an object");
|
|
14
|
+
}
|
|
15
|
+
const input = value as Record<string, unknown>;
|
|
16
|
+
const extra = Object.keys(input).filter((key) => !FIELDS.has(key));
|
|
17
|
+
if (extra.length > 0) {
|
|
18
|
+
throw new Error(`workflow work context has unknown field ${JSON.stringify(extra[0])}`);
|
|
19
|
+
}
|
|
20
|
+
if (input.schema_version !== WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION) {
|
|
21
|
+
throw new Error("workflow work context has an unsupported schema");
|
|
22
|
+
}
|
|
23
|
+
const id = requiredString(input.id, "workflow work context id", 100);
|
|
24
|
+
if (!WORK_ID.test(id)) {
|
|
25
|
+
throw new Error(`invalid workflow work context id ${JSON.stringify(id)}`);
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(input.acceptance) || input.acceptance.length > MAX_ACCEPTANCE) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`workflow work context acceptance must contain at most ${MAX_ACCEPTANCE} criteria`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const acceptance = input.acceptance.map((criterion, index) =>
|
|
33
|
+
requiredString(criterion, `workflow work context acceptance[${index}]`, MAX_ACCEPTANCE_ITEM),
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
schema_version: WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION,
|
|
37
|
+
id,
|
|
38
|
+
title: requiredString(input.title, "workflow work context title", MAX_TITLE),
|
|
39
|
+
objective: requiredString(input.objective, "workflow work context objective", MAX_OBJECTIVE),
|
|
40
|
+
acceptance,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function freezeWorkflowWorkContext(value: unknown): Readonly<WorkflowWorkContext> {
|
|
45
|
+
const normalized = normalizeWorkflowWorkContext(value);
|
|
46
|
+
Object.freeze(normalized.acceptance);
|
|
47
|
+
return Object.freeze(normalized);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isCanonicalWorkflowWorkContext(value: unknown): value is WorkflowWorkContext {
|
|
51
|
+
try {
|
|
52
|
+
const normalized = normalizeWorkflowWorkContext(value);
|
|
53
|
+
return JSON.stringify(normalized) === JSON.stringify(value);
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function requiredString(value: unknown, field: string, max: number): string {
|
|
60
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
61
|
+
throw new Error(`${field} is required`);
|
|
62
|
+
}
|
|
63
|
+
const normalized = value.trim();
|
|
64
|
+
if (normalized.length > max) throw new Error(`${field} exceeds ${max} characters`);
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|