harnery 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +1 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +2 -0
  4. package/dist/commands/work.d.ts +4 -0
  5. package/dist/commands/work.d.ts.map +1 -0
  6. package/dist/commands/work.js +281 -0
  7. package/dist/core/work/index.d.ts +3 -0
  8. package/dist/core/work/index.d.ts.map +1 -0
  9. package/dist/core/work/index.js +2 -0
  10. package/dist/core/work/read.d.ts +2 -0
  11. package/dist/core/work/read.d.ts.map +1 -0
  12. package/dist/core/work/read.js +1 -0
  13. package/dist/core/work/runner.d.ts +10 -0
  14. package/dist/core/work/runner.d.ts.map +1 -0
  15. package/dist/core/work/runner.js +63 -0
  16. package/dist/core/work/state.d.ts +109 -0
  17. package/dist/core/work/state.d.ts.map +1 -0
  18. package/dist/core/work/state.js +698 -0
  19. package/dist/core/workflow/engine.d.ts.map +1 -1
  20. package/dist/core/workflow/engine.js +20 -1
  21. package/dist/core/workflow/proof.d.ts +1 -0
  22. package/dist/core/workflow/proof.d.ts.map +1 -1
  23. package/dist/core/workflow/proof.js +1 -0
  24. package/dist/core/workflow/run-state.d.ts +1 -0
  25. package/dist/core/workflow/run-state.d.ts.map +1 -1
  26. package/dist/core/workflow/run-state.js +2 -0
  27. package/dist/core/workflow/types.d.ts +7 -0
  28. package/dist/core/workflow/types.d.ts.map +1 -1
  29. package/package.json +11 -1
  30. package/src/commander.ts +2 -0
  31. package/src/commands/work.ts +367 -0
  32. package/src/core/work/index.ts +24 -0
  33. package/src/core/work/read.ts +15 -0
  34. package/src/core/work/runner.ts +81 -0
  35. package/src/core/work/state.ts +900 -0
  36. package/src/core/workflow/engine.ts +21 -1
  37. package/src/core/workflow/proof.ts +2 -0
  38. package/src/core/workflow/run-state.ts +3 -0
  39. package/src/core/workflow/types.ts +7 -0
@@ -35,7 +35,7 @@ import {
35
35
  policyDigest,
36
36
  summarizePolicyRequest,
37
37
  } from "../policy/index.ts";
38
- import { createWorkflowApproval } from "./approvals.ts";
38
+ import { assertWorkflowRunId, createWorkflowApproval } from "./approvals.ts";
39
39
  import { type BillingProbe, probeBilling } from "./billing.ts";
