harnery 0.9.0 → 0.11.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 (211) hide show
  1. package/README.md +1 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +6 -0
  4. package/dist/commands/agents.d.ts +24 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +244 -13
  7. package/dist/commands/backup.d.ts +5 -4
  8. package/dist/commands/backup.d.ts.map +1 -1
  9. package/dist/commands/backup.js +15 -14
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +27 -1
  12. package/dist/commands/claude-desktop.d.ts +19 -0
  13. package/dist/commands/claude-desktop.d.ts.map +1 -0
  14. package/dist/commands/claude-desktop.js +168 -0
  15. package/dist/commands/context.d.ts.map +1 -1
  16. package/dist/commands/context.js +149 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +2 -3
  19. package/dist/commands/eml.d.ts +32 -0
  20. package/dist/commands/eml.d.ts.map +1 -1
  21. package/dist/commands/eml.js +16 -3
  22. package/dist/commands/harness.d.ts +11 -0
  23. package/dist/commands/harness.d.ts.map +1 -0
  24. package/dist/commands/harness.js +118 -0
  25. package/dist/commands/presence.d.ts +9 -4
  26. package/dist/commands/presence.d.ts.map +1 -1
  27. package/dist/commands/presence.js +88 -5
  28. package/dist/commands/relay.d.ts +9 -0
  29. package/dist/commands/relay.d.ts.map +1 -0
  30. package/dist/commands/relay.js +143 -0
  31. package/dist/commands/sync.d.ts.map +1 -1
  32. package/dist/commands/sync.js +5 -0
  33. package/dist/commands/workflow.d.ts.map +1 -1
  34. package/dist/commands/workflow.js +53 -17
  35. package/dist/core/agents/canonical-emit.d.ts +15 -0
  36. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  37. package/dist/core/agents/canonical-emit.js +41 -3
  38. package/dist/core/agents/cli.js +4 -2
  39. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  40. package/dist/core/agents/render/prompt-context.js +44 -3
  41. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  42. package/dist/core/agents/render/session-context.js +26 -1
  43. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  44. package/dist/core/agents/rules/commit-conflict.js +4 -37
  45. package/dist/core/agents/session-events.d.ts.map +1 -1
  46. package/dist/core/agents/session-events.js +16 -43
  47. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  48. package/dist/core/agents/state/heartbeat-projector.js +11 -2
  49. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  50. package/dist/core/agents/state/heartbeat-writer.js +3 -3
  51. package/dist/core/agents/state/names.d.ts +38 -3
  52. package/dist/core/agents/state/names.d.ts.map +1 -1
  53. package/dist/core/agents/state/names.js +46 -7
  54. package/dist/core/agents/state/pidmap.d.ts +5 -1
  55. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  56. package/dist/core/agents/state/pidmap.js +58 -2
  57. package/dist/core/agents/state/stale-sweep.d.ts +3 -2
  58. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  59. package/dist/core/agents/state/stale-sweep.js +6 -6
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +191 -25
  63. package/dist/core/context/index.d.ts +144 -0
  64. package/dist/core/context/index.d.ts.map +1 -0
  65. package/dist/core/context/index.js +380 -0
  66. package/dist/core/harnesses/bench.d.ts +29 -0
  67. package/dist/core/harnesses/bench.d.ts.map +1 -0
  68. package/dist/core/harnesses/bench.js +151 -0
  69. package/dist/core/harnesses/index.d.ts +9 -0
  70. package/dist/core/harnesses/index.d.ts.map +1 -0
  71. package/dist/core/harnesses/index.js +4 -0
  72. package/dist/core/harnesses/profiles.d.ts +62 -0
  73. package/dist/core/harnesses/profiles.d.ts.map +1 -0
  74. package/dist/core/harnesses/profiles.js +115 -0
  75. package/dist/core/harnesses/registry.d.ts +15 -0
  76. package/dist/core/harnesses/registry.d.ts.map +1 -0
  77. package/dist/core/harnesses/registry.js +113 -0
  78. package/dist/core/harnesses/types.d.ts +64 -0
  79. package/dist/core/harnesses/types.d.ts.map +1 -0
  80. package/dist/core/harnesses/types.js +20 -0
  81. package/dist/core/hooks/cli.js +280 -63
  82. package/dist/core/hooks/events/schema.d.ts +38 -1
  83. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  84. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  85. package/dist/core/hooks/harness/events.js +3 -0
  86. package/dist/core/hooks/harness/parse.d.ts +1 -1
  87. package/dist/core/hooks/harness/parse.d.ts.map +1 -1
  88. package/dist/core/hooks/harness/parse.js +4 -0
  89. package/dist/core/presence/blob.d.ts +40 -0
  90. package/dist/core/presence/blob.d.ts.map +1 -0
  91. package/dist/core/presence/blob.js +91 -0
  92. package/dist/core/presence/git.d.ts +68 -0
  93. package/dist/core/presence/git.d.ts.map +1 -0
  94. package/dist/core/presence/git.js +173 -0
  95. package/dist/core/presence/index.d.ts +74 -0
  96. package/dist/core/presence/index.d.ts.map +1 -0
  97. package/dist/core/presence/index.js +224 -0
  98. package/dist/core/presence/relay-client.d.ts +39 -0
  99. package/dist/core/presence/relay-client.d.ts.map +1 -0
  100. package/dist/core/presence/relay-client.js +295 -0
  101. package/dist/core/presence/relay-protocol.d.ts +94 -0
  102. package/dist/core/presence/relay-protocol.d.ts.map +1 -0
  103. package/dist/core/presence/relay-protocol.js +169 -0
  104. package/dist/core/workflow/billing.d.ts +1 -1
  105. package/dist/core/workflow/billing.d.ts.map +1 -1
  106. package/dist/core/workflow/billing.js +7 -1
  107. package/dist/core/workflow/engine.d.ts +5 -0
  108. package/dist/core/workflow/engine.d.ts.map +1 -1
  109. package/dist/core/workflow/engine.js +164 -16
  110. package/dist/core/workflow/harnesses.d.ts +3 -3
  111. package/dist/core/workflow/harnesses.d.ts.map +1 -1
  112. package/dist/core/workflow/harnesses.js +8 -17
  113. package/dist/core/workflow/index.d.ts +5 -0
  114. package/dist/core/workflow/index.d.ts.map +1 -0
  115. package/dist/core/workflow/index.js +3 -0
  116. package/dist/core/workflow/proof.d.ts +42 -0
  117. package/dist/core/workflow/proof.d.ts.map +1 -0
  118. package/dist/core/workflow/proof.js +342 -0
  119. package/dist/core/workflow/spawn-claude.d.ts +4 -1
  120. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  121. package/dist/core/workflow/spawn-claude.js +39 -18
  122. package/dist/core/workflow/spawn-codex.d.ts +4 -1
  123. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  124. package/dist/core/workflow/spawn-codex.js +44 -22
  125. package/dist/core/workflow/spawn-cursor.d.ts +4 -1
  126. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  127. package/dist/core/workflow/spawn-cursor.js +35 -16
  128. package/dist/core/workflow/types.d.ts +156 -2
  129. package/dist/core/workflow/types.d.ts.map +1 -1
  130. package/dist/core/workflow/types.js +1 -1
  131. package/dist/lib/browser/client.d.ts +16 -0
  132. package/dist/lib/browser/client.d.ts.map +1 -1
  133. package/dist/lib/browser/client.js +22 -0
  134. package/dist/lib/browser/index.d.ts +1 -0
  135. package/dist/lib/browser/index.d.ts.map +1 -1
  136. package/dist/lib/browser/index.js +1 -0
  137. package/dist/lib/browser/launch-args.d.ts +21 -0
  138. package/dist/lib/browser/launch-args.d.ts.map +1 -0
  139. package/dist/lib/browser/launch-args.js +32 -0
  140. package/dist/lib/claude-desktop.d.ts +105 -0
  141. package/dist/lib/claude-desktop.d.ts.map +1 -0
  142. package/dist/lib/claude-desktop.js +217 -0
  143. package/dist/lib/docs-lint.d.ts.map +1 -1
  144. package/dist/lib/docs-lint.js +5 -2
  145. package/dist/lib/identities/assume.d.ts +51 -0
  146. package/dist/lib/identities/assume.d.ts.map +1 -0
  147. package/dist/lib/identities/assume.js +274 -0
  148. package/dist/lib/identities/index.d.ts +7 -7
  149. package/dist/lib/identities/index.d.ts.map +1 -1
  150. package/dist/lib/identities/index.js +24 -24
  151. package/dist/lib/instructions/templates.d.ts.map +1 -1
  152. package/dist/lib/instructions/templates.js +15 -2
  153. package/package.json +22 -6
  154. package/schemas/config.schema.json +88 -19
  155. package/src/commander.ts +6 -0
  156. package/src/commands/agents.ts +280 -14
  157. package/src/commands/backup.ts +17 -16
  158. package/src/commands/browse.ts +32 -0
  159. package/src/commands/claude-desktop.ts +215 -0
  160. package/src/commands/context.ts +174 -1
  161. package/src/commands/doctor.ts +2 -3
  162. package/src/commands/eml.ts +22 -4
  163. package/src/commands/harness.ts +147 -0
  164. package/src/commands/presence.ts +111 -5
  165. package/src/commands/relay.ts +160 -0
  166. package/src/commands/sync.ts +4 -0
  167. package/src/commands/workflow.ts +62 -19
  168. package/src/core/agents/canonical-emit.ts +41 -3
  169. package/src/core/agents/cli.ts +4 -2
  170. package/src/core/agents/render/prompt-context.ts +48 -3
  171. package/src/core/agents/render/session-context.ts +27 -1
  172. package/src/core/agents/rules/commit-conflict.ts +4 -33
  173. package/src/core/agents/session-events.ts +15 -39
  174. package/src/core/agents/state/heartbeat-projector.ts +10 -1
  175. package/src/core/agents/state/heartbeat-writer.ts +3 -2
  176. package/src/core/agents/state/names.ts +68 -9
  177. package/src/core/agents/state/pidmap.ts +59 -2
  178. package/src/core/agents/state/stale-sweep.ts +6 -11
  179. package/src/core/config.ts +248 -24
  180. package/src/core/context/index.ts +575 -0
  181. package/src/core/harnesses/bench.ts +214 -0
  182. package/src/core/harnesses/index.ts +31 -0
  183. package/src/core/harnesses/profiles.ts +141 -0
  184. package/src/core/harnesses/registry.ts +140 -0
  185. package/src/core/harnesses/types.ts +90 -0
  186. package/src/core/hooks/cli.ts +331 -62
  187. package/src/core/hooks/events/schema.ts +68 -0
  188. package/src/core/hooks/harness/events.ts +3 -0
  189. package/src/core/hooks/harness/parse.ts +7 -1
  190. package/src/core/presence/blob.ts +125 -0
  191. package/src/core/presence/git.ts +191 -0
  192. package/src/core/presence/index.ts +274 -0
  193. package/src/core/presence/relay-client.ts +322 -0
  194. package/src/core/presence/relay-protocol.ts +246 -0
  195. package/src/core/workflow/billing.ts +8 -2
  196. package/src/core/workflow/engine.ts +182 -16
  197. package/src/core/workflow/harnesses.ts +14 -19
  198. package/src/core/workflow/index.ts +43 -0
  199. package/src/core/workflow/proof.ts +434 -0
  200. package/src/core/workflow/spawn-claude.ts +39 -20
  201. package/src/core/workflow/spawn-codex.ts +47 -28
  202. package/src/core/workflow/spawn-cursor.ts +37 -18
  203. package/src/core/workflow/types.ts +176 -2
  204. package/src/lib/browser/client.ts +37 -0
  205. package/src/lib/browser/index.ts +1 -0
  206. package/src/lib/browser/launch-args.ts +33 -0
  207. package/src/lib/claude-desktop.ts +293 -0
  208. package/src/lib/docs-lint.ts +5 -2
  209. package/src/lib/identities/assume.ts +363 -0
  210. package/src/lib/identities/index.ts +28 -24
  211. package/src/lib/instructions/templates.ts +16 -2
