@vimhead.dev/norn-cli 0.1.0-tip.35614826665.1 → 0.1.0-tip.35724621678.1

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 (119) hide show
  1. package/assets/README.md +41 -13
  2. package/assets/docs/README.md +16 -16
  3. package/assets/docs/agents.md +23 -15
  4. package/assets/docs/cli.md +10 -8
  5. package/assets/docs/composition.md +7 -7
  6. package/assets/docs/persistence.md +22 -22
  7. package/assets/docs/projects.md +6 -6
  8. package/assets/docs/recovery.md +10 -10
  9. package/assets/docs/schemas.md +42 -36
  10. package/assets/docs/workflows.md +39 -32
  11. package/assets/examples/agent-then-analysis/input.json +3 -3
  12. package/assets/examples/agent-then-analysis/norn.project.json +2 -2
  13. package/assets/examples/agent-then-analysis/plugin.ts +37 -9
  14. package/assets/examples/caller-owned-routing/README.md +6 -6
  15. package/assets/examples/caller-owned-routing/assessment.ts +18 -4
  16. package/assets/examples/caller-owned-routing/contracts.ts +7 -2
  17. package/assets/examples/caller-owned-routing/input.json +12 -12
  18. package/assets/examples/caller-owned-routing/norn.project.json +2 -2
  19. package/assets/examples/caller-owned-routing/revision.ts +7 -2
  20. package/assets/examples/caller-owned-routing/router.ts +14 -3
  21. package/assets/examples/caller-selected-continuation/caller.ts +38 -12
  22. package/assets/examples/caller-selected-continuation/input.json +7 -7
  23. package/assets/examples/caller-selected-continuation/norn.project.json +2 -2
  24. package/assets/examples/caller-selected-continuation/producer.ts +10 -3
  25. package/assets/examples/coordinating-multiple-agents/README.md +6 -6
  26. package/assets/examples/coordinating-multiple-agents/input.json +20 -8
  27. package/assets/examples/coordinating-multiple-agents/norn.project.json +2 -2
  28. package/assets/examples/coordinating-multiple-agents/plugin.ts +165 -46
  29. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +30 -7
  30. package/assets/examples/coordinating-multiple-agents/work-queue.ts +211 -63
  31. package/assets/examples/getting-started/norn.project.json +2 -2
  32. package/assets/examples/getting-started/plugin.ts +33 -13
  33. package/assets/examples/minimal-workflow/norn.project.json +2 -2
  34. package/assets/examples/minimal-workflow/plugin.ts +12 -3
  35. package/assets/examples/shared-state/README.md +6 -6
  36. package/assets/examples/shared-state/input.json +1 -1
  37. package/assets/examples/shared-state/norn.project.json +2 -2
  38. package/assets/examples/shared-state/plugin.ts +44 -15
  39. package/assets/examples/shared-state/shared-state.ts +46 -13
  40. package/assets/examples/shared-state/state-tools.ts +68 -16
  41. package/assets/examples/worktree-development-loop/README.md +12 -12
  42. package/assets/examples/worktree-development-loop/norn.project.json +7 -7
  43. package/assets/examples/worktree-development-loop/plugin.ts +7 -1
  44. package/assets/examples/worktree-development-loop/scope.ts +4 -1
  45. package/assets/examples/worktree-development-loop/shared/commands.ts +6 -2
  46. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -4
  47. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -1
  48. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +11 -1
  49. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +6 -2
  50. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +40 -10
  51. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +5 -1
  52. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  53. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +5 -1
  54. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +33 -9
  55. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -8
  56. package/assets/package.json +1 -1
  57. package/assets/packages/cli/src/build-info.ts +4 -1
  58. package/assets/packages/cli/src/bun/cli.ts +21 -5
  59. package/assets/packages/cli/src/cli.ts +585 -170
  60. package/assets/packages/cli/src/client.ts +194 -36
  61. package/assets/packages/cli/src/documentation.ts +135 -30
  62. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  63. package/assets/packages/cli/src/internal/agent-directory.ts +7 -2
  64. package/assets/packages/cli/src/internal/agent-response-tool.ts +57 -15
  65. package/assets/packages/cli/src/internal/agents.ts +248 -60
  66. package/assets/packages/cli/src/internal/commands.ts +82 -20
  67. package/assets/packages/cli/src/internal/documentation-bundle.ts +44 -13
  68. package/assets/packages/cli/src/internal/engine.ts +396 -87
  69. package/assets/packages/cli/src/internal/errors.ts +13 -5
  70. package/assets/packages/cli/src/internal/execution-context.ts +20 -12
  71. package/assets/packages/cli/src/internal/file-coordinator.ts +95 -26
  72. package/assets/packages/cli/src/internal/file-names.ts +4 -1
  73. package/assets/packages/cli/src/internal/launch-request.ts +74 -22
  74. package/assets/packages/cli/src/internal/logs.ts +10 -2
  75. package/assets/packages/cli/src/internal/metrics.ts +247 -61
  76. package/assets/packages/cli/src/internal/pi-assets.ts +105 -23
  77. package/assets/packages/cli/src/internal/run-lease.ts +72 -22
  78. package/assets/packages/cli/src/internal/run-log.ts +49 -20
  79. package/assets/packages/cli/src/internal/run-names.ts +120 -12
  80. package/assets/packages/cli/src/internal/run-state.ts +306 -71
  81. package/assets/packages/cli/src/internal/run-store.ts +216 -53
  82. package/assets/packages/cli/src/internal/usage.ts +23 -7
  83. package/assets/packages/cli/src/internal/worker-directory.ts +9 -3
  84. package/assets/packages/cli/src/internal/workflow-registry.ts +281 -70
  85. package/assets/packages/cli/src/internal/working-directory.ts +2 -1
  86. package/assets/packages/cli/src/workflow-loader.ts +341 -74
  87. package/assets/packages/core/src/atomic-files.ts +13 -4
  88. package/assets/packages/core/src/workflow-transition.ts +13 -2
  89. package/assets/packages/sdk/src/api.ts +248 -80
  90. package/assets/packages/sdk/src/index.ts +6 -1
  91. package/assets/packages/sdk/src/schema.ts +75 -21
  92. package/assets/tests/workflow-ref.test.ts +242 -57
  93. package/bin/norn.mjs +13 -4
  94. package/dist/cli.js +344 -90
  95. package/dist/client.js +100 -22
  96. package/dist/documentation.js +102 -25
  97. package/dist/generated-build-info.d.ts +2 -2
  98. package/dist/generated-build-info.js +2 -2
  99. package/dist/internal/agent-directory.js +3 -1
  100. package/dist/internal/agent-response-tool.js +42 -11
  101. package/dist/internal/agents.js +149 -37
  102. package/dist/internal/commands.js +65 -16
  103. package/dist/internal/documentation-bundle.js +23 -11
  104. package/dist/internal/engine.js +304 -66
  105. package/dist/internal/errors.js +4 -2
  106. package/dist/internal/execution-context.js +2 -1
  107. package/dist/internal/file-coordinator.js +59 -19
  108. package/dist/internal/launch-request.js +32 -14
  109. package/dist/internal/metrics.js +138 -38
  110. package/dist/internal/pi-assets.js +70 -19
  111. package/dist/internal/run-lease.js +29 -9
  112. package/dist/internal/run-log.js +24 -12
  113. package/dist/internal/run-state.js +172 -48
  114. package/dist/internal/run-store.js +157 -45
  115. package/dist/internal/worker-directory.js +6 -2
  116. package/dist/internal/workflow-registry.js +123 -32
  117. package/dist/internal/working-directory.js +2 -1
  118. package/dist/workflow-loader.js +198 -41
  119. package/package.json +2 -2
