intentdna 1.9.0 → 1.9.3

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 (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -1,3 +1,4 @@
1
+ import { LEGACY_RUN_STORE_SCHEMA_VERSION, RunStoreError, } from "../../runtime/run-store.js";
1
2
  class LifecycleUsageError extends Error {
2
3
  constructor(message) {
3
4
  super(message);
@@ -15,31 +16,44 @@ const TASK_STATES = [
15
16
  "skipped",
16
17
  "cancelled",
17
18
  ];
19
+ const ACTIVATION_STATES = [
20
+ "dormant",
21
+ "dependency_blocked",
22
+ "ready",
23
+ "claimed",
24
+ "running",
25
+ "waiting_for_human",
26
+ "succeeded",
27
+ "failed",
28
+ "skipped",
29
+ "cancelled",
30
+ ];
18
31
  function requireText(value, label) {
19
- if (!value.trim()) {
32
+ if (!value.trim())
20
33
  throw new LifecycleUsageError(`${label} is required`);
21
- }
22
34
  }
23
35
  function requireRunId(runId) {
24
36
  requireText(runId, "run_id");
25
37
  }
38
+ function validateReason(reason) {
39
+ if (reason !== null && !reason.trim()) {
40
+ throw new LifecycleUsageError("reason must not be empty");
41
+ }
42
+ }
26
43
  function validateStartRun(run) {
27
44
  requireRunId(run.run_id);
28
45
  requireText(run.workflow_id, "workflow_id");
29
46
  requireText(run.plan_digest, "plan_digest");
30
47
  requireText(run.created_at, "created_at");
31
48
  requireText(run.updated_at, "updated_at");
32
- if (run.status !== "active"
33
- || run.cancellation !== null
34
- || run.terminal !== null) {
49
+ if (run.status !== "active" || run.cancellation !== null || run.terminal !== null) {
35
50
  throw new LifecycleUsageError("start requires an active run without cancellation or terminal state");
36
51
  }
37
52
  }
38
- function statusProjection(snapshot) {
53
+ function legacyStatusProjection(snapshot) {
39
54
  const taskCounts = Object.fromEntries(TASK_STATES.map((state) => [state, 0]));
40
- for (const task of snapshot.tasks) {
55
+ for (const task of snapshot.tasks)
41
56
  taskCounts[task.state] += 1;
42
- }
43
57
  return {
44
58
  schema_version: "intentdna.run_status.v1",
45
59
  run_id: snapshot.run.run_id,
@@ -56,14 +70,36 @@ function statusProjection(snapshot) {
56
70
  updated_at: snapshot.run.updated_at,
57
71
  };
58
72
  }
73
+ export function canonicalViewStatusProjection(view) {
74
+ const activationCounts = Object.fromEntries(ACTIVATION_STATES.map((state) => [state, 0]));
75
+ for (const activation of view.activations) {
76
+ activationCounts[activation.state] += 1;
77
+ }
78
+ return {
79
+ schema_version: "intentdna.run_status.v2",
80
+ ledger_schema_version: 2,
81
+ run_id: view.run.run_id,
82
+ target: view.run.target,
83
+ plan_id: view.run.plan_id,
84
+ binding_id: view.run.run_binding.binding_id,
85
+ status: view.run.status,
86
+ record_version: view.record_version,
87
+ activation_counts: activationCounts,
88
+ attempt_count: view.attempts.length,
89
+ result_count: view.results.length,
90
+ cancellation: view.run.cancellation,
91
+ terminal: view.run.terminal,
92
+ created_at: view.run.created_at,
93
+ updated_at: view.run.updated_at,
94
+ };
95
+ }
59
96
  function durableHandoffs(snapshot) {
60
97
  return snapshot.events.flatMap((event) => {
61
98
  if (event.type !== "handoff_resolved"
62
99
  || event.step_id === null
63
100
  || event.attempt_id === null
64
- || event.worker_session_id === null) {
101
+ || event.worker_session_id === null)
65
102
  return [];
66
- }
67
103
  return [{
68
104
  event_id: event.event_id,
69
105
  sequence: event.sequence,
@@ -77,7 +113,7 @@ function durableHandoffs(snapshot) {
77
113
  }];
78
114
  });
79
115
  }
80
- function inspectionProjection(snapshot) {
116
+ function legacyInspectionProjection(snapshot) {
81
117
  return {
82
118
  schema_version: "intentdna.run_inspection.v1",
83
119
  record_version: snapshot.record_version,
@@ -92,13 +128,31 @@ function inspectionProjection(snapshot) {
92
128
  ledger_updated_at: snapshot.updated_at,
93
129
  };
94
130
  }
131
+ function canonicalInspectionProjection(view) {
132
+ return {
133
+ schema_version: "intentdna.run_inspection.v2",
134
+ ledger_schema_version: 2,
135
+ record_version: view.record_version,
136
+ run: view.run,
137
+ activations: view.activations,
138
+ attempts: view.attempts,
139
+ claims: view.claims.map((claim) => {
140
+ const { claim_token: _claimToken, ...publicClaim } = claim;
141
+ return publicClaim;
142
+ }),
143
+ events: view.events,
144
+ result_refs: view.results,
145
+ ledger_created_at: view.ledger.created_at,
146
+ ledger_updated_at: view.ledger.updated_at,
147
+ };
148
+ }
95
149
  function writeJson(value) {
96
150
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
97
151
  }
98
- function nonZeroTaskCounts(counts) {
99
- const summary = TASK_STATES
100
- .filter((state) => counts[state] > 0)
101
- .map((state) => `${state}=${counts[state]}`)
152
+ function nonZeroCounts(counts) {
153
+ const summary = Object.entries(counts)
154
+ .filter(([, count]) => count > 0)
155
+ .map(([state, count]) => `${state}=${count}`)
102
156
  .join(", ");
103
157
  return summary || "none";
104
158
  }
@@ -106,10 +160,16 @@ function writeHumanStatus(status, action) {
106
160
  if (action)
107
161
  process.stdout.write(`Action: ${action}\n`);
108
162
  process.stdout.write(`Run: ${status.run_id}\n`);
109
- process.stdout.write(`Workflow: ${status.workflow_id}\n`);
163
+ if (status.schema_version === "intentdna.run_status.v1") {
164
+ process.stdout.write(`Workflow: ${status.workflow_id}\n`);
165
+ process.stdout.write(`Tasks: ${nonZeroCounts(status.task_counts)}\n`);
166
+ }
167
+ else {
168
+ process.stdout.write(`Target: ${status.target.kind}:${status.target.key}\n`);
169
+ process.stdout.write(`Activations: ${nonZeroCounts(status.activation_counts)}\n`);
170
+ }
110
171
  process.stdout.write(`Status: ${status.status}\n`);
111
172
  process.stdout.write(`Ledger version: ${status.record_version}\n`);
112
- process.stdout.write(`Tasks: ${nonZeroTaskCounts(status.task_counts)}\n`);
113
173
  process.stdout.write(`Attempts: ${status.attempt_count}; results: ${status.result_count}\n`);
114
174
  if (status.cancellation) {
115
175
  process.stdout.write(`Cancellation: ${status.cancellation.requested_at}`
@@ -121,52 +181,93 @@ function writeHumanStatus(status, action) {
121
181
  }
122
182
  }
123
183
  function writeInspection(snapshot, json) {
124
- const projection = inspectionProjection(snapshot);
184
+ const projection = legacyInspectionProjection(snapshot);
125
185
  if (json) {
126
186
  writeJson(projection);
127
187
  return;
128
188
  }
129
- writeHumanStatus(statusProjection(snapshot));
189
+ writeHumanStatus(legacyStatusProjection(snapshot));
190
+ const attempts = projection.attempts;
130
191
  process.stdout.write("\nAttempts:\n");
131
- if (projection.attempts.length === 0) {
192
+ if (attempts.length === 0)
132
193
  process.stdout.write(" (none)\n");
133
- }
134
- for (const attempt of projection.attempts) {
194
+ for (const attempt of attempts) {
135
195
  process.stdout.write(` ${attempt.attempt_id} step=${attempt.step_id}`
136
196
  + ` worker=${attempt.worker_session_id} phase=${attempt.phase}`
137
197
  + ` pid=${attempt.process_id ?? "-"}`
138
198
  + ` outcome=${attempt.terminal_outcome?.kind ?? "none"}\n`);
139
199
  }
140
200
  process.stdout.write("\nResolved handoffs:\n");
141
- if (projection.handoffs.length === 0) {
201
+ if (projection.handoffs.length === 0)
142
202
  process.stdout.write(" (none)\n");
143
- }
144
203
  for (const handoff of projection.handoffs) {
145
204
  process.stdout.write(` attempt=${handoff.attempt_id} worker=${handoff.worker_session_id}`
146
205
  + ` binding=${JSON.stringify(handoff.binding)}\n`);
147
206
  }
148
207
  process.stdout.write("\nCommitted results:\n");
149
- if (projection.results.length === 0) {
208
+ if (projection.results.length === 0)
150
209
  process.stdout.write(" (none)\n");
151
- }
152
210
  for (const result of projection.results) {
153
211
  process.stdout.write(` ${result.result_id} attempt=${result.attempt_id}`
154
212
  + ` worker=${result.worker_session_id}`
155
213
  + ` outputs=${JSON.stringify(result.outputs)}\n`);
156
214
  }
157
215
  process.stdout.write("\nEvents:\n");
158
- if (projection.events.length === 0) {
216
+ if (projection.events.length === 0)
159
217
  process.stdout.write(" (none)\n");
218
+ for (const event of projection.events) {
219
+ process.stdout.write(` ${event.sequence} ${event.type}`
220
+ + ` step=${event.step_id ?? "-"} attempt=${event.attempt_id ?? "-"}`
221
+ + ` payload=${JSON.stringify(event.payload)}\n`);
222
+ }
223
+ }
224
+ function writeCanonicalInspection(view, json) {
225
+ const projection = canonicalInspectionProjection(view);
226
+ if (json) {
227
+ writeJson(projection);
228
+ return;
160
229
  }
230
+ writeHumanStatus(canonicalViewStatusProjection(view));
231
+ process.stdout.write("\nAttempts:\n");
232
+ if (projection.attempts.length === 0)
233
+ process.stdout.write(" (none)\n");
234
+ for (const attempt of projection.attempts) {
235
+ const executor = attempt.executor?.kind ?? "unattached";
236
+ process.stdout.write(` ${attempt.attempt_id} step=${attempt.step_id}`
237
+ + ` activation=${attempt.activation_id}`
238
+ + ` executor=${executor} phase=${attempt.phase}`
239
+ + ` outcome=${attempt.terminal_outcome?.kind ?? "none"}\n`);
240
+ }
241
+ process.stdout.write("\nResult references:\n");
242
+ if (projection.result_refs.length === 0)
243
+ process.stdout.write(" (none)\n");
244
+ for (const result of projection.result_refs) {
245
+ process.stdout.write(` ${result.result_id} activation=${result.activation_id}`
246
+ + ` attempt=${result.attempt_id} digest=${result.result_digest}\n`);
247
+ }
248
+ process.stdout.write("\nEvents:\n");
249
+ if (projection.events.length === 0)
250
+ process.stdout.write(" (none)\n");
161
251
  for (const event of projection.events) {
162
252
  process.stdout.write(` ${event.sequence} ${event.type}`
163
253
  + ` step=${event.step_id ?? "-"} attempt=${event.attempt_id ?? "-"}`
164
- + ` worker=${event.worker_session_id ?? "-"}`
165
254
  + ` payload=${JSON.stringify(event.payload)}\n`);
166
255
  }
167
256
  }
168
- function writeOperation(action, snapshot, json) {
169
- const status = statusProjection(snapshot);
257
+ function writeLegacyOperation(action, snapshot, json) {
258
+ const status = legacyStatusProjection(snapshot);
259
+ if (json) {
260
+ writeJson({
261
+ schema_version: "intentdna.run_lifecycle_operation.v1",
262
+ action,
263
+ status,
264
+ });
265
+ return;
266
+ }
267
+ writeHumanStatus(status, action);
268
+ }
269
+ export function writeCanonicalOperation(action, view, json) {
270
+ const status = canonicalViewStatusProjection(view);
170
271
  if (json) {
171
272
  writeJson({
172
273
  schema_version: "intentdna.run_lifecycle_operation.v1",
@@ -180,6 +281,15 @@ function writeOperation(action, snapshot, json) {
180
281
  function operationExitCode(snapshot) {
181
282
  return snapshot.run.status === "failed" ? 1 : 0;
182
283
  }
284
+ function canonicalOperationExitCode(view) {
285
+ return view.run.status === "failed" ? 1 : 0;
286
+ }
287
+ async function requireCanonicalService(options) {
288
+ const service = options.service ?? await options.service_factory?.();
289
+ if (!service)
290
+ throw new LifecycleUsageError("canonical run operation requires a service");
291
+ return service;
292
+ }
183
293
  async function handle(operation) {
184
294
  try {
185
295
  return await operation();
@@ -190,20 +300,13 @@ async function handle(operation) {
190
300
  return error instanceof LifecycleUsageError ? 2 : 1;
191
301
  }
192
302
  }
193
- export async function runLifecycleStart(options) {
194
- return handle(async () => {
195
- validateStartRun(options.run);
196
- await options.controller.createRun({ run: options.run });
197
- const snapshot = await options.controller.runUntilTerminal(options.run.run_id);
198
- writeOperation("start", snapshot, options.json === true);
199
- return operationExitCode(snapshot);
200
- });
201
- }
202
303
  export async function runLifecycleStatus(options) {
203
304
  return handle(async () => {
204
305
  requireRunId(options.run_id);
205
- const snapshot = await options.run_store.requireRun(options.run_id);
206
- const projection = statusProjection(snapshot);
306
+ const stored = await options.run_store.requireAnyRun(options.run_id);
307
+ const projection = stored.schema_version === LEGACY_RUN_STORE_SCHEMA_VERSION
308
+ ? legacyStatusProjection(stored)
309
+ : canonicalViewStatusProjection(await (await requireCanonicalService(options)).inspect(options.run_id));
207
310
  if (options.json === true)
208
311
  writeJson(projection);
209
312
  else
@@ -214,28 +317,59 @@ export async function runLifecycleStatus(options) {
214
317
  export async function runLifecycleInspect(options) {
215
318
  return handle(async () => {
216
319
  requireRunId(options.run_id);
217
- const snapshot = await options.run_store.requireRun(options.run_id);
218
- writeInspection(snapshot, options.json === true);
320
+ const stored = await options.run_store.requireAnyRun(options.run_id);
321
+ if (stored.schema_version === LEGACY_RUN_STORE_SCHEMA_VERSION) {
322
+ writeInspection(stored, options.json === true);
323
+ }
324
+ else {
325
+ const audit = await (await requireCanonicalService(options)).inspectAudit(options.run_id);
326
+ writeCanonicalInspection(audit, options.json === true);
327
+ }
219
328
  return 0;
220
329
  });
221
330
  }
222
331
  export async function runLifecycleResume(options) {
223
332
  return handle(async () => {
224
333
  requireRunId(options.run_id);
225
- const snapshot = await options.controller.runUntilTerminal(options.run_id);
226
- writeOperation("resume", snapshot, options.json === true);
227
- return operationExitCode(snapshot);
334
+ const stored = await options.run_store.requireAnyRun(options.run_id);
335
+ if (stored.schema_version === LEGACY_RUN_STORE_SCHEMA_VERSION) {
336
+ throw new RunStoreError("unsupported_legacy_run", `unsupported_legacy_run: legacy run ${options.run_id} cannot resume through the canonical scheduler`);
337
+ }
338
+ const service = await requireCanonicalService(options);
339
+ const view = options.drive
340
+ ? await options.drive(service, options.run_id)
341
+ : await service.resume(options.run_id);
342
+ writeCanonicalOperation("resume", view, options.json === true);
343
+ return canonicalOperationExitCode(view);
228
344
  });
229
345
  }
230
346
  export async function runLifecycleCancel(options) {
231
347
  return handle(async () => {
232
348
  requireRunId(options.run_id);
233
349
  const reason = options.reason ?? null;
234
- if (reason !== null && !reason.trim()) {
235
- throw new LifecycleUsageError("reason must not be empty");
350
+ validateReason(reason);
351
+ const stored = await options.run_store.requireAnyRun(options.run_id);
352
+ if (stored.schema_version === LEGACY_RUN_STORE_SCHEMA_VERSION) {
353
+ const snapshot = await options.run_store.cancelLegacyRun(options.run_id, reason);
354
+ writeLegacyOperation("cancel", snapshot, options.json === true);
355
+ return 0;
356
+ }
357
+ const view = await (await requireCanonicalService(options)).cancel(options.run_id, reason);
358
+ writeCanonicalOperation("cancel", view, options.json === true);
359
+ return 0;
360
+ });
361
+ }
362
+ export async function runLifecycleClose(options) {
363
+ return handle(async () => {
364
+ requireRunId(options.run_id);
365
+ const reason = options.reason ?? null;
366
+ validateReason(reason);
367
+ const stored = await options.run_store.requireAnyRun(options.run_id);
368
+ if (stored.schema_version !== LEGACY_RUN_STORE_SCHEMA_VERSION) {
369
+ throw new Error(`unsupported: canonical run ${options.run_id} does not support close`);
236
370
  }
237
- const snapshot = await options.controller.requestCancellation(options.run_id, reason);
238
- writeOperation("cancel", snapshot, options.json === true);
371
+ const snapshot = await options.run_store.closeLegacyRun(options.run_id, reason);
372
+ writeLegacyOperation("close", snapshot, options.json === true);
239
373
  return 0;
240
374
  });
241
375
  }
@@ -0,0 +1,15 @@
1
+ import type { RunView } from "../../runtime/canonical-run-service.js";
2
+ import type { CanonicalRuntimeProjection, CanonicalTarget } from "../../runtime/canonical-target-compiler.js";
3
+ import { type CompiledIRFile } from "../../runtime/plugin-adapter.js";
4
+ import type { PushDriverAttemptContext, PushDriverAttemptLifecycleHooks } from "../../runtime/push-driver.js";
5
+ export interface ObservedCanonicalRunMetadata {
6
+ readonly project_directory: string;
7
+ readonly target: CanonicalTarget;
8
+ readonly task_id: string;
9
+ readonly workflow_asset: string | null;
10
+ readonly runtime_projection: CompiledIRFile;
11
+ }
12
+ export declare function durableRuntimeProjection(projection: CanonicalRuntimeProjection): CompiledIRFile;
13
+ export declare function syncRuntimeProjection(projectDirectory: string, agentsDirectory: string, projection: CanonicalRuntimeProjection, compiled: CompiledIRFile): Promise<void>;
14
+ export declare function createRunAttemptObservers(readMetadata: (context: PushDriverAttemptContext) => ObservedCanonicalRunMetadata): PushDriverAttemptLifecycleHooks;
15
+ export declare function writeTerminalDriverFailure(view: RunView): void;
@@ -0,0 +1,210 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { DNAStateManager } from "../../hooks/state-manager.js";
4
+ import { appendEvidenceCaptureEvent, EVIDENCE_CAPTURE_SCHEMA_VERSION, writeCanonicalHookRuntimeProjection, } from "../../hooks/state.js";
5
+ import { compileAllRolesToAgentMD, writeAgentMDFiles } from "../../runtime/agent-md.js";
6
+ import { durableCanonicalRuntimeProjection } from "../../runtime/canonical-runtime-projection.js";
7
+ import { writeCompiledIR, } from "../../runtime/plugin-adapter.js";
8
+ import { WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES } from "../../runtime/workflow-runtime-manifest.js";
9
+ function logicalWorkflowStep(stepId) {
10
+ const separator = stepId.lastIndexOf("/");
11
+ const component = separator === -1 ? null : stepId.slice(0, separator);
12
+ const localStepId = separator === -1 ? stepId : stepId.slice(separator + 1);
13
+ const prefix = "worker:";
14
+ if (!localStepId.startsWith(prefix) || localStepId.length === prefix.length) {
15
+ throw new Error(`canonical worker step id is invalid: ${stepId}`);
16
+ }
17
+ return { component, step_id: localStepId.slice(prefix.length) };
18
+ }
19
+ function workflowBindingForStep(metadata, stepId) {
20
+ const manifest = metadata.runtime_projection.workflow_runtime;
21
+ if (!manifest)
22
+ throw new Error("Missing canonical workflow runtime manifest");
23
+ const logical = logicalWorkflowStep(stepId);
24
+ const workflowName = metadata.target.kind === "workflow"
25
+ ? metadata.target.key
26
+ : Object.keys(manifest.workflows).filter((name) => (name === logical.component || name.endsWith(`_${logical.component}`)))[0];
27
+ if (!workflowName || !manifest.workflows[workflowName]) {
28
+ throw new Error(`Missing runtime binding for canonical step ${stepId}`);
29
+ }
30
+ return {
31
+ manifest,
32
+ binding: manifest.workflows[workflowName],
33
+ workflow_name: workflowName,
34
+ logical_step_id: logical.step_id,
35
+ };
36
+ }
37
+ export function durableRuntimeProjection(projection) {
38
+ return durableCanonicalRuntimeProjection(projection);
39
+ }
40
+ export async function syncRuntimeProjection(projectDirectory, agentsDirectory, projection, compiled) {
41
+ await writeAgentMDFiles(compileAllRolesToAgentMD(projection.roles, projection.constraint_ir), agentsDirectory);
42
+ await writeCompiledIR(projectDirectory, compiled);
43
+ }
44
+ async function writeAttemptState(context, metadata) {
45
+ const compiled = metadata.runtime_projection;
46
+ const { manifest, binding, workflow_name: workflowName, logical_step_id: logicalStepId } = workflowBindingForStep(metadata, context.packet.step_id);
47
+ if (context.packet.workspace.isolation === "worktree") {
48
+ await writeAgentMDFiles(compileAllRolesToAgentMD(compiled.roles ?? {}, compiled.ir), join(context.packet.workspace.working_directory, ".claude", "agents"));
49
+ await writeCompiledIR(context.packet.workspace.working_directory, compiled);
50
+ }
51
+ const state = new DNAStateManager(context.packet.workspace.working_directory, context.worker_session_id);
52
+ const compiledIRHash = compiled.compiled_ir_hash ?? compiled.ir.compiled_ir_hash;
53
+ if (!compiledIRHash || manifest.compiled_ir_hash !== compiledIRHash) {
54
+ throw new Error("Canonical Hook projection is missing its pinned compiled IR hash");
55
+ }
56
+ await writeCanonicalHookRuntimeProjection(context.packet.workspace.working_directory, {
57
+ schema_version: "intentdna.canonical_hook_projection.v2",
58
+ run_id: context.packet.run_id,
59
+ step_id: context.packet.step_id,
60
+ attempt_id: context.packet.attempt_id,
61
+ worker_session_id: context.worker_session_id,
62
+ compiled_ir_hash: compiledIRHash,
63
+ compiled_ir: compiled,
64
+ lease_token: context.lease.claim_token,
65
+ owner_id: context.lease.owner_id,
66
+ });
67
+ const inputs = {};
68
+ for (const name of binding.runtime_input_refs) {
69
+ const value = WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name)
70
+ ? metadata.task_id
71
+ : context.binding.validated_inputs[name];
72
+ if (typeof value === "string")
73
+ inputs[name] = value;
74
+ }
75
+ await state.writeWorkflowState({
76
+ active: true,
77
+ workflow_asset: binding.workflow_asset,
78
+ workflow: workflowName,
79
+ current_step: logicalStepId,
80
+ current_role: context.packet.role,
81
+ iteration: context.packet.workflow_round,
82
+ session_id: context.worker_session_id,
83
+ worker_session_id: context.worker_session_id,
84
+ run_id: context.packet.run_id,
85
+ step_id: context.packet.step_id,
86
+ attempt_id: context.packet.attempt_id,
87
+ started_at: context.packet.created_at,
88
+ inputs,
89
+ resolved_variables: binding.resolved_variables,
90
+ resolved_variables_source: "compiled_manifest",
91
+ workflow_runtime_manifest_hash: manifest.manifest_hash,
92
+ workflow_runtime_compiled_ir_hash: manifest.compiled_ir_hash,
93
+ workflow_inputs_pinned: true,
94
+ });
95
+ return state;
96
+ }
97
+ async function recordEvidence(context, response, metadata) {
98
+ const { binding, workflow_name: workflowName, logical_step_id: logicalStepId } = workflowBindingForStep(metadata, context.packet.step_id);
99
+ const result = response.result;
100
+ const status = result.terminal_outcome.kind === "worker_process"
101
+ ? result.terminal_outcome.status
102
+ : result.terminal_outcome.kind;
103
+ const processOutcome = result.terminal_outcome.kind === "worker_process"
104
+ ? result.terminal_outcome.process
105
+ : null;
106
+ const startedAt = Date.parse(result.started_at);
107
+ const completedAt = Date.parse(result.completed_at);
108
+ const persisted = await appendEvidenceCaptureEvent(metadata.project_directory, {
109
+ schema_version: EVIDENCE_CAPTURE_SCHEMA_VERSION,
110
+ event_id: `c5:${randomUUID()}`,
111
+ captured_at: result.committed_at,
112
+ project_root: metadata.project_directory,
113
+ source: "dna_cli",
114
+ workflow_asset: metadata.workflow_asset ?? "unknown",
115
+ workflow_id: workflowName,
116
+ run_id: context.packet.run_id,
117
+ step_id: logicalStepId,
118
+ fact: "result",
119
+ summary: `dna run recorded ${status === "succeeded" ? "success" : "failure"} for workflow step ${logicalStepId}`,
120
+ result: status === "succeeded" ? "success" : "failure",
121
+ exit_code: processOutcome !== null && "exit_code" in processOutcome
122
+ ? processOutcome.exit_code ?? undefined
123
+ : undefined,
124
+ duration_ms: Number.isFinite(startedAt) && Number.isFinite(completedAt)
125
+ ? Math.max(0, completedAt - startedAt)
126
+ : undefined,
127
+ });
128
+ if (!persisted)
129
+ throw new Error("evidence capture event was not persisted");
130
+ }
131
+ function writeObservationFailure(kind, error) {
132
+ process.stderr.write(`Warning: post-submit ${kind} observation failed: `
133
+ + `${error instanceof Error ? error.message : String(error)}\n`);
134
+ }
135
+ function writeVerificationDiagnostics(context, response) {
136
+ const outcome = response.result.terminal_outcome;
137
+ if (outcome.kind === "worker_process"
138
+ && outcome.status === "failed"
139
+ && outcome.reason_code !== null
140
+ && [
141
+ "unsupported_output_contract",
142
+ "invalid_provider_output",
143
+ "driver_postprocess_failed",
144
+ ].includes(outcome.reason_code)) {
145
+ process.stderr.write(`Error: ${outcome.reason_code}: ${outcome.message ?? "provider output post-processing failed"}\n`);
146
+ }
147
+ const verifierById = new Map(context.run.plan.plan.verifier_plan.map((verifier) => [verifier.verifier_id, verifier]));
148
+ const logicalStepId = logicalWorkflowStep(context.packet.step_id).step_id;
149
+ for (const result of response.result.verification_results) {
150
+ if (result.passed)
151
+ continue;
152
+ const verifier = verifierById.get(result.verifier_id);
153
+ if (!verifier) {
154
+ process.stderr.write(`Verification failed: ${result.verifier_id}`
155
+ + ` reason=${result.reason_code} (step: ${logicalStepId})\n`);
156
+ }
157
+ else if (verifier.kind === "checkpoint") {
158
+ process.stderr.write(`Checkpoint failed: ${verifier.assertion}`
159
+ + `${verifier.message ? ` - ${verifier.message}` : ""}`
160
+ + ` (step: ${logicalStepId})\n`);
161
+ }
162
+ else {
163
+ process.stderr.write(`Completion failed: ${result.reason_code} (step: ${logicalStepId})\n`);
164
+ }
165
+ }
166
+ }
167
+ export function createRunAttemptObservers(readMetadata) {
168
+ const states = new Map();
169
+ return {
170
+ before_execute: async (context) => {
171
+ states.set(context.lease.attempt_id, await writeAttemptState(context, readMetadata(context)));
172
+ },
173
+ after_submit: async (context, _execution, response) => {
174
+ try {
175
+ await recordEvidence(context, response, readMetadata(context));
176
+ }
177
+ catch (error) {
178
+ writeObservationFailure("evidence", error);
179
+ }
180
+ try {
181
+ writeVerificationDiagnostics(context, response);
182
+ }
183
+ catch (error) {
184
+ writeObservationFailure("verification diagnostic", error);
185
+ }
186
+ finally {
187
+ await states.get(context.lease.attempt_id)?.cleanup().catch(() => undefined);
188
+ states.delete(context.lease.attempt_id);
189
+ }
190
+ },
191
+ on_attempt_error: async (context) => {
192
+ if (!context)
193
+ return;
194
+ await states.get(context.lease.attempt_id)?.cleanup().catch(() => undefined);
195
+ states.delete(context.lease.attempt_id);
196
+ },
197
+ };
198
+ }
199
+ export function writeTerminalDriverFailure(view) {
200
+ if (view.run.status !== "failed")
201
+ return;
202
+ const failure = [...view.attempts].reverse().find((attempt) => (attempt.phase === "terminal"
203
+ && attempt.terminal_outcome?.kind === "worker_process"
204
+ && attempt.terminal_outcome.status === "failed"
205
+ && attempt.terminal_outcome.reason_code === "driver_prelaunch_failed"));
206
+ if (failure?.terminal_outcome?.kind !== "worker_process")
207
+ return;
208
+ process.stderr.write(`Error: ${failure.terminal_outcome.reason_code}: `
209
+ + `${failure.terminal_outcome.message ?? "provider launch preparation failed"}\n`);
210
+ }
@@ -1,11 +1,6 @@
1
- /**
2
- * Intent DNA - canonical dna run command.
3
- *
4
- * The CLI compiles one workflow into Controller definitions, persists one
5
- * durable run ledger, and launches one disposable provider session per Step
6
- * attempt. Cross-Step inputs come only from committed results.
7
- */
8
- import type { TransitionRule } from "../../schema/types.js";
1
+ /** Intent DNA - canonical dna run CLI transport. */
2
+ import type { AttemptExecutionAuthorityPort, ExecutionRecoveryProbeRequest } from "../../runtime/execution-authority.js";
3
+ import { type LocalExecutionAuthorityBinding, type LocalExecutionBindRequest, type LocalExecutionPreparationRequest, type LocalExecutionRecoveryBinding, type LocalPreparedExecutionHandle } from "../../runtime/local-execution-authority.js";
9
4
  import { type RunLifecycleAction } from "./run-lifecycle.js";
10
5
  export type RunProviderKind = "claude" | "codex";
11
6
  export interface RunOptions {
@@ -13,6 +8,7 @@ export interface RunOptions {
13
8
  runId?: string;
14
9
  dnaFiles: string[];
15
10
  workflowName?: string;
11
+ controllerName?: string;
16
12
  taskId?: string;
17
13
  context?: string;
18
14
  vars?: Record<string, string>;
@@ -31,17 +27,17 @@ export interface RunOptions {
31
27
  reason?: string | null;
32
28
  json?: boolean;
33
29
  }
34
- /**
35
- * Retained as a source-compatible result shape for callers that only use the
36
- * pure legacy routing helper below. Canonical execution truth is RunStoreSnapshot.
37
- */
38
- export interface StepResult {
39
- stepId: string;
40
- status: "pass" | "fail" | "skipped";
41
- durationMs: number;
42
- resultFile?: string;
30
+ export interface RunExecutionAuthority extends AttemptExecutionAuthorityPort {
31
+ readonly authority_instance_id: string;
32
+ assertExecutionSupported(): void;
33
+ prepareExecution(request: LocalExecutionPreparationRequest): LocalPreparedExecutionHandle;
34
+ abandonPreparedExecution(handle: LocalPreparedExecutionHandle): void;
35
+ recoverPreparedExecution(request: LocalExecutionPreparationRequest): LocalPreparedExecutionHandle | null;
36
+ bindExecution(handle: LocalPreparedExecutionHandle, request: LocalExecutionBindRequest): LocalExecutionAuthorityBinding;
37
+ recoverExecution(request: ExecutionRecoveryProbeRequest): LocalExecutionRecoveryBinding;
38
+ }
39
+ export interface RunCompositionOptions {
40
+ readonly execution_authority_factory?: (rootDirectory: string) => RunExecutionAuthority;
43
41
  }
44
42
  export declare function substituteTemplate(template: string, vars: Record<string, string>): string;
45
- export declare function evaluateRunIf(condition: string | null, round: number): boolean;
46
- export declare function checkRetryNeeded(stepResults: Map<string, StepResult>, transitions: TransitionRule[]): boolean;
47
- export declare function runRun(opts: RunOptions): Promise<number>;
43
+ export declare function runRun(opts: RunOptions, composition?: RunCompositionOptions): Promise<number>;