@@ -21,16 +21,28 @@ import { createHash, randomBytes } from "node:crypto";
21
21
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
22
22
  import { isAbsolute, join, resolve } from "node:path";
23
23
  import { pathToFileURL } from "node:url";
24
+ import { snapshotRepo } from "../context/index.ts";
24
25
  import { type BillingProbe, probeBilling } from "./billing.ts";
26
+ import {
27
+ buildWorkflowProof,
28
+ createEvidenceRecord,
29
+ digestResult,
30
+ normalizeWorkflowMeta,
31
+ writeWorkflowProof,
32
+ } from "./proof.ts";
25
33
  import type {
26
34
  AgentOpts,
27
35
  EngineOpts,
28
36
  HarnessName,
29
37
  RunReport,
30
38
  SpawnResult,
39
+ WorkflowAgentProof,
31
40
  WorkflowContext,
41
+ WorkflowEvidenceRecord,
32
42
  WorkflowModule,
43
+ WorkflowProof,
33
44
  } from "./types.ts";
45
+ import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.ts";
34
46
  import { parseStageOutput, validateAgainstSchema } from "./validate.ts";
35
47
 
36
48
  const DEFAULT_MAX_AGENTS = 50;
@@ -39,24 +51,40 @@ const DEFAULT_MAX_ATTEMPTS = 2;
39
51
  const DEFAULT_TIMEOUT_MS = 300_000;