@@ -6,14 +6,27 @@ import { dirname, join, resolve } from "node:path";
6
6
  import { Value } from "typebox/value";
7
7
  import { NornAgentResponseCollector } from "./agent-response-tool.js";
8
8
  import { errorMessage, NornRunStoppedError } from "./errors.js";
9
- import { clearRunResumeRequest, matchesRunResumeRequest, readOptionalRunResumeRequest, RESUME_START_GRACE_MS } from "./launch-request.js";
9
+ import {
10
+ clearRunResumeRequest,
11
+ matchesRunResumeRequest,
12
+ readOptionalRunResumeRequest,
13
+ RESUME_START_GRACE_MS
14
+ } from "./launch-request.js";
10
15
  import { NornRunLogs } from "./logs.js";
11
16
  import { NornRunLease } from "./run-lease.js";
12
17
  import { NornRunLogger } from "./run-log.js";
13
- import { assertRunVersion, getRunInfo, mergeInterruptedWorkflowArgs, NornRunStateStore, resolveRunRoot } from "./run-state.js";
18
+ import {
19
+ assertRunVersion,
20
+ getRunInfo,
21
+ mergeInterruptedWorkflowArgs,
22
+ NornRunStateStore,
23
+ resolveRunRoot
24
+ } from "./run-state.js";
14
25
  import { NornRunStore, runCurrentRoot } from "./run-store.js";
