harnery 0.12.2 → 0.13.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.
@@ -22,7 +22,12 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "n
22
22
  import { isAbsolute, join, resolve } from "node:path";
23
23
  import { pathToFileURL } from "node:url";
24
24
  import { snapshotRepo } from "../context/index.ts";
25
- import type { ExternalMutationRequest, PolicyDecision, PolicyRequest } from "../policy/index.ts";
25
+ import type {
26
+ ExternalMutationRequest,
27
+ NormalizedPolicy,
28
+ PolicyDecision,
29
+ PolicyRequest,
30
+ } from "../policy/index.ts";
26
31
  import {
27
32
  evaluatePolicy,
28
33
  normalizePolicy,
@@ -30,6 +35,7 @@ import {
30
35
  policyDigest,
31
36
  summarizePolicyRequest,
32
37
  } from "../policy/index.ts";
38
+ import { createWorkflowApproval } from "./approvals.ts";
33
39
  import { type BillingProbe, probeBilling } from "./billing.ts";
34
40
  import {
35
41
  buildWorkflowProof,
@@ -38,6 +44,13 @@ import {
38
44
  normalizeWorkflowMeta,
39
45
  writeWorkflowProof,
40
46
  } from "./proof.ts";
47
+ import {
48
+ acquireWorkflowResumeLease,
49
+ assertWorkflowRunResumable,
50
+ assertWorkflowScriptUnchanged,
51
+ workflowScriptDigest,
52
+ writeWorkflowRunManifest,
53
+ } from "./run-state.ts";
41
54
  import type {
42
55
  AgentOpts,
43
56
  EngineOpts,
@@ -73,8 +86,55 @@ export class WorkflowRunError extends Error {
73
86
  }
74
87
  }
75
88
 
89
+ export class WorkflowParkedError extends Error {
90
+ readonly runId: string;
91
+ readonly approvalId: string;
92
+ readonly journalPath: string;
93
+
94
+ constructor(message: string, runId: string, approvalId: string, journalPath: string) {
95
+ super(message);
96
+ this.name = "WorkflowParkedError";
97
+ this.runId = runId;
98
+ this.approvalId = approvalId;
99
+ this.journalPath = journalPath;
100
+ }
101
+ }
102
+
76
103
  export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise<RunReport> {
104
+ if (opts.resumeRunId && opts.resumeFrom) {
105
+ throw new Error("resumeRunId and resumeFrom are mutually exclusive");
106
+ }
107
+ const resumeState = opts.resumeRunId
108
+ ? assertWorkflowRunResumable(opts.coordRoot, opts.resumeRunId)
109
+ : undefined;
77
110
  const absScript = isAbsolute(scriptPath) ? scriptPath : resolve(process.cwd(), scriptPath);
111
+ if (resumeState) {
112
+ if (resolve(resumeState.manifest.script.path) !== resolve(absScript)) {
113
+ throw new Error(`workflow run ${opts.resumeRunId} belongs to a different script`);
114
+ }
115
+ assertWorkflowScriptUnchanged(resumeState.manifest);
116
+ }
117
+ const releaseResumeLease = opts.resumeRunId
118
+ ? acquireWorkflowResumeLease(opts.coordRoot, opts.resumeRunId)
119
+ : undefined;
120
+ try {
121
+ if (resumeState) {
122
+ // Recheck under the exclusive lease. Another process may have completed
123
+ // the run between the optimistic read and lease acquisition.
124
+ assertWorkflowRunResumable(opts.coordRoot, resumeState.manifest.run_id);
125
+ }
126
+ return await executeWorkflow(scriptPath, absScript, opts, resumeState);
127
+ } finally {
128
+ releaseResumeLease?.();
129
+ }
130
+ }
131
+
132
+ async function executeWorkflow(
133
+ scriptPath: string,
134
+ absScript: string,
135
+ opts: EngineOpts,
136
+ resumeState: ReturnType<typeof assertWorkflowRunResumable> | undefined,
137
+ ): Promise<RunReport> {
78
138
  const mod = (await import(pathToFileURL(absScript).href)) as WorkflowModule;
79
139
  if (typeof mod.default !== "function") {
80
140
  throw new Error(`${scriptPath}: workflow script must \`export default async (ctx) => …\``);
@@ -83,21 +143,61 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
83
143
  const meta = normalizeWorkflowMeta(mod.meta, fallbackName);
84
144
  const name = meta.name;
85
145
 
86
- const runId = `wf-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
146
+ const runId =
147
+ opts.resumeRunId ??
148
+ `wf-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
87
149
  const runDir = join(opts.coordRoot, ".harnery", "workflows", runId);
88
150
  mkdirSync(runDir, { recursive: true });
89
151
  const journalPath = join(runDir, "journal.jsonl");
90
152
  const proofPath = join(runDir, "proof.json");
91
153
 
92
- const maxAgents = opts.maxAgents ?? DEFAULT_MAX_AGENTS;
93
- const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
94
- const cwd = opts.cwd ?? opts.coordRoot;
154
+ const frozen = resumeState?.manifest.execution;
155
+ const maxAgents = frozen?.max_agents ?? opts.maxAgents ?? DEFAULT_MAX_AGENTS;
156
+ const concurrency = frozen?.concurrency ?? opts.concurrency ?? DEFAULT_CONCURRENCY;
157
+ assertPositiveWorkflowBound(maxAgents, "maxAgents");
158
+ assertPositiveWorkflowBound(concurrency, "concurrency");
159
+ const cwd = frozen?.cwd ?? opts.cwd ?? opts.coordRoot;
95
160
  const log = opts.onLog ?? ((line: string) => process.stderr.write(`${line}\n`));
96
- const defaultHarness: HarnessName = opts.defaultHarness ?? "claude-code";
97
- const isolation = opts.isolation ?? "shared";
98
- const networkAccess = opts.networkAccess ?? "unknown";
99
- const policy = opts.policy ? normalizePolicy(opts.policy, { baseDir: cwd }) : undefined;
100
- const repoBefore = snapshotRepo(cwd);
161
+ const defaultHarness: HarnessName =
162
+ frozen?.default_harness ?? opts.defaultHarness ?? "claude-code";
163
+ const isolation = frozen?.isolation ?? opts.isolation ?? "shared";
164
+ const networkAccess = frozen?.network_access ?? opts.networkAccess ?? "unknown";
165
+ const policy =
166
+ frozen?.policy ?? (opts.policy ? normalizePolicy(opts.policy, { baseDir: cwd }) : undefined);
167
+ const approvalMode = frozen?.approval_mode ?? opts.approvalMode ?? "deny";
168
+ const approvalAddressee = frozen?.approval_addressee ?? opts.approvalAddressee ?? "operator";
169
+ const subscriptionOnly = frozen?.subscription_only ?? Boolean(opts.subscriptionOnly);
170
+ const allowApiBilling = frozen?.allow_api_billing ?? Boolean(opts.allowApiBilling);
171
+ const repoBefore = resumeState?.manifest.repository_before ?? snapshotRepo(cwd);
172
+ const startedAt = resumeState?.manifest.started_at ?? new Date().toISOString();
173
+ const t0 = resumeState ? Date.parse(startedAt) : Date.now();
174
+
175
+ if (!resumeState) {
176
+ writeWorkflowRunManifest({
177
+ coordRoot: opts.coordRoot,
178
+ manifest: {
179
+ schema_version: 1,
180
+ run_id: runId,
181
+ name,
182
+ started_at: startedAt,
183
+ script: { path: absScript, sha256: workflowScriptDigest(absScript) },
184
+ repository_before: repoBefore,
185
+ execution: {
186
+ cwd,
187
+ default_harness: defaultHarness,
188
+ max_agents: maxAgents,
189
+ concurrency,
190
+ subscription_only: subscriptionOnly,
191
+ allow_api_billing: allowApiBilling,
192
+ approval_mode: approvalMode,
193
+ approval_addressee: approvalAddressee,
194
+ isolation,
195
+ network_access: networkAccess,
196
+ policy: policy ? (policy as NormalizedPolicy) : undefined,
197
+ },
198
+ },
199
+ });
200
+ }
101
201
 
102
202
  // Per-child fixed context overhead: children spawn in `cwd` and load its
103
203
  // repo-instructions file into their system prompt, cache-writing it once
@@ -111,13 +211,16 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
111
211
  }
112
212
 
113
213
  // Resume: journaled results of a prior run, keyed by agent-call identity.
114
- const resumeCache = opts.resumeFrom
115
- ? loadResumeCache(opts.coordRoot, opts.resumeFrom)
214
+ const resumeSource = opts.resumeRunId ?? opts.resumeFrom;
215
+ const resumeCache = resumeSource
216
+ ? loadResumeCache(opts.coordRoot, resumeSource)
116
217
  : new Map<string, { kind: "json" | "text"; value: unknown }>();
117
218
 
118
- let agentsSpawned = 0;
219
+ const history = opts.resumeRunId ? loadRunHistory(journalPath) : undefined;
220
+
221
+ let agentsSpawned = history?.agentsSpawned ?? 0;
119
222
  let agentsCached = 0;
120
- let costUsd = 0;
223
+ let costUsd = history?.costUsd ?? 0;
121
224
  let reservedCostUsd = 0;
122
225
  let currentStage = "";
123
226
  let agentSeq = 0;
@@ -126,7 +229,9 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
126
229
  const billingProbed = new Map<HarnessName, BillingProbe>();
127
230
  const agentProofs = new Map<string, WorkflowAgentProof>();
128
231
  const evidenceRecords: WorkflowEvidenceRecord[] = [];
129
- const policyDecisions: PolicyDecision[] = [];
232
+ const policyDecisions = new Map<string, PolicyDecision>(
233
+ history?.policyDecisions.map((decision) => [decision.id, decision]),
234
+ );
130
235
  const acceptanceIds = new Set(meta.acceptance.map((criterion) => criterion.id));
131
236
 
132
237
  const journal = (event: string, data: Record<string, unknown>): void => {
@@ -181,7 +286,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
181
286
  if (!policy) {
182
287
  throw new Error("external mutation authorization requires a host policy");
183
288
  }
184
- if (policyDecisions.length >= MAX_POLICY_DECISIONS) {
289
+ if (policyDecisions.size >= MAX_POLICY_DECISIONS && !policyDecisions.has(`p${policySeq + 1}`)) {
185
290
  throw new Error(
186
291
  `workflow policy decision cap reached (${MAX_POLICY_DECISIONS}); split the workflow or reduce protected actions`,
187
292
  );
@@ -204,9 +309,8 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
204
309
  let reason = evaluation.reason;
205
310
  if (evaluation.verdict === "ask") {
206
311
  resolvedBy = "fail_closed";
207
- if (!opts.resolvePolicyAsk) {
208
- reason = `${evaluation.reason}; no host approval resolver is configured`;
209
- } else {
312
+ let immediateResolution = false;
313
+ if (opts.resolvePolicyAsk) {
210
314
  try {
211
315
  const resolution = await withTimeout(
212
316
  Promise.resolve(opts.resolvePolicyAsk(request, evaluation)),
@@ -218,10 +322,57 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
218
322
  verdict = resolution.verdict;
219
323
  resolvedBy = "host";
220
324
  reason = boundedPolicyReason(resolution.reason ?? evaluation.reason);
325
+ immediateResolution = true;
221
326
  }
222
327
  } catch (error) {
223
328
  reason = `${evaluation.reason}; approval failed closed: ${(error as Error).message}`;
224
329
  }
330
+ } else {
331
+ reason = `${evaluation.reason}; no host approval resolver is configured`;
332
+ }
333
+
334
+ if (!immediateResolution && approvalMode === "park") {
335
+ const stored = createWorkflowApproval({
336
+ coordRoot: opts.coordRoot,
337
+ runId,
338
+ decisionId: id,
339
+ addressedTo: approvalAddressee,
340
+ policy: { name: policy.name, sha256: policyDigest(policy) },
341
+ request,
342
+ evaluation,
343
+ });
344
+ if (stored.approval.status === "pending") {
345
+ if (stored.created) {
346
+ journal("approval.requested", {
347
+ approval_id: stored.approval.request.id,
348
+ decision_id: id,
349
+ addressed_to: stored.approval.request.addressed_to,
350
+ request_sha256: stored.approval.request.request_sha256,
351
+ });
352
+ }
353
+ journal("run.parked", {
354
+ approval_id: stored.approval.request.id,
355
+ decision_id: id,
356
+ phase: request.phase,
357
+ action: request.action,
358
+ });
359
+ throw new WorkflowParkedError(
360
+ `workflow parked for approval ${stored.approval.request.id}: ${evaluation.reason}`,
361
+ runId,
362
+ stored.approval.request.id,
363
+ journalPath,
364
+ );
365
+ }
366
+ verdict = stored.approval.decision!.verdict;
367
+ resolvedBy = "approval";
368
+ reason = boundedPolicyReason(stored.approval.decision!.reason ?? evaluation.reason);
369
+ journal("approval.consumed", {
370
+ approval_id: stored.approval.request.id,
371
+ decision_id: id,
372
+ verdict,
373
+ actor: stored.approval.decision!.actor,
374
+ decided_at: stored.approval.decision!.decided_at,
375
+ });
225
376
  }
226
377
  }
227
378
 
@@ -237,7 +388,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
237
388
  rule_codes: evaluation.rules.map((rule) => rule.code),
238
389
  request,
239
390
  };
240
- policyDecisions.push(decision);
391
+ policyDecisions.set(decision.id, decision);
241
392
  journal("policy.resolve", { ...decision });
242
393
  if (decision.verdict === "deny") {
243
394
  throw new PolicyDeniedError(
@@ -279,6 +430,10 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
279
430
  const key = agentCallKey(currentStage, harness, agentOpts, prompt);
280
431
  const cached = resumeCache.get(key);
281
432
  if (cached) {
433
+ // Exact-run replay skips dispatch authorization because no dispatch
434
+ // occurs, but it must still reserve the original policy slot so a later
435
+ // durable ASK resolves against the same pN identity.
436
+ if (policy && opts.resumeRunId) policySeq++;
282
437
  agentsCached++;
283
438
  agentProof.status = "cached";
284
439
  agentProof.result = digestResult(cached.value, cached.kind);
@@ -291,7 +446,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
291
446
  kind: cached.kind,
292
447
  });
293
448
  log(
294
- `[${name}] ${currentStage || "(no stage)"} → ${id} ${label} (cached from ${opts.resumeFrom})`,
449
+ `[${name}] ${currentStage || "(no stage)"} → ${id} ${label} (cached from ${resumeSource})`,
295
450
  );
296
451
  return cached.value;
297
452
  }
@@ -370,12 +525,12 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
370
525
  billingProbed.set(harness, probe);
371
526
  journal("billing.probe", {
372
527
  harness,
373
- mode: opts.subscriptionOnly ? "subscription" : probe.mode,
528
+ mode: subscriptionOnly ? "subscription" : probe.mode,
374
529
  api_key_source: probe.apiKeySource,
375
530
  login: probe.login,
376
- subscription_only: Boolean(opts.subscriptionOnly),
531
+ subscription_only: subscriptionOnly,
377
532
  });
378
- if (opts.subscriptionOnly) {
533
+ if (subscriptionOnly) {
379
534
  if (probe.login === "absent") {
380
535
  throw new Error(
381
536
  `subscription-only: no stored login detected for ${harness}; ` +
@@ -383,7 +538,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
383
538
  );
384
539
  }
385
540
  log(`[billing] ${harness}: subscription-only (API-key vars scrubbed from child env)`);
386
- } else if (probe.mode === "api-key-override" && !opts.allowApiBilling) {
541
+ } else if (probe.mode === "api-key-override" && !allowApiBilling) {
387
542
  throw new Error(
388
543
  `${probe.apiKeySource} is set AND a stored ${harness} login exists — the key silently ` +
389
544
  `overrides your subscription auth, so children would bill per-token API rates. ` +
@@ -440,7 +595,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
440
595
  maxTurns: agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
441
596
  cwd,
442
597
  runId,
443
- subscriptionOnly: opts.subscriptionOnly,
598
+ subscriptionOnly,
444
599
  });
445
600
  agentProof.attempts = attempt;
446
601
  agentProof.duration_ms += last.durationMs;
@@ -462,6 +617,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
462
617
  key,
463
618
  attempts: attempt,
464
619
  cost_usd: last.costUsd,
620
+ total_cost_usd: agentCostUsd,
465
621
  duration_ms: last.durationMs,
466
622
  session_id: last.sessionId,
467
623
  result_kind: "text",
@@ -486,6 +642,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
486
642
  key,
487
643
  attempts: attempt,
488
644
  cost_usd: last.costUsd,
645
+ total_cost_usd: agentCostUsd,
489
646
  duration_ms: last.durationMs,
490
647
  session_id: last.sessionId,
491
648
  result_kind: "json",
@@ -572,21 +729,26 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
572
729
  };
573
730
 
574
731
  const ctx: WorkflowContext = { agent, parallel, stage, log, evidence, authorize };
575
-
576
- const t0 = Date.now();
577
- const startedAt = new Date(t0).toISOString();
578
- journal("run.start", {
579
- name,
580
- script: absScript,
581
- objective: meta.objective ?? null,
582
- acceptance: meta.acceptance,
583
- max_agents: maxAgents,
584
- concurrency,
585
- policy: policy ? { name: policy.name, sha256: policyDigest(policy) } : null,
586
- isolation,
587
- network_access: networkAccess,
588
- });
589
732
  try {
733
+ if (resumeState) {
734
+ journal("run.resume", {
735
+ approval_id: resumeState.approvalId,
736
+ script: absScript,
737
+ resumed_at: new Date().toISOString(),
738
+ });
739
+ } else {
740
+ journal("run.start", {
741
+ name,
742
+ script: absScript,
743
+ objective: meta.objective ?? null,
744
+ acceptance: meta.acceptance,
745
+ max_agents: maxAgents,
746
+ concurrency,
747
+ policy: policy ? { name: policy.name, sha256: policyDigest(policy) } : null,
748
+ isolation,
749
+ network_access: networkAccess,
750
+ });
751
+ }
590
752
  const result = await mod.default(ctx);
591
753
  const endedAt = new Date().toISOString();
592
754
  const durationMs = Date.now() - t0;
@@ -613,7 +775,12 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
613
775
  evidence: evidenceRecords,
614
776
  harnessEvidence: opts.harnessEvidence,
615
777
  policy: policy
616
- ? { config: policy, decisions: policyDecisions, isolation, networkAccess }
778
+ ? {
779
+ config: policy,
780
+ decisions: Array.from(policyDecisions.values()),
781
+ isolation,
782
+ networkAccess,
783
+ }
617
784
  : undefined,
618
785
  result,
619
786
  });
@@ -640,13 +807,13 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
640
807
  contextTokensPerChildEstimate,
641
808
  billing: Array.from(billingProbed.values()).map((p) => ({
642
809
  harness: p.harness,
643
- mode: opts.subscriptionOnly ? "subscription" : p.mode,
810
+ mode: subscriptionOnly ? "subscription" : p.mode,
644
811
  })),
645
812
  policy: proof.policy?.summary,
646
813
  };
647
814
  return report;
648
815
  } catch (err) {
649
- if (err instanceof WorkflowRunError) throw err;
816
+ if (err instanceof WorkflowParkedError || err instanceof WorkflowRunError) throw err;
650
817
  const endedAt = new Date().toISOString();
651
818
  const durationMs = Date.now() - t0;
652
819
  journal("run.end", {
@@ -672,7 +839,12 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
672
839
  evidence: evidenceRecords,
673
840
  harnessEvidence: opts.harnessEvidence,
674
841
  policy: policy
675
- ? { config: policy, decisions: policyDecisions, isolation, networkAccess }
842
+ ? {
843
+ config: policy,
844
+ decisions: Array.from(policyDecisions.values()),
845
+ isolation,
846
+ networkAccess,
847
+ }
676
848
  : undefined,
677
849
  error: (err as Error).message,
678
850
  });
@@ -758,6 +930,52 @@ function loadResumeCache(
758
930
  return cache;
759
931
  }
760
932
 
933
+ function loadRunHistory(path: string): {
934
+ agentsSpawned: number;
935
+ costUsd: number;
936
+ policyDecisions: PolicyDecision[];
937
+ } {
938
+ const agentIds = new Set<string>();
939
+ const agentCosts = new Map<string, number>();
940
+ const decisions = new Map<string, PolicyDecision>();
941
+ for (const line of readFileSync(path, "utf8").split("\n")) {
942
+ if (!line.trim()) continue;
943
+ const event = JSON.parse(line) as {
944
+ event?: string;
945
+ id?: string;
946
+ cost_usd?: number;
947
+ total_cost_usd?: number;
948
+ verdict?: unknown;
949
+ resolved_by?: unknown;
950
+ request?: unknown;
951
+ };
952
+ if (event.event === "agent.start" && typeof event.id === "string") {
953
+ agentIds.add(event.id);
954
+ }
955
+ if (event.event === "agent.end" && typeof event.id === "string") {
956
+ const cost = event.total_cost_usd ?? event.cost_usd;
957
+ if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) {
958
+ agentCosts.set(event.id, cost);
959
+ }
960
+ }
961
+ if (
962
+ event.event === "policy.resolve" &&
963
+ typeof event.id === "string" &&
964
+ (event.verdict === "allow" || event.verdict === "deny") &&
965
+ typeof event.resolved_by === "string" &&
966
+ event.request &&
967
+ typeof event.request === "object"
968
+ ) {
969
+ decisions.set(event.id, event as PolicyDecision);
970
+ }
971
+ }
972
+ return {
973
+ agentsSpawned: agentIds.size,
974
+ costUsd: round4(Array.from(agentCosts.values()).reduce((sum, value) => sum + value, 0)),
975
+ policyDecisions: Array.from(decisions.values()),
976
+ };
977
+ }
978
+
761
979
  function round4(n: number): number {
762
980
  return Math.round(n * 10_000) / 10_000;
763
981
  }
@@ -770,6 +988,12 @@ function assertAgentCapacity(agentsSpawned: number, maxAgents: number): void {
770
988
  }
771
989
  }
772
990
 
991
+ function assertPositiveWorkflowBound(value: number, field: string): void {
992
+ if (!Number.isSafeInteger(value) || value <= 0) {
993
+ throw new Error(`${field} must be a positive safe integer`);
994
+ }
995
+ }
996
+
773
997
  function boundedPolicyReason(value: string): string {
774
998
  const normalized = Array.from(value, (character) => {
775
999
  const code = character.charCodeAt(0);
@@ -1,4 +1,22 @@
1
- export { runWorkflow, WorkflowRunError } from "./engine.ts";
1
+ export type {
2
+ CreateWorkflowApprovalInput,
3
+ ResolveWorkflowApprovalInput,
4
+ WorkflowApproval,
5
+ WorkflowApprovalDecision,
6
+ WorkflowApprovalRequest,
7
+ WorkflowApprovalStatus,
8
+ } from "./approvals.ts";
9
+ export {
10
+ assertWorkflowRunId,
11
+ createWorkflowApproval,
12
+ listWorkflowApprovals,
13
+ readWorkflowApproval,
14
+ renderWorkflowApproval,
15
+ resolveWorkflowApproval,
16
+ WORKFLOW_APPROVAL_SCHEMA_VERSION,
17
+ workflowApprovalId,
18
+ } from "./approvals.ts";
19
+ export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.ts";
2
20
  export {
3
21
  buildWorkflowProof,
4
22
  createEvidenceRecord,
@@ -9,6 +27,17 @@ export {
9
27
  rollupAcceptance,
10
28
  writeWorkflowProof,
11
29
  } from "./proof.ts";
30
+ export type { WorkflowRunManifest } from "./run-state.ts";
31
+ export {
32
+ acquireWorkflowResumeLease,
33
+ assertWorkflowRunResumable,
34
+ assertWorkflowScriptUnchanged,
35
+ readWorkflowRunManifest,
36
+ WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION,
37
+ workflowRunManifestPath,
38
+ workflowScriptDigest,
39
+ writeWorkflowRunManifest,
40
+ } from "./run-state.ts";
12
41
  export type {
13
42
  AcceptanceCriterion,
14
43
  AcceptanceResult,
@@ -29,6 +58,7 @@ export type {
29
58
  SpawnResult,
30
59
  StageSchema,
31
60
  WorkflowAgentProof,
61
+ WorkflowApprovalMode,
32
62
  WorkflowContext,
33
63
  WorkflowEvidenceInput,
34
64
  WorkflowEvidenceRecord,