40
52
  const DEFAULT_MAX_TURNS = 25;
41
53
 
54
+ export class WorkflowRunError extends Error {
55
+ readonly runId: string;
56
+ readonly proofPath: string;
57
+
58
+ constructor(message: string, runId: string, proofPath: string, cause?: unknown) {
59
+ super(message, cause === undefined ? undefined : { cause });
60
+ this.name = "WorkflowRunError";
61
+ this.runId = runId;
62
+ this.proofPath = proofPath;
63
+ }
64
+ }
65
+
42
66
  export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise<RunReport> {
43
67
  const absScript = isAbsolute(scriptPath) ? scriptPath : resolve(process.cwd(), scriptPath);
44
68
  const mod = (await import(pathToFileURL(absScript).href)) as WorkflowModule;
45
69
  if (typeof mod.default !== "function") {
46
70
  throw new Error(`${scriptPath}: workflow script must \`export default async (ctx) => …\``);
47
71
  }
48
- const name = mod.meta?.name ?? scriptPath.replace(/^.*\//, "").replace(/\.[cm]?js$/, "");
72
+ const fallbackName = scriptPath.replace(/^.*\//, "").replace(/\.[cm]?js$/, "");
73
+ const meta = normalizeWorkflowMeta(mod.meta, fallbackName);
74
+ const name = meta.name;
49
75
 
50
76
  const runId = `wf-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
51
77
  const runDir = join(opts.coordRoot, ".harnery", "workflows", runId);
52
78
  mkdirSync(runDir, { recursive: true });
53
79
  const journalPath = join(runDir, "journal.jsonl");
80
+ const proofPath = join(runDir, "proof.json");
54
81
 
55
82
  const maxAgents = opts.maxAgents ?? DEFAULT_MAX_AGENTS;
56
83
  const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
57
84
  const cwd = opts.cwd ?? opts.coordRoot;
58
85
  const log = opts.onLog ?? ((line: string) => process.stderr.write(`${line}\n`));
59
86
  const defaultHarness: HarnessName = opts.defaultHarness ?? "claude-code";
87
+ const repoBefore = snapshotRepo(cwd);
60
88
 
61
89
  // Per-child fixed context overhead: children spawn in `cwd` and load its
62
90
  // repo-instructions file into their system prompt, cache-writing it once
@@ -79,10 +107,16 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
79
107
  let costUsd = 0;
80
108
  let currentStage = "";
81
109
  let agentSeq = 0;
110
+ let evidenceSeq = 0;
82
111
  const billingProbed = new Map<HarnessName, BillingProbe>();
112
+ const agentProofs = new Map<string, WorkflowAgentProof>();
113
+ const evidenceRecords: WorkflowEvidenceRecord[] = [];
114
+ const acceptanceIds = new Set(meta.acceptance.map((criterion) => criterion.id));
83
115
 
84
116
  const journal = (event: string, data: Record<string, unknown>): void => {
85
117
  const line = JSON.stringify({
118
+ schema_version: WORKFLOW_PROOF_SCHEMA_VERSION,
119
+ run_id: runId,
86
120
  ts: new Date().toISOString(),
87
121
  event,
88
122
  stage: currentStage,
@@ -119,15 +153,36 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
119
153
  }
120
154
  const id = `a${++agentSeq}`;
121
155
  const label = agentOpts.label ?? `${prompt.slice(0, 60).replace(/\s+/g, " ")}…`;
156
+ const proofLabel = agentOpts.label ?? id;
122
157
  const maxAttempts = agentOpts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
158
+ const agentProof: WorkflowAgentProof = {
159
+ id,
160
+ label: proofLabel,
161
+ stage: currentStage || undefined,
162
+ harness,
163
+ model: agentOpts.model,
164
+ status: "failed",
165
+ attempts: 0,
166
+ duration_ms: 0,
167
+ };
168
+ agentProofs.set(id, agentProof);
123
169
 
124
- // Call identity for resume: same stage + harness + model + turns + schema
170
+ // Call identity for resume: same stage + harness + model + effort + turns + schema
125
171
  // + ORIGINAL prompt → same key. Retry-mutated prompts never enter the key.
126
172
  const key = agentCallKey(currentStage, harness, agentOpts, prompt);
127
173
  const cached = resumeCache.get(key);
128
174
  if (cached) {
129
175
  agentsCached++;
130
- journal("agent.cached", { id, label, key, kind: cached.kind });
176
+ agentProof.status = "cached";
177
+ agentProof.result = digestResult(cached.value, cached.kind);
178
+ journal("agent.cached", {
179
+ id,
180
+ label,
181
+ key,
182
+ harness,
183
+ model: agentOpts.model ?? null,
184
+ kind: cached.kind,
185
+ });
131
186
  log(
132
187
  `[${name}] ${currentStage || "(no stage)"} → ${id} ${label} (cached from ${opts.resumeFrom})`,
133
188
  );
@@ -179,6 +234,7 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
179
234
  }
180
235
 
181
236
  if (agentsSpawned >= maxAgents) {
237
+ agentProof.error = `workflow agent cap reached (${maxAgents})`;
182
238
  throw new Error(
183
239
  `workflow agent cap reached (${maxAgents}); raise --max-agents deliberately if the fan-out is intended`,
184
240
  );
@@ -187,21 +243,33 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
187
243
 
188
244
  await acquire();
189
245
  try {
190
- journal("agent.start", { id, label, key, harness, model: agentOpts.model ?? null });
246
+ journal("agent.start", {
247
+ id,
248
+ label,
249
+ key,
250
+ harness,
251
+ model: agentOpts.model ?? null,
252
+ effort: agentOpts.effort ?? null,
253
+ });
191
254
  log(`[${name}] ${currentStage || "(no stage)"} → ${id} [${harness}] ${label}`);
192
255
 
193
256
  let attemptPrompt = prompt;
194
257
  let last: SpawnResult | null = null;
258
+ let agentCostUsd = 0;
195
259
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
196
260
  last = await spawner({
197
261
  prompt: attemptPrompt,
198
262
  model: agentOpts.model,
263
+ effort: agentOpts.effort,
199
264
  timeoutMs: agentOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
200
265
  maxTurns: agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
201
266
  cwd,
202
267
  runId,
203
268
  subscriptionOnly: opts.subscriptionOnly,
204
269
  });
270
+ agentProof.attempts = attempt;
271
+ agentProof.duration_ms += last.durationMs;
272
+ agentCostUsd += last.costUsd ?? 0;
205
273
  costUsd += last.costUsd ?? 0;
206
274
 
207
275
  if (!last.ok) {
@@ -209,6 +277,11 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
209
277
  continue; // spawn-level failure: retry with the original prompt
210
278
  }
211
279
  if (!agentOpts.schema) {
280
+ agentProof.status = "succeeded";
281
+ agentProof.cost_usd =
282
+ agentCostUsd > 0 || last.costUsd !== undefined ? agentCostUsd : undefined;
283
+ agentProof.session_id = last.sessionId;
284
+ agentProof.result = digestResult(last.text, "text");
212
285
  journal("agent.end", {
213
286
  id,
214
287
  key,
@@ -228,6 +301,11 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
228
301
  ? [parsed.error]
229
302
  : validateAgainstSchema(parsed.value, agentOpts.schema);
230
303
  if (problems.length === 0) {
304
+ agentProof.status = "succeeded";
305
+ agentProof.cost_usd =
306
+ agentCostUsd > 0 || last.costUsd !== undefined ? agentCostUsd : undefined;
307
+ agentProof.session_id = last.sessionId;
308
+ agentProof.result = digestResult(parsed.value, "json");
231
309
  journal("agent.end", {
232
310
  id,
233
311
  key,
@@ -253,8 +331,15 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
253
331
  const reason = last?.ok
254
332
  ? `schema validation failed after ${maxAttempts} attempt(s)`
255
333
  : (last?.error ?? "spawn failed");
334
+ agentProof.cost_usd =
335
+ agentCostUsd > 0 || last?.costUsd !== undefined ? agentCostUsd : undefined;
336
+ agentProof.session_id = last?.sessionId;
337
+ agentProof.error = reason;
256
338
  journal("agent.failed", { id, error: reason });
257
- throw new Error(`agent ${id} (${label}): ${reason}`);
339
+ throw new Error(`agent ${proofLabel}: ${reason}`);
340
+ } catch (error) {
341
+ agentProof.error ??= (error as Error).message;
342
+ throw error;
258
343
  } finally {
259
344
  release();
260
345
  }
@@ -280,12 +365,68 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
280
365
  log(`[${name}] ── stage: ${title}`);
281
366
  };
282
367
 
283
- const ctx: WorkflowContext = { agent, parallel, stage, log };
368
+ const evidence = (input: Parameters<WorkflowContext["evidence"]>[0]): string => {
369
+ const record = createEvidenceRecord({
370
+ value: input,
371
+ sequence: evidenceSeq + 1,
372
+ acceptanceIds,
373
+ stage: currentStage || undefined,
374
+ });
375
+ evidenceSeq++;
376
+ evidenceRecords.push(record);
377
+ journal("evidence.recorded", { ...record });
378
+ return record.id;
379
+ };
380
+
381
+ const ctx: WorkflowContext = { agent, parallel, stage, log, evidence };
284
382
 
285
383
  const t0 = Date.now();
286
- journal("run.start", { name, script: absScript, max_agents: maxAgents, concurrency });
384
+ const startedAt = new Date(t0).toISOString();
385
+ journal("run.start", {
386
+ name,
387
+ script: absScript,
388
+ objective: meta.objective ?? null,
389
+ acceptance: meta.acceptance,
390
+ max_agents: maxAgents,
391
+ concurrency,
392
+ });
287
393
  try {
288
394
  const result = await mod.default(ctx);
395
+ const endedAt = new Date().toISOString();
396
+ const durationMs = Date.now() - t0;
397
+ journal("run.end", {
398
+ ok: true,
399
+ agents: agentsSpawned,
400
+ cached: agentsCached,
401
+ cost_usd: round4(costUsd),
402
+ duration_ms: durationMs,
403
+ });
404
+ let proof: WorkflowProof;
405
+ try {
406
+ proof = buildWorkflowProof({
407
+ runId,
408
+ meta,
409
+ status: "succeeded",
410
+ startedAt,
411
+ endedAt,
412
+ durationMs,
413
+ journalPath,
414
+ before: repoBefore,
415
+ after: snapshotRepo(cwd),
416
+ agents: Array.from(agentProofs.values()),
417
+ evidence: evidenceRecords,
418
+ harnessEvidence: opts.harnessEvidence,
419
+ result,
420
+ });
421
+ writeWorkflowProof(proofPath, proof);
422
+ } catch (error) {
423
+ throw new WorkflowRunError(
424
+ `workflow completed but its proof packet could not be written: ${(error as Error).message}`,
425
+ runId,
426
+ proofPath,
427
+ error,
428
+ );
429
+ }
289
430
  const report: RunReport = {
290
431
  runId,
291
432
  name,
@@ -293,31 +434,55 @@ export async function runWorkflow(scriptPath: string, opts: EngineOpts): Promise
293
434
  agentsSpawned,
294
435
  agentsCached,
295
436
  costUsd: round4(costUsd),
296
- durationMs: Date.now() - t0,
437
+ durationMs,
297
438
  journalPath,
439
+ proofPath,
440
+ acceptance: proof.acceptance.summary,
298
441
  contextTokensPerChildEstimate,
299
442
  billing: Array.from(billingProbed.values()).map((p) => ({
300
443
  harness: p.harness,
301
444
  mode: opts.subscriptionOnly ? "subscription" : p.mode,
302
445
  })),
303
446
  };
304
- journal("run.end", {
305
- ok: true,
306
- agents: agentsSpawned,
307
- cached: agentsCached,
308
- cost_usd: report.costUsd,
309
- duration_ms: report.durationMs,
310
- });
311
447
  return report;
312
448
  } catch (err) {
449
+ if (err instanceof WorkflowRunError) throw err;
450
+ const endedAt = new Date().toISOString();
451
+ const durationMs = Date.now() - t0;
313
452
  journal("run.end", {
314
453
  ok: false,
315
454
  error: (err as Error).message,
316
455
  agents: agentsSpawned,
317
456
  cached: agentsCached,
318
457
  cost_usd: round4(costUsd),
458
+ duration_ms: durationMs,
319
459
  });
320
- throw err;
460
+ try {
461
+ const proof = buildWorkflowProof({
462
+ runId,
463
+ meta,
464
+ status: "failed",
465
+ startedAt,
466
+ endedAt,
467
+ durationMs,
468
+ journalPath,
469
+ before: repoBefore,
470
+ after: snapshotRepo(cwd),
471
+ agents: Array.from(agentProofs.values()),
472
+ evidence: evidenceRecords,
473
+ harnessEvidence: opts.harnessEvidence,
474
+ error: (err as Error).message,
475
+ });
476
+ writeWorkflowProof(proofPath, proof);
477
+ } catch (proofError) {
478
+ throw new WorkflowRunError(
479
+ `${(err as Error).message}; proof packet write also failed: ${(proofError as Error).message}`,
480
+ runId,
481
+ proofPath,
482
+ err,
483
+ );
484
+ }
485
+ throw new WorkflowRunError((err as Error).message, runId, proofPath, err);
321
486
  }
322
487
  }
323
488
 
@@ -333,6 +498,7 @@ function agentCallKey(
333
498
  stage,
334
499
  harness,
335
500
  agentOpts.model ?? null,
501
+ agentOpts.effort ?? null,
336
502
  agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
337
503
  agentOpts.schema ?? null,
338
504
  prompt,
@@ -7,32 +7,27 @@
7
7
  * but they do drift — keep this module the single place they live.
8
8
  */
9
9
 
10
+ import { BUILTIN_HARNESS_PROFILES } from "../harnesses/profiles.ts";
10
11
  import type { HarnessName } from "./types.ts";
11
12
 
12
- export const HARNESS_BINARIES: Record<HarnessName, string> = {
13
- "claude-code": "claude",
14
- codex: "codex",
15
- cursor: "cursor-agent",
16
- };
13
+ export const HARNESS_BINARIES: Record<string, string> = Object.fromEntries(
14
+ Object.values(BUILTIN_HARNESS_PROFILES).map((profile) => [profile.id, profile.binary]),
15
+ );
17
16
 
18
- export const HARNESS_INSTALL_HINTS: Record<HarnessName, string> = {
19
- "claude-code": "npm install -g @anthropic-ai/claude-code",
20
- codex: "npm install -g @openai/codex",
21
- cursor: "curl https://cursor.com/install -fsS | bash",
22
- };
17
+ export const HARNESS_INSTALL_HINTS: Record<string, string> = Object.fromEntries(
18
+ Object.values(BUILTIN_HARNESS_PROFILES).map((profile) => [profile.id, profile.installHint]),
19
+ );
23
20
 
24
21
  /** How to authenticate each CLI with a subscription login (the billing
25
22
  * default — see billing.ts). */
26
- export const HARNESS_LOGIN_HINTS: Record<HarnessName, string> = {
27
- "claude-code": "run `claude` and use /login",
28
- codex: "codex login",
29
- cursor: "cursor-agent login",
30
- };
23
+ export const HARNESS_LOGIN_HINTS: Record<string, string> = Object.fromEntries(
24
+ Object.values(BUILTIN_HARNESS_PROFILES).map((profile) => [profile.id, profile.loginHint]),
25
+ );
31
26
 
32
27
  /** One-line "it's missing, here's the fix" string for spawn adapters. */
33
28
  export function notFoundError(harness: HarnessName): string {
34
- return (
35
- `${HARNESS_BINARIES[harness]} CLI not found on PATH; ` +
36
- `install: ${HARNESS_INSTALL_HINTS[harness]} then authenticate: ${HARNESS_LOGIN_HINTS[harness]}`
37
- );
29
+ const binary = HARNESS_BINARIES[harness] ?? harness;
30
+ const install = HARNESS_INSTALL_HINTS[harness] ?? "install the harness CLI";
31
+ const login = HARNESS_LOGIN_HINTS[harness] ?? "authenticate the harness CLI";
32
+ return `${binary} CLI not found on PATH; ` + `install: ${install} then authenticate: ${login}`;
38
33
  }
@@ -0,0 +1,43 @@
1
+ export { runWorkflow, WorkflowRunError } from "./engine.ts";
2
+ export {
3
+ buildWorkflowProof,
4
+ createEvidenceRecord,
5
+ digestResult,
6
+ normalizeWorkflowMeta,
7
+ readWorkflowProof,
8
+ renderWorkflowProof,
9
+ rollupAcceptance,
10
+ writeWorkflowProof,
11
+ } from "./proof.ts";
12
+ export type {
13
+ AcceptanceCriterion,
14
+ AcceptanceResult,
15
+ AcceptanceStatus,
16
+ AcceptanceSummary,
17
+ AgentOpts,
18
+ EngineOpts,
19
+ EvidenceKind,
20
+ EvidenceSource,
21
+ EvidenceStatus,
22
+ HarnessEvidenceCapability,
23
+ HarnessEvidenceCoverage,
24
+ HarnessName,
25
+ ResultDigest,
26
+ RunReport,
27
+ Spawner,
28
+ SpawnRequest,
29
+ SpawnResult,
30
+ StageSchema,
31
+ WorkflowAgentProof,
32
+ WorkflowContext,
33
+ WorkflowEvidenceInput,
34
+ WorkflowEvidenceRecord,
35
+ WorkflowMeta,
36
+ WorkflowModule,
37
+ WorkflowProof,
38
+ WorkflowProofUnknown,
39
+ WorkflowRepoEvidence,
40
+ WorkflowRepoSnapshot,
41
+ WorkflowRunStatus,
42
+ } from "./types.ts";
43
+ export { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.ts";