15
26
  import { NornExecutionContext } from "./execution-context.js";
16
- import { NornWorkflowRegistry } from "./workflow-registry.js";
27
+ import {
28
+ NornWorkflowRegistry
29
+ } from "./workflow-registry.js";
17
30
  var RUNS_DIR_NAME = ".norn";
18
31
  var MANIFEST_FILE_NAME = "manifest.json";
19
32
  var NornEngine = class {
@@ -28,7 +41,14 @@ var NornEngine = class {
28
41
  registerWorkflows(workflows) {
29
42
  const disposers = [];
30
43
  try {
31
- for (const workflow of workflows) disposers.push(this.registry.register({ workflow, config: this.input.config ?? {}, source: void 0 }));
44
+ for (const workflow of workflows)
45
+ disposers.push(
46
+ this.registry.register({
47
+ workflow,
48
+ config: this.input.config ?? {},
49
+ source: void 0
50
+ })
51
+ );
32
52
  } catch (error) {
33
53
  for (const dispose of disposers.reverse()) dispose();
34
54
  throw error;
@@ -46,7 +66,8 @@ var NornEngine = class {
46
66
  async runWorkflow(workflow, args, options) {
47
67
  const session = await this.createRunSession(workflow, args, options);
48
68
  const current = session.state.currentState().current;
49
- if (!current) throw new Error("New run is missing its initial workflow step");
69
+ if (!current)
70
+ throw new Error("New run is missing its initial workflow step");
50
71
  return this.runScheduler(session, toWorkflowStep(workflow, current));
51
72
  }
52
73
  async listRunCheckpoints(path) {
@@ -59,56 +80,98 @@ var NornEngine = class {
59
80
  const lease = await NornRunLease.acquire(runRoot);
60
81
  try {
61
82
  const pendingRequest = await readOptionalRunResumeRequest(runRoot);
62
- if (pendingRequest && Date.now() - Date.parse(pendingRequest.createdAt) < RESUME_START_GRACE_MS) throw new Error(`Resume request is still pending: ${runRoot}`);
83
+ if (pendingRequest && Date.now() - Date.parse(pendingRequest.createdAt) < RESUME_START_GRACE_MS)
84
+ throw new Error(`Resume request is still pending: ${runRoot}`);
63
85
  const runStore = await NornRunStore.open(runRoot);
64
86
  await runStore.restoreSnapshot(checkpointId, async (stagedRunRoot) => {
65
87
  const state = await NornRunStateStore.load(stagedRunRoot);
66
88
  await state.prepareForResumeAfterRollback();
67
89
  });
68
- if (pendingRequest) await clearRunResumeRequest({ runRoot, request: pendingRequest, lease });
90
+ if (pendingRequest)
91
+ await clearRunResumeRequest({
92
+ runRoot,
93
+ request: pendingRequest,
94
+ lease
95
+ });
69
96
  return getRunInfo(runRoot);
70
97
  } finally {
71
98
  await lease.release();
72
99
  }
73
100
  }
74
101
  async resumeWorkflow(path, args) {
75
- return this.resumeWorkflowExecution({ path, args, expectedRequest: void 0 });
102
+ return this.resumeWorkflowExecution({
103
+ path,
104
+ args,
105
+ expectedRequest: void 0
106
+ });
76
107
  }
77
108
  async resumeRequestedWorkflow(input) {
78
- return this.resumeWorkflowExecution({ path: input.runRoot, args: input.request.args, expectedRequest: input.request });
109
+ return this.resumeWorkflowExecution({
110
+ path: input.runRoot,
111
+ args: input.request.args,
112
+ expectedRequest: input.request
113
+ });
79
114
  }
80
115
  async resumeWorkflowExecution(input) {
81
116
  const { args, expectedRequest } = input;
82
117
  const runRoot = await resolveRunRoot(this.input.cwd, input.path);
83
118
  const initialStateStore = await NornRunStateStore.load(runRoot);
84
119
  const initialState = initialStateStore.currentState();
85
- if (initialState.status === "interrupted" && args === void 0) throw new Error(`Interrupted workflow resume requires args: ${runRoot}`);
86
- if (initialState.status === "pendingResume" && args !== void 0) throw new Error(`Pending-resume workflows do not accept args: ${runRoot}`);
87
- if (initialState.status !== "interrupted" && initialState.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
120
+ if (initialState.status === "interrupted" && args === void 0)
121
+ throw new Error(`Interrupted workflow resume requires args: ${runRoot}`);
122
+ if (initialState.status === "pendingResume" && args !== void 0)
123
+ throw new Error(
124
+ `Pending-resume workflows do not accept args: ${runRoot}`
125
+ );
126
+ if (initialState.status !== "interrupted" && initialState.status !== "pendingResume")
127
+ throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
88
128
  const lease = await NornRunLease.acquire(runRoot);
89
129
  let isLeaseOwnedByScheduler = false;
90
130
  let session;
91
131
  try {
92
132
  const pendingRequest = await readOptionalRunResumeRequest(runRoot);
93
- if (expectedRequest ? !matchesRunResumeRequest(pendingRequest, expectedRequest) : pendingRequest !== void 0) throw new Error(`Resume request changed or belongs to another executor: ${runRoot}`);
133
+ if (expectedRequest ? !matchesRunResumeRequest(pendingRequest, expectedRequest) : pendingRequest !== void 0)
134
+ throw new Error(
135
+ `Resume request changed or belongs to another executor: ${runRoot}`
136
+ );
94
137
  session = await this.openRunSession(runRoot, lease);
95
138
  const state = session.state.currentState();
96
139
  const current = state.current;
97
140
  if (!current) throw new Error(`Run is not resumable: ${runRoot}`);
98
141
  const workflow = this.registry.workflowById(current.workflowId);
99
- if (!workflow) throw new Error(`Unknown workflow for resumed run: ${current.workflowId}`);
142
+ if (!workflow)
143
+ throw new Error(
144
+ `Unknown workflow for resumed run: ${current.workflowId}`
145
+ );
100
146
  if (state.status === "interrupted") {
101
- const rawArgs = mergeInterruptedWorkflowArgs(current.args, args, current.interruption?.fields);
147
+ const rawArgs = mergeInterruptedWorkflowArgs(
148
+ current.args,
149
+ args,
150
+ current.interruption?.fields
151
+ );
102
152
  Value.Decode(workflow.args, rawArgs);
103
153
  await session.state.replaceCurrentArgs(rawArgs);
104
- await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.cwd });
154
+ await recordRunEvent(session, {
155
+ type: "run.resumed",
156
+ workflowId: workflow.id,
157
+ cwd: session.cwd
158
+ });
105
159
  const resumedCurrent = session.state.currentState().current;
106
- if (!resumedCurrent) throw new Error(`Run is not resumable: ${runRoot}`);
160
+ if (!resumedCurrent)
161
+ throw new Error(`Run is not resumable: ${runRoot}`);
107
162
  isLeaseOwnedByScheduler = true;
108
- return this.runScheduler(session, toWorkflowStep(workflow, resumedCurrent));
163
+ return this.runScheduler(
164
+ session,
165
+ toWorkflowStep(workflow, resumedCurrent)
166
+ );
109
167
  }
110
- if (state.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
111
- await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.cwd });
168
+ if (state.status !== "pendingResume")
169
+ throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
170
+ await recordRunEvent(session, {
171
+ type: "run.resumed",
172
+ workflowId: workflow.id,
173
+ cwd: session.cwd
174
+ });
112
175
  isLeaseOwnedByScheduler = true;
113
176
  return this.runScheduler(session, toWorkflowStep(workflow, current));
114
177
  } finally {
@@ -127,42 +190,124 @@ var NornEngine = class {
127
190
  for (let step = 1; step <= 1e3; step++) {
128
191
  throwIfRunAborted(session.activeRun);
129
192
  if (shouldPauseForGate(currentStep)) {
130
- const description = await this.registry.describeGate({ workflow: currentStep.workflow, execution: session.execution, paths: session.paths, args: currentStep.args, configOverride: session.state.currentState().configOverride });
131
- const interruption = { description, fields: currentStep.workflow.gate?.fields };
193
+ const description = await this.registry.describeGate({
194
+ workflow: currentStep.workflow,
195
+ execution: session.execution,
196
+ paths: session.paths,
197
+ args: currentStep.args,
198
+ configOverride: session.state.currentState().configOverride
199
+ });
200
+ const interruption = {
201
+ description,
202
+ fields: currentStep.workflow.gate?.fields
203
+ };
132
204
  await session.state.interruptCurrent(currentStep.args, interruption);
133
- await recordRunEvent(session, { type: "run.interrupted", workflowId: currentStep.workflow.id });
134
- await commitRunBoundary(session, `run interrupted: ${currentStep.workflow.id}`);
135
- return interruptedLaunchResult({ id: session.execution.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd }, currentStep.workflow, currentStep.args, interruption);
205
+ await recordRunEvent(session, {
206
+ type: "run.interrupted",
207
+ workflowId: currentStep.workflow.id
208
+ });
209
+ await commitRunBoundary(
210
+ session,
211
+ `run interrupted: ${currentStep.workflow.id}`
212
+ );
213
+ return interruptedLaunchResult(
214
+ {
215
+ id: session.execution.run.id,
216
+ name: session.state.currentState().name,
217
+ workspace: session.paths.workspace,
218
+ cwd: session.cwd
219
+ },
220
+ currentStep.workflow,
221
+ currentStep.args,
222
+ interruption
223
+ );
136
224
  }
137
225
  await session.state.startStep(toRunStateStep(currentStep));
138
226
  const stepResult = await this.executeWorkflowStep(session, currentStep);
139
227
  throwIfRunAborted(session.activeRun);
140
228
  if (stepResult.type === "complete") {
141
- await session.state.completeRun(stepResult.workflow.id, stepResult.metadata);
142
- await recordRunEvent(session, { type: "run.completed", workflowId: stepResult.workflow.id, metadata: stepResult.metadata });
143
- await commitRunBoundary(session, `run completed: ${stepResult.workflow.id}`);
144
- return { status: "completed", id: session.execution.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: stepResult.workflow.id, metadata: stepResult.metadata };
229
+ await session.state.completeRun(
230
+ stepResult.workflow.id,
231
+ stepResult.metadata
232
+ );
233
+ await recordRunEvent(session, {
234
+ type: "run.completed",
235
+ workflowId: stepResult.workflow.id,
236
+ metadata: stepResult.metadata
237
+ });
238
+ await commitRunBoundary(
239
+ session,
240
+ `run completed: ${stepResult.workflow.id}`
241
+ );
242
+ return {
243
+ status: "completed",
244
+ id: session.execution.run.id,
245
+ name: session.state.currentState().name,
246
+ workspace: session.paths.workspace,
247
+ cwd: session.cwd,
248
+ workflowId: stepResult.workflow.id,
249
+ metadata: stepResult.metadata
250
+ };
145
251
  }
146
252
  if (stepResult.type === "fail") {
147
- await session.state.failRun(stepResult.workflow.id, stepResult.metadata);
148
- await recordRunEvent(session, { type: "run.failed", workflowId: stepResult.workflow.id, metadata: stepResult.metadata });
149
- return { status: "failed", id: session.execution.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: stepResult.workflow.id, metadata: stepResult.metadata };
253
+ await session.state.failRun(
254
+ stepResult.workflow.id,
255
+ stepResult.metadata
256
+ );
257
+ await recordRunEvent(session, {
258
+ type: "run.failed",
259
+ workflowId: stepResult.workflow.id,
260
+ metadata: stepResult.metadata
261
+ });
262
+ return {
263
+ status: "failed",
264
+ id: session.execution.run.id,
265
+ name: session.state.currentState().name,
266
+ workspace: session.paths.workspace,
267
+ cwd: session.cwd,
268
+ workflowId: stepResult.workflow.id,
269
+ metadata: stepResult.metadata
270
+ };
150
271
  }
151
272
  const nextStep = this.nextWorkflowStep(stepResult);
152
- await session.state.completeWithNext(currentStep.workflow.id, toRunStateStep(nextStep));
153
- await recordRunEvent(session, { type: "run.transitioned", fromWorkflowId: currentStep.workflow.id, toWorkflowId: nextStep.workflow.id });
154
- await commitRunBoundary(session, `transition: ${currentStep.workflow.id} -> ${nextStep.workflow.id}`);
273
+ await session.state.completeWithNext(
274
+ currentStep.workflow.id,
275
+ toRunStateStep(nextStep)
276
+ );
277
+ await recordRunEvent(session, {
278
+ type: "run.transitioned",
279
+ fromWorkflowId: currentStep.workflow.id,
280
+ toWorkflowId: nextStep.workflow.id
281
+ });
282
+ await commitRunBoundary(
283
+ session,
284
+ `transition: ${currentStep.workflow.id} -> ${nextStep.workflow.id}`
285
+ );
155
286
  currentStep = nextStep;
156
287
  }
157
288
  throw new Error("Run exceeded 1000 scheduler steps");
158
289
  } catch (error) {
159
290
  if (isRunStopped(session, error)) {
160
291
  await session.state.stopCurrent();
161
- await recordRunEvent(session, { type: "run.stopped", workflowId: currentStep.workflow.id });
162
- return { status: "stopped", id: session.execution.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: currentStep.workflow.id };
292
+ await recordRunEvent(session, {
293
+ type: "run.stopped",
294
+ workflowId: currentStep.workflow.id
295
+ });
296
+ return {
297
+ status: "stopped",
298
+ id: session.execution.run.id,
299
+ name: session.state.currentState().name,
300
+ workspace: session.paths.workspace,
301
+ cwd: session.cwd,
302
+ workflowId: currentStep.workflow.id
303
+ };
163
304
  }
164
305
  await session.state.failCurrent(errorMessage(error));
165
- await recordRunEvent(session, { type: "run.failed", workflowId: currentStep.workflow.id, error: errorMessage(error) });
306
+ await recordRunEvent(session, {
307
+ type: "run.failed",
308
+ workflowId: currentStep.workflow.id,
309
+ error: errorMessage(error)
310
+ });
166
311
  throw error;
167
312
  } finally {
168
313
  await session.lease.release();
@@ -173,13 +318,40 @@ var NornEngine = class {
173
318
  const startedAtMs = Date.now();
174
319
  try {
175
320
  await assertWorkspaceBoundary(session, session.paths.workspace);
176
- await recordRunEvent(session, { type: "workflow.started", workflowId: step.workflow.id });
177
- const result = await this.registry.execute({ workflow: step.workflow, execution: session.execution, paths: session.paths, args: step.args, configOverride: session.state.currentState().configOverride });
321
+ await recordRunEvent(session, {
322
+ type: "workflow.started",
323
+ workflowId: step.workflow.id
324
+ });
325
+ const result = await this.registry.execute({
326
+ workflow: step.workflow,
327
+ execution: session.execution,
328
+ paths: session.paths,
329
+ args: step.args,
330
+ configOverride: session.state.currentState().configOverride
331
+ });
178
332
  await assertWorkspaceBoundary(session, session.paths.workspace);
179
333
  const durationMs = Date.now() - startedAtMs;
180
- if (result.type === "complete") await recordRunEvent(session, { type: "workflow.completed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
181
- else if (result.type === "fail") await recordRunEvent(session, { type: "workflow.failed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
182
- else await recordRunEvent(session, { type: "workflow.transitioned", fromWorkflowId: step.workflow.id, toWorkflowId: result.workflowId, durationMs });
334
+ if (result.type === "complete")
335
+ await recordRunEvent(session, {
336
+ type: "workflow.completed",
337
+ workflowId: result.workflow.id,
338
+ durationMs,
339
+ metadata: result.metadata
340
+ });
341
+ else if (result.type === "fail")
342
+ await recordRunEvent(session, {
343
+ type: "workflow.failed",
344
+ workflowId: result.workflow.id,
345
+ durationMs,
346
+ metadata: result.metadata
347
+ });
348
+ else
349
+ await recordRunEvent(session, {
350
+ type: "workflow.transitioned",
351
+ fromWorkflowId: step.workflow.id,
352
+ toWorkflowId: result.workflowId,
353
+ durationMs
354
+ });
183
355
  return result;
184
356
  } catch (error) {
185
357
  await recordRunEvent(session, {
@@ -206,7 +378,10 @@ var NornEngine = class {
206
378
  const runRoot = defaultRunRoot(this.input.cwd, id);
207
379
  const currentRoot = runCurrentRoot(runRoot);
208
380
  const workspace = join(currentRoot, "workspace");
209
- const paths = { project: resolve(this.input.cwd), workspace };
381
+ const paths = {
382
+ project: resolve(this.input.cwd),
383
+ workspace
384
+ };
210
385
  const cwd = process.cwd();
211
386
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
212
387
  await mkdir(runRoot, { recursive: true });
@@ -215,16 +390,26 @@ var NornEngine = class {
215
390
  try {
216
391
  const runStore = await NornRunStore.initialize(runRoot);
217
392
  await mkdir(workspace, { recursive: true });
218
- const logger = new NornRunLogger({ manifestPath: join(currentRoot, MANIFEST_FILE_NAME), files: createRunFileCoordinator(runRoot), manifest: {
393
+ const logger = new NornRunLogger({
394
+ manifestPath: join(currentRoot, MANIFEST_FILE_NAME),
395
+ files: createRunFileCoordinator(runRoot),
396
+ manifest: {
397
+ id,
398
+ name,
399
+ workflowId: workflow.id,
400
+ runRoot,
401
+ workspace,
402
+ initialCwd: cwd,
403
+ startedAt
404
+ }
405
+ });
406
+ const execution = await this.buildExecutionContext({
219
407
  id,
220
- name,
221
- workflowId: workflow.id,
222
- runRoot,
223
- workspace,
224
- initialCwd: cwd,
225
- startedAt
226
- } });
227
- const execution = await this.buildExecutionContext({ id, currentRoot, paths, signal: activeRun.controller.signal, logger });
408
+ currentRoot,
409
+ paths,
410
+ signal: activeRun.controller.signal,
411
+ logger
412
+ });
228
413
  const state = await NornRunStateStore.create(runRoot, {
229
414
  projectRoot: paths.project,
230
415
  id,
@@ -241,8 +426,23 @@ var NornEngine = class {
241
426
  },
242
427
  startedAt
243
428
  });
244
- const session = { runRoot, cwd, execution, paths, state, lease, runStore, logger, activeRun };
245
- await logger.record({ type: "run.started", workflowId: workflow.id, cwd, workspace });
429
+ const session = {
430
+ runRoot,
431
+ cwd,
432
+ execution,
433
+ paths,
434
+ state,
435
+ lease,
436
+ runStore,
437
+ logger,
438
+ activeRun
439
+ };
440
+ await logger.record({
441
+ type: "run.started",
442
+ workflowId: workflow.id,
443
+ cwd,
444
+ workspace
445
+ });
246
446
  await commitRunBoundary(session, `run started: ${workflow.id}`);
247
447
  return session;
248
448
  } catch (error) {
@@ -256,13 +456,36 @@ var NornEngine = class {
256
456
  try {
257
457
  const state = await NornRunStateStore.load(runRoot);
258
458
  const currentState = state.currentState();
259
- if (currentState.status === "completed") throw new Error(`Run is already completed: ${runRoot}`);
459
+ if (currentState.status === "completed")
460
+ throw new Error(`Run is already completed: ${runRoot}`);
260
461
  const runStore = await NornRunStore.open(runRoot);
261
462
  const currentRoot = runCurrentRoot(runRoot);
262
- const logger = await NornRunLogger.load(join(currentRoot, MANIFEST_FILE_NAME), createRunFileCoordinator(runRoot));
263
- const paths = { project: currentState.projectRoot ?? resolve(runRoot, "../../.."), workspace: currentState.workspace };
264
- const execution = await this.buildExecutionContext({ id: currentState.id, currentRoot, paths, signal: activeRun.controller.signal, logger });
265
- return { runRoot, cwd: process.cwd(), execution, paths, state, lease, runStore, logger, activeRun };
463
+ const logger = await NornRunLogger.load(
464
+ join(currentRoot, MANIFEST_FILE_NAME),
465
+ createRunFileCoordinator(runRoot)
466
+ );
467
+ const paths = {
468
+ project: currentState.projectRoot ?? resolve(runRoot, "../../.."),
469
+ workspace: currentState.workspace
470
+ };
471
+ const execution = await this.buildExecutionContext({
472
+ id: currentState.id,
473
+ currentRoot,
474
+ paths,
475
+ signal: activeRun.controller.signal,
476
+ logger
477
+ });
478
+ return {
479
+ runRoot,
480
+ cwd: process.cwd(),
481
+ execution,
482
+ paths,
483
+ state,
484
+ lease,
485
+ runStore,
486
+ logger,
487
+ activeRun
488
+ };
266
489
  } catch (error) {
267
490
  this.failActiveRun(runRoot, activeRun);
268
491
  throw error;
@@ -284,7 +507,8 @@ var NornEngine = class {
284
507
  });
285
508
  }
286
509
  startActiveRun(runRoot) {
287
- if (this.activeRuns.has(runRoot)) throw new Error(`Run is already active in this engine: ${runRoot}`);
510
+ if (this.activeRuns.has(runRoot))
511
+ throw new Error(`Run is already active in this engine: ${runRoot}`);
288
512
  const controller = new AbortController();
289
513
  let finish;
290
514
  const finished = new Promise((resolvePromise) => {
@@ -292,7 +516,10 @@ var NornEngine = class {
292
516
  });
293
517
  const abortFromParent = () => controller.abort(this.input.signal?.reason);
294
518
  if (this.input.signal?.aborted) abortFromParent();
295
- else this.input.signal?.addEventListener("abort", abortFromParent, { once: true });
519
+ else
520
+ this.input.signal?.addEventListener("abort", abortFromParent, {
521
+ once: true
522
+ });
296
523
  const activeRun = {
297
524
  controller,
298
525
  finished,
@@ -309,7 +536,8 @@ var NornEngine = class {
309
536
  activeRun.finish();
310
537
  }
311
538
  failActiveRun(runRoot, activeRun) {
312
- if (this.activeRuns.get(runRoot) === activeRun) this.activeRuns.delete(runRoot);
539
+ if (this.activeRuns.get(runRoot) === activeRun)
540
+ this.activeRuns.delete(runRoot);
313
541
  activeRun.dispose();
314
542
  activeRun.finish();
315
543
  }
@@ -340,14 +568,19 @@ function throwIfRunAborted(activeRun) {
340
568
  if (!activeRun.controller.signal.aborted) return;
341
569
  const reason = activeRun.controller.signal.reason;
342
570
  if (reason instanceof Error) throw reason;
343
- throw new Error(typeof reason === "string" && reason.length > 0 ? reason : "Run aborted");
571
+ throw new Error(
572
+ typeof reason === "string" && reason.length > 0 ? reason : "Run aborted"
573
+ );
344
574
  }
345
575
  function isRunStopped(session, error) {
346
576
  return error instanceof NornRunStoppedError || session.activeRun.controller.signal.reason instanceof NornRunStoppedError;
347
577
  }
348
578
  function interruptedLaunchResult(run, workflow, args, interruption) {
349
579
  if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
350
- if (!interruption.description) throw new Error(`Interrupted workflow is missing description: ${workflow.id}`);
580
+ if (!interruption.description)
581
+ throw new Error(
582
+ `Interrupted workflow is missing description: ${workflow.id}`
583
+ );
351
584
  return {
352
585
  status: "interrupted",
353
586
  id: run.id,
@@ -355,7 +588,12 @@ function interruptedLaunchResult(run, workflow, args, interruption) {
355
588
  workspace: run.workspace,
356
589
  cwd: run.cwd,
357
590
  workflowId: workflow.id,
358
- interruption: { workflowId: workflow.id, args, description: interruption.description, fields: interruption.fields }
591
+ interruption: {
592
+ workflowId: workflow.id,
593
+ args,
594
+ description: interruption.description,
595
+ fields: interruption.fields
596
+ }
359
597
  };
360
598
  }
361
599
  async function recordRunEvent(session, event) {
@@ -8,8 +8,10 @@ var NornProjectLoadError = class extends Error {
8
8
  const details = input.diagnostics.map(
9
9
  (diagnostic) => `${diagnostic.stage}: ${diagnostic.modulePath} (declared in ${diagnostic.configPath}${diagnostic.workflowId ? `; workflow ${diagnostic.workflowId}` : ""}): ${diagnostic.message}`
10
10
  );
11
- super(`Norn project is incomplete (${input.diagnostics.length} diagnostics); execution is blocked.
12
- ${details.join("\n")}`);
11
+ super(
12
+ `Norn project is incomplete (${input.diagnostics.length} diagnostics); execution is blocked.
13
+ ${details.join("\n")}`
14
+ );
13
15
  this.name = "NornProjectLoadError";
14
16
  this.diagnostics = input.diagnostics;
15
17
  }
@@ -1,6 +1,7 @@
1
1
  // ../core/src/workflow-transition.ts
2
2
  function createWorkflowTransition(input) {
3
- if (typeof input.workflowId !== "string" || input.workflowId.trim().length === 0) throw new Error("Workflow transition requires a nonempty workflow ID");
3
+ if (typeof input.workflowId !== "string" || input.workflowId.trim().length === 0)
4
+ throw new Error("Workflow transition requires a nonempty workflow ID");
4
5
  return { type: "next", workflowId: input.workflowId, args: input.args };
5
6
  }
6
7