40
40
  import {
41
41
  buildWorkflowProof,
@@ -101,12 +101,26 @@ export class WorkflowParkedError extends Error {
101
101
  }
102
102
 
103
103
  export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise<RunReport> {
104
+ if (opts.runId && opts.resumeRunId) {
105
+ throw new Error("runId and resumeRunId are mutually exclusive");
106
+ }
107
+ if (opts.runId) assertWorkflowRunId(opts.runId);
108
+ if (opts.workItemId && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(opts.workItemId)) {
109
+ throw new Error(`invalid work item id ${JSON.stringify(opts.workItemId)}`);
110
+ }
104
111
  if (opts.resumeRunId && opts.resumeFrom) {
105
112
  throw new Error("resumeRunId and resumeFrom are mutually exclusive");
106
113
  }
107
114
  const resumeState = opts.resumeRunId
108
115
  ? assertWorkflowRunResumable(opts.coordRoot, opts.resumeRunId)
109
116
  : undefined;
117
+ if (
118
+ resumeState !== undefined &&
119
+ opts.workItemId !== undefined &&
120
+ resumeState.manifest.work_item_id !== opts.workItemId
121
+ ) {
122
+ throw new Error(`workflow run ${opts.resumeRunId} belongs to a different work item`);
123
+ }
110
124
  const absScript = isAbsolute(scriptPath) ? scriptPath : resolve(process.cwd(), scriptPath);
111
125
  if (resumeState) {
112
126
  if (resolve(resumeState.manifest.script.path) !== resolve(absScript)) {
@@ -145,6 +159,7 @@ async function executeWorkflow(
145
159
 
146
160
  const runId =
147
161
  opts.resumeRunId ??
162
+ opts.runId ??
148
163
  `wf-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
149
164
  const runDir = join(opts.coordRoot, ".harnery", "workflows", runId);
150
165
  mkdirSync(runDir, { recursive: true });
@@ -178,6 +193,7 @@ async function executeWorkflow(
178
193
  manifest: {
179
194
  schema_version: 1,
180
195
  run_id: runId,
196
+ work_item_id: opts.workItemId,
181
197
  name,
182
198
  started_at: startedAt,
183
199
  script: { path: absScript, sha256: workflowScriptDigest(absScript) },
@@ -739,6 +755,7 @@ async function executeWorkflow(
739
755
  } else {
740
756
  journal("run.start", {
741
757
  name,
758
+ work_item_id: opts.workItemId ?? null,
742
759
  script: absScript,
743
760
  objective: meta.objective ?? null,
744
761
  acceptance: meta.acceptance,
@@ -763,6 +780,7 @@ async function executeWorkflow(
763
780
  try {
764
781
  proof = buildWorkflowProof({
765
782
  runId,
783
+ workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
766
784
  meta,
767
785
  status: "succeeded",
768
786
  startedAt,
@@ -795,6 +813,7 @@ async function executeWorkflow(
795
813
  }
796
814
  const report: RunReport = {
797
815
  runId,
816
+ workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
798
817
  name,
799
818
  result,
800
819
  agentsSpawned,
@@ -827,6 +846,7 @@ async function executeWorkflow(
827
846
  try {
828
847
  const proof = buildWorkflowProof({
829
848
  runId,
849
+ workItemId: opts.workItemId ?? resumeState?.manifest.work_item_id,
830
850
  meta,
831
851
  status: "failed",
832
852
  startedAt,
@@ -58,6 +58,7 @@ export interface NormalizedWorkflowMeta {
58
58
 
59
59
  export interface BuildWorkflowProofInput {
60
60
  runId: string;
61
+ workItemId?: string;
61
62
  meta: NormalizedWorkflowMeta;
62
63
  status: "succeeded" | "failed";
63
64
  startedAt: string;
@@ -213,6 +214,7 @@ export function buildWorkflowProof(input: BuildWorkflowProofInput): WorkflowProo
213
214
  schema_version: WORKFLOW_PROOF_SCHEMA_VERSION,
214
215
  run: {
215
216
  id: input.runId,
217
+ work_item_id: input.workItemId,
216
218
  name: input.meta.name,
217
219
  status: input.status,
218
220
  started_at: input.startedAt,
@@ -31,6 +31,7 @@ const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
31
31
  export interface WorkflowRunManifest {
32
32
  schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
33
33
  run_id: string;
34
+ work_item_id?: string;
34
35
  name: string;
35
36
  started_at: string;
36
37
  script: { path: string; sha256: string };
@@ -97,6 +98,8 @@ export function readWorkflowRunManifest(coordRoot: string, runId: string): Workf
97
98
  if (
98
99
  manifest.schema_version !== WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION ||
99
100
  manifest.run_id !== runId ||
101
+ (manifest.work_item_id !== undefined &&
102
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(manifest.work_item_id)) ||
100
103
  typeof manifest.name !== "string" ||
101
104
  manifest.name.length === 0 ||
102
105
  manifest.name.length > 200 ||
@@ -147,6 +147,8 @@ export interface WorkflowProof {
147
147
  schema_version: typeof WORKFLOW_PROOF_SCHEMA_VERSION;
148
148
  run: {
149
149
  id: string;
150
+ /** Durable objective this execution attempt belongs to, when linked. */
151
+ work_item_id?: string;
150
152
  name: string;
151
153
  status: WorkflowRunStatus;
152
154
  started_at: string;
@@ -315,6 +317,10 @@ export interface EngineOpts {
315
317
  * approval has been resolved. The frozen run manifest supplies execution
316
318
  * options and the original repository-before snapshot. */
317
319
  resumeRunId?: string;
320
+ /** Stable id for a new run allocated by a durable-work host. */
321
+ runId?: string;
322
+ /** Durable objective this execution attempt belongs to. */
323
+ workItemId?: string;
318
324
  /** Total-agent ceiling for the run (default 50): the runaway backstop. */
319
325
  maxAgents?: number;
320
326
  /** Concurrent-subagent cap for parallel() (default 4). */
@@ -357,6 +363,7 @@ export interface EngineOpts {
357
363
 
358
364
  export interface RunReport {
359
365
  runId: string;
366
+ workItemId?: string;
360
367
  name: string;
361
368
  /** What the script's default export returned. */
362
369
  result: unknown;