@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.
- package/assets/README.md +41 -13
- package/assets/docs/README.md +16 -16
- package/assets/docs/agents.md +23 -15
- package/assets/docs/cli.md +10 -8
- package/assets/docs/composition.md +7 -7
- package/assets/docs/persistence.md +22 -22
- package/assets/docs/projects.md +6 -6
- package/assets/docs/recovery.md +10 -10
- package/assets/docs/schemas.md +42 -36
- package/assets/docs/workflows.md +39 -32
- package/assets/examples/agent-then-analysis/input.json +3 -3
- package/assets/examples/agent-then-analysis/norn.project.json +2 -2
- package/assets/examples/agent-then-analysis/plugin.ts +37 -9
- package/assets/examples/caller-owned-routing/README.md +6 -6
- package/assets/examples/caller-owned-routing/assessment.ts +18 -4
- package/assets/examples/caller-owned-routing/contracts.ts +7 -2
- package/assets/examples/caller-owned-routing/input.json +12 -12
- package/assets/examples/caller-owned-routing/norn.project.json +2 -2
- package/assets/examples/caller-owned-routing/revision.ts +7 -2
- package/assets/examples/caller-owned-routing/router.ts +14 -3
- package/assets/examples/caller-selected-continuation/caller.ts +38 -12
- package/assets/examples/caller-selected-continuation/input.json +7 -7
- package/assets/examples/caller-selected-continuation/norn.project.json +2 -2
- package/assets/examples/caller-selected-continuation/producer.ts +10 -3
- package/assets/examples/coordinating-multiple-agents/README.md +6 -6
- package/assets/examples/coordinating-multiple-agents/input.json +20 -8
- package/assets/examples/coordinating-multiple-agents/norn.project.json +2 -2
- package/assets/examples/coordinating-multiple-agents/plugin.ts +165 -46
- package/assets/examples/coordinating-multiple-agents/queue-tools.ts +30 -7
- package/assets/examples/coordinating-multiple-agents/work-queue.ts +211 -63
- package/assets/examples/getting-started/norn.project.json +2 -2
- package/assets/examples/getting-started/plugin.ts +33 -13
- package/assets/examples/minimal-workflow/norn.project.json +2 -2
- package/assets/examples/minimal-workflow/plugin.ts +12 -3
- package/assets/examples/shared-state/README.md +6 -6
- package/assets/examples/shared-state/input.json +1 -1
- package/assets/examples/shared-state/norn.project.json +2 -2
- package/assets/examples/shared-state/plugin.ts +44 -15
- package/assets/examples/shared-state/shared-state.ts +46 -13
- package/assets/examples/shared-state/state-tools.ts +68 -16
- package/assets/examples/worktree-development-loop/README.md +12 -12
- package/assets/examples/worktree-development-loop/norn.project.json +7 -7
- package/assets/examples/worktree-development-loop/plugin.ts +7 -1
- package/assets/examples/worktree-development-loop/scope.ts +4 -1
- package/assets/examples/worktree-development-loop/shared/commands.ts +6 -2
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -4
- package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -1
- package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +11 -1
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +6 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +40 -10
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +5 -1
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
- package/assets/examples/worktree-development-loop/workflows/planning/index.ts +5 -1
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +33 -9
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -8
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/build-info.ts +4 -1
- package/assets/packages/cli/src/bun/cli.ts +21 -5
- package/assets/packages/cli/src/cli.ts +585 -170
- package/assets/packages/cli/src/client.ts +194 -36
- package/assets/packages/cli/src/documentation.ts +135 -30
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/agent-directory.ts +7 -2
- package/assets/packages/cli/src/internal/agent-response-tool.ts +57 -15
- package/assets/packages/cli/src/internal/agents.ts +248 -60
- package/assets/packages/cli/src/internal/commands.ts +82 -20
- package/assets/packages/cli/src/internal/documentation-bundle.ts +44 -13
- package/assets/packages/cli/src/internal/engine.ts +396 -87
- package/assets/packages/cli/src/internal/errors.ts +13 -5
- package/assets/packages/cli/src/internal/execution-context.ts +20 -12
- package/assets/packages/cli/src/internal/file-coordinator.ts +95 -26
- package/assets/packages/cli/src/internal/file-names.ts +4 -1
- package/assets/packages/cli/src/internal/launch-request.ts +74 -22
- package/assets/packages/cli/src/internal/logs.ts +10 -2
- package/assets/packages/cli/src/internal/metrics.ts +247 -61
- package/assets/packages/cli/src/internal/pi-assets.ts +105 -23
- package/assets/packages/cli/src/internal/run-lease.ts +72 -22
- package/assets/packages/cli/src/internal/run-log.ts +49 -20
- package/assets/packages/cli/src/internal/run-names.ts +120 -12
- package/assets/packages/cli/src/internal/run-state.ts +306 -71
- package/assets/packages/cli/src/internal/run-store.ts +216 -53
- package/assets/packages/cli/src/internal/usage.ts +23 -7
- package/assets/packages/cli/src/internal/worker-directory.ts +9 -3
- package/assets/packages/cli/src/internal/workflow-registry.ts +281 -70
- package/assets/packages/cli/src/internal/working-directory.ts +2 -1
- package/assets/packages/cli/src/workflow-loader.ts +341 -74
- package/assets/packages/core/src/atomic-files.ts +13 -4
- package/assets/packages/core/src/workflow-transition.ts +13 -2
- package/assets/packages/sdk/src/api.ts +248 -80
- package/assets/packages/sdk/src/index.ts +6 -1
- package/assets/packages/sdk/src/schema.ts +75 -21
- package/assets/tests/workflow-ref.test.ts +242 -57
- package/bin/norn.mjs +13 -4
- package/dist/cli.js +344 -90
- package/dist/client.js +100 -22
- package/dist/documentation.js +102 -25
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/agent-directory.js +3 -1
- package/dist/internal/agent-response-tool.js +42 -11
- package/dist/internal/agents.js +149 -37
- package/dist/internal/commands.js +65 -16
- package/dist/internal/documentation-bundle.js +23 -11
- package/dist/internal/engine.js +304 -66
- package/dist/internal/errors.js +4 -2
- package/dist/internal/execution-context.js +2 -1
- package/dist/internal/file-coordinator.js +59 -19
- package/dist/internal/launch-request.js +32 -14
- package/dist/internal/metrics.js +138 -38
- package/dist/internal/pi-assets.js +70 -19
- package/dist/internal/run-lease.js +29 -9
- package/dist/internal/run-log.js +24 -12
- package/dist/internal/run-state.js +172 -48
- package/dist/internal/run-store.js +157 -45
- package/dist/internal/worker-directory.js +6 -2
- package/dist/internal/workflow-registry.js +123 -32
- package/dist/internal/working-directory.js +2 -1
- package/dist/workflow-loader.js +198 -41
- package/package.json +2 -2
package/dist/internal/engine.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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({
|
|
102
|
+
return this.resumeWorkflowExecution({
|
|
103
|
+
path,
|
|
104
|
+
args,
|
|
105
|
+
expectedRequest: void 0
|
|
106
|
+
});
|
|
76
107
|
}
|
|
77
108
|
async resumeRequestedWorkflow(input) {
|
|
78
|
-
return this.resumeWorkflowExecution({
|
|
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)
|
|
86
|
-
|
|
87
|
-
if (initialState.status
|
|
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)
|
|
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)
|
|
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(
|
|
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, {
|
|
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)
|
|
160
|
+
if (!resumedCurrent)
|
|
161
|
+
throw new Error(`Run is not resumable: ${runRoot}`);
|
|
107
162
|
isLeaseOwnedByScheduler = true;
|
|
108
|
-
return this.runScheduler(
|
|
163
|
+
return this.runScheduler(
|
|
164
|
+
session,
|
|
165
|
+
toWorkflowStep(workflow, resumedCurrent)
|
|
166
|
+
);
|
|
109
167
|
}
|
|
110
|
-
if (state.status !== "pendingResume")
|
|
111
|
-
|
|
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({
|
|
131
|
-
|
|
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, {
|
|
134
|
-
|
|
135
|
-
|
|
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(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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(
|
|
148
|
-
|
|
149
|
-
|
|
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(
|
|
153
|
-
|
|
154
|
-
|
|
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, {
|
|
162
|
-
|
|
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, {
|
|
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, {
|
|
177
|
-
|
|
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")
|
|
181
|
-
|
|
182
|
-
|
|
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 = {
|
|
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({
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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 = {
|
|
245
|
-
|
|
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")
|
|
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(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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))
|
|
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
|
|
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)
|
|
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(
|
|
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)
|
|
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: {
|
|
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) {
|
package/dist/internal/errors.js
CHANGED
|
@@ -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(
|
|
12
|
-
${
|
|
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)
|
|
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
|
|