@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/cli.js
CHANGED
|
@@ -9,25 +9,61 @@ function isNodeError(error) {
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
chmod,
|
|
14
|
+
mkdir,
|
|
15
|
+
readFile,
|
|
16
|
+
rename,
|
|
17
|
+
rm,
|
|
18
|
+
stat,
|
|
19
|
+
writeFile
|
|
20
|
+
} from "node:fs/promises";
|
|
13
21
|
import { homedir } from "node:os";
|
|
14
22
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
15
23
|
import { setTimeout as delay } from "node:timers/promises";
|
|
16
24
|
import { fileURLToPath } from "node:url";
|
|
17
25
|
import { Value } from "typebox/value";
|
|
18
|
-
import {
|
|
19
|
-
|
|
26
|
+
import {
|
|
27
|
+
NORN_BUILD_INFO
|
|
28
|
+
} from "./build-info.js";
|
|
29
|
+
import {
|
|
30
|
+
renderNornDocumentationIntro,
|
|
31
|
+
resolveDocumentationCacheRoot,
|
|
32
|
+
resolveNornDocumentation
|
|
33
|
+
} from "./documentation.js";
|
|
20
34
|
import { NornEngine } from "./internal/engine.js";
|
|
21
35
|
import { resolveNornAgentDirectory } from "./internal/agent-directory.js";
|
|
22
|
-
import {
|
|
23
|
-
|
|
36
|
+
import {
|
|
37
|
+
errorMessage,
|
|
38
|
+
NornProjectLoadError,
|
|
39
|
+
NornRunStoppedError
|
|
40
|
+
} from "./internal/errors.js";
|
|
41
|
+
import {
|
|
42
|
+
clearRunResumeRequest,
|
|
43
|
+
readRunLaunchRequest,
|
|
44
|
+
readRunResumeRequest,
|
|
45
|
+
writeRunLaunchRequest,
|
|
46
|
+
writeRunResumeRequest
|
|
47
|
+
} from "./internal/launch-request.js";
|
|
24
48
|
import { readRunMetrics } from "./internal/metrics.js";
|
|
25
49
|
import { getRunLeaseOwner, NornRunLease } from "./internal/run-lease.js";
|
|
26
50
|
import { generateRunName } from "./internal/run-names.js";
|
|
27
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
assertRunVersion,
|
|
53
|
+
getRunInfo,
|
|
54
|
+
listRuns,
|
|
55
|
+
mergeInterruptedWorkflowArgs,
|
|
56
|
+
resolveRunRoot
|
|
57
|
+
} from "./internal/run-state.js";
|
|
28
58
|
import { NornRunStore } from "./internal/run-store.js";
|
|
29
59
|
import { prepareRunWorkerDirectory } from "./internal/worker-directory.js";
|
|
30
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
discoverNornProject,
|
|
62
|
+
findNornProject,
|
|
63
|
+
inspectNornWorkflow,
|
|
64
|
+
loadNornProject,
|
|
65
|
+
NORN_PROJECT_FILE_NAME
|
|
66
|
+
} from "./workflow-loader.js";
|
|
31
67
|
var RUNS_ROOT = join(".norn", "runs");
|
|
32
68
|
var RUN_WAIT_INTERVAL_MS = 1e3;
|
|
33
69
|
var CLI_DESCRIPTION = "Norn is a harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents. Build workflows with the Norn SDK; use this JSON-native CLI to discover workflows, start or resume runs, inspect evidence, and manage the installed runtime.";
|
|
@@ -37,9 +73,16 @@ var COMMANDS = [
|
|
|
37
73
|
path: ["pi"],
|
|
38
74
|
description: "Run Norn's bundled Pi CLI for interactive authentication, provider packages, model configuration, or direct Pi use. No Norn project is required.",
|
|
39
75
|
usage: "norn pi [pi arguments...]",
|
|
40
|
-
arguments: [
|
|
76
|
+
arguments: [
|
|
77
|
+
"pi arguments: forwarded unchanged; use norn pi --help for Pi's CLI contract"
|
|
78
|
+
],
|
|
41
79
|
output: "Pi's native terminal, text, JSON, or RPC output and exit status; not wrapped in Norn JSON.",
|
|
42
|
-
examples: [
|
|
80
|
+
examples: [
|
|
81
|
+
"norn pi",
|
|
82
|
+
"norn pi install npm:pi-cursor-sdk",
|
|
83
|
+
"norn pi --list-models cursor",
|
|
84
|
+
"norn pi --help"
|
|
85
|
+
],
|
|
43
86
|
execute: (args) => runPi([...args])
|
|
44
87
|
},
|
|
45
88
|
{
|
|
@@ -67,9 +110,16 @@ var COMMANDS = [
|
|
|
67
110
|
path: ["help"],
|
|
68
111
|
description: "Use when reading JSON help for all Norn commands, one command group, or one command.",
|
|
69
112
|
usage: "norn help [command-or-group]",
|
|
70
|
-
arguments: [
|
|
113
|
+
arguments: [
|
|
114
|
+
"command-or-group: optional command path such as runs or runs start"
|
|
115
|
+
],
|
|
71
116
|
output: "JSON object with help metadata under help.",
|
|
72
|
-
examples: [
|
|
117
|
+
examples: [
|
|
118
|
+
"norn help",
|
|
119
|
+
"norn help runs",
|
|
120
|
+
"norn help runs start",
|
|
121
|
+
"norn --help"
|
|
122
|
+
],
|
|
73
123
|
execute: (args) => writeCliHelp(args)
|
|
74
124
|
},
|
|
75
125
|
{
|
|
@@ -81,7 +131,9 @@ var COMMANDS = [
|
|
|
81
131
|
examples: ["norn docs inspect"],
|
|
82
132
|
execute: async (args, documentationSource) => {
|
|
83
133
|
assertNoExtraArgs("docs inspect", args);
|
|
84
|
-
writeJson({
|
|
134
|
+
writeJson({
|
|
135
|
+
documentation: await resolveCliDocumentation(documentationSource)
|
|
136
|
+
});
|
|
85
137
|
}
|
|
86
138
|
},
|
|
87
139
|
{
|
|
@@ -93,10 +145,15 @@ var COMMANDS = [
|
|
|
93
145
|
examples: ["norn docs intro"],
|
|
94
146
|
execute: async (args, documentationSource) => {
|
|
95
147
|
assertNoExtraArgs("docs intro", args);
|
|
96
|
-
writeJson({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
148
|
+
writeJson({
|
|
149
|
+
intro: renderNornDocumentationIntro({
|
|
150
|
+
documentation: await resolveCliDocumentation(documentationSource),
|
|
151
|
+
invocation: documentationSource.kind === "embedded" ? [process.execPath] : [
|
|
152
|
+
process.execPath,
|
|
153
|
+
fileURLToPath(new URL("../bin/norn.mjs", import.meta.url))
|
|
154
|
+
]
|
|
155
|
+
})
|
|
156
|
+
});
|
|
100
157
|
}
|
|
101
158
|
},
|
|
102
159
|
{
|
|
@@ -128,7 +185,10 @@ var COMMANDS = [
|
|
|
128
185
|
path: ["workflows", "list"],
|
|
129
186
|
description: "Use when selecting a Norn workflow for a user task; defaults to entrypoint workflows.",
|
|
130
187
|
usage: "norn workflows list [--entrypoints|--all]",
|
|
131
|
-
options: [
|
|
188
|
+
options: [
|
|
189
|
+
"--entrypoints: list entrypoint workflows",
|
|
190
|
+
"--all: include internal workflow steps"
|
|
191
|
+
],
|
|
132
192
|
output: "JSON object with workflow summaries under workflows, isComplete, and registration diagnostics. Successful discovery can be incomplete; start/resume remain strict.",
|
|
133
193
|
examples: ["norn workflows list", "norn workflows list --all"],
|
|
134
194
|
execute: listWorkflows
|
|
@@ -142,7 +202,12 @@ var COMMANDS = [
|
|
|
142
202
|
output: "JSON object with workflow details, isComplete, and registration diagnostics. workflow is null if unavailable in an incomplete catalog or its schema cannot be inspected; an unknown id in a complete catalog is an error.",
|
|
143
203
|
examples: ["norn workflows inspect example.plan"],
|
|
144
204
|
execute: async (args) => {
|
|
145
|
-
const workflowId = requiredArg(
|
|
205
|
+
const workflowId = requiredArg(
|
|
206
|
+
"workflows inspect",
|
|
207
|
+
args,
|
|
208
|
+
0,
|
|
209
|
+
"workflow id"
|
|
210
|
+
);
|
|
146
211
|
assertNoExtraArgs("workflows inspect", args.slice(1));
|
|
147
212
|
await inspectWorkflow(workflowId);
|
|
148
213
|
}
|
|
@@ -155,7 +220,9 @@ var COMMANDS = [
|
|
|
155
220
|
arguments: ["workflow-id: fully qualified workflow id to start"],
|
|
156
221
|
stdin: 'Optional JSON object: {"args":{...},"config":{"workflowOrScopeId":{...}}}.',
|
|
157
222
|
output: "JSON object with started run info under run.",
|
|
158
|
-
examples: [
|
|
223
|
+
examples: [
|
|
224
|
+
`printf '{"args":{"task":"Add tests"}}' | norn runs start example.plan`
|
|
225
|
+
],
|
|
159
226
|
execute: async (args) => {
|
|
160
227
|
const workflowId = requiredArg("runs start", args, 0, "workflow id");
|
|
161
228
|
await startRun(workflowId, args.slice(1));
|
|
@@ -169,7 +236,9 @@ var COMMANDS = [
|
|
|
169
236
|
arguments: ["run: run id, generated name, or run path"],
|
|
170
237
|
stdin: 'Optional JSON object: {"args":{...}}. Interrupted runs require a patch containing only editable gate fields; pending-resume runs do not accept args.',
|
|
171
238
|
output: "JSON object with resumed run info under run.",
|
|
172
|
-
examples: [
|
|
239
|
+
examples: [
|
|
240
|
+
`printf '{"args":{"decision":"accept"}}' | norn runs resume quiet-river-lantern`
|
|
241
|
+
],
|
|
173
242
|
execute: async (args) => {
|
|
174
243
|
const run = requiredArg("runs resume", args, 0, "run");
|
|
175
244
|
await resumeRun(run, args.slice(1));
|
|
@@ -228,7 +297,9 @@ var COMMANDS = [
|
|
|
228
297
|
assertNoExtraArgs("runs checkpoints", args.slice(1));
|
|
229
298
|
const project = await findNornProject(process.cwd());
|
|
230
299
|
const runRoot = await resolveRunRoot(project.projectRoot, run);
|
|
231
|
-
writeJson({
|
|
300
|
+
writeJson({
|
|
301
|
+
checkpoints: await (await NornRunStore.open(runRoot)).listCheckpoints()
|
|
302
|
+
});
|
|
232
303
|
}
|
|
233
304
|
},
|
|
234
305
|
{
|
|
@@ -255,7 +326,10 @@ var COMMANDS = [
|
|
|
255
326
|
arguments: ["run: run id, generated name, or run path"],
|
|
256
327
|
options: ["--follow: continue streaming until the run stops"],
|
|
257
328
|
output: "JSON Lines stream of run events.",
|
|
258
|
-
examples: [
|
|
329
|
+
examples: [
|
|
330
|
+
"norn runs logs quiet-river-lantern",
|
|
331
|
+
"norn runs logs quiet-river-lantern --follow"
|
|
332
|
+
],
|
|
259
333
|
execute: async (args) => {
|
|
260
334
|
const run = requiredArg("runs logs", args, 0, "run");
|
|
261
335
|
const logArgs = args.slice(1);
|
|
@@ -268,7 +342,10 @@ var COMMANDS = [
|
|
|
268
342
|
path: ["runs", "rollback"],
|
|
269
343
|
description: "Use when restoring a failed or stopped run to a checkpoint and marking it pending resume.",
|
|
270
344
|
usage: "norn runs rollback <run> <checkpoint-id>",
|
|
271
|
-
arguments: [
|
|
345
|
+
arguments: [
|
|
346
|
+
"run: run id, generated name, or run path",
|
|
347
|
+
"checkpoint-id: checkpoint id from runs.checkpoints"
|
|
348
|
+
],
|
|
272
349
|
output: "JSON object with rolled-back run info under run.",
|
|
273
350
|
examples: ["norn runs rollback quiet-river-lantern checkpoint-1"],
|
|
274
351
|
execute: async (args) => {
|
|
@@ -338,7 +415,9 @@ var COMMANDS = [
|
|
|
338
415
|
path: ["upgrade"],
|
|
339
416
|
description: "Use when upgrading this Norn CLI installation according to explicit build metadata.",
|
|
340
417
|
usage: "norn upgrade [--dry-run]",
|
|
341
|
-
options: [
|
|
418
|
+
options: [
|
|
419
|
+
"--dry-run: report the upgrade plan without changing files or running installers"
|
|
420
|
+
],
|
|
342
421
|
output: "JSON object with upgrade status, plan, or unsupported reason under upgrade.",
|
|
343
422
|
examples: ["norn upgrade --dry-run", "norn upgrade"],
|
|
344
423
|
execute: upgradeNorn
|
|
@@ -416,11 +495,13 @@ async function main(args, documentationSource) {
|
|
|
416
495
|
try {
|
|
417
496
|
await runCommand(args, documentationSource);
|
|
418
497
|
} catch (error) {
|
|
419
|
-
writeJson({
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
498
|
+
writeJson({
|
|
499
|
+
error: {
|
|
500
|
+
code: errorCode(error),
|
|
501
|
+
message: errorMessage(error),
|
|
502
|
+
...error instanceof NornProjectLoadError ? { isComplete: error.isComplete, diagnostics: error.diagnostics } : {}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
424
505
|
process.exitCode = 1;
|
|
425
506
|
}
|
|
426
507
|
}
|
|
@@ -445,7 +526,11 @@ async function runCommand(args, documentationSource) {
|
|
|
445
526
|
async function listCliCommands(args) {
|
|
446
527
|
assertKnownFlags("commands list", args, ["--all"]);
|
|
447
528
|
const shouldIncludeHidden = args.includes("--all");
|
|
448
|
-
writeJson({
|
|
529
|
+
writeJson({
|
|
530
|
+
commands: COMMANDS.filter(
|
|
531
|
+
(command) => shouldIncludeHidden || !command.hidden
|
|
532
|
+
).map(cliCommandInfo)
|
|
533
|
+
});
|
|
449
534
|
}
|
|
450
535
|
async function inspectCliCommand(args) {
|
|
451
536
|
const commandId = requiredArg("commands inspect", args, 0, "command id");
|
|
@@ -482,8 +567,13 @@ function writeCliHelp(path) {
|
|
|
482
567
|
process.stdout.write(renderCommandHelp(command));
|
|
483
568
|
return;
|
|
484
569
|
}
|
|
485
|
-
const groupCommands = sortedCommandsForHelp(
|
|
486
|
-
|
|
570
|
+
const groupCommands = sortedCommandsForHelp(
|
|
571
|
+
visibleCommands().filter(
|
|
572
|
+
(candidate) => path.length === 0 || startsWithPath(candidate.path, path)
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
if (groupCommands.length === 0)
|
|
576
|
+
throw new Error(`Unknown norn help topic: ${path.join(" ")}`);
|
|
487
577
|
process.stdout.write(renderGroupHelp(path, groupCommands));
|
|
488
578
|
}
|
|
489
579
|
function renderGroupHelp(path, commands) {
|
|
@@ -526,27 +616,42 @@ function renderCommandHelp(command) {
|
|
|
526
616
|
function renderCommandSummary(commands) {
|
|
527
617
|
const commandNames = commands.map((command) => command.path.join(" "));
|
|
528
618
|
const width = Math.max(...commandNames.map((name) => name.length));
|
|
529
|
-
return commands.map(
|
|
619
|
+
return commands.map(
|
|
620
|
+
(command, index) => ` ${commandNames[index].padEnd(width)} ${commandHumanSummary(command)}`
|
|
621
|
+
).join("\n");
|
|
530
622
|
}
|
|
531
623
|
function helpSection(title, lines) {
|
|
532
624
|
return ["", `${title}:`, ...lines.map((line) => ` ${line}`)];
|
|
533
625
|
}
|
|
534
626
|
function groupHelpUsage(path) {
|
|
535
|
-
if (path.length === 0)
|
|
627
|
+
if (path.length === 0)
|
|
628
|
+
return ["norn <command> [args]", "norn help <command>", "norn --version"];
|
|
536
629
|
const prefix = `norn ${path.join(" ")}`;
|
|
537
|
-
return [
|
|
630
|
+
return [
|
|
631
|
+
`${prefix} <command> [args]`,
|
|
632
|
+
`norn help ${path.join(" ")} <command>`,
|
|
633
|
+
"norn --version"
|
|
634
|
+
];
|
|
538
635
|
}
|
|
539
636
|
function findCliCommand(args) {
|
|
540
|
-
return sortedCommandsByPathLength(COMMANDS).find(
|
|
637
|
+
return sortedCommandsByPathLength(COMMANDS).find(
|
|
638
|
+
(command) => startsWithPath(args, command.path)
|
|
639
|
+
);
|
|
541
640
|
}
|
|
542
641
|
function findExactCliCommand(path) {
|
|
543
|
-
return COMMANDS.find(
|
|
642
|
+
return COMMANDS.find(
|
|
643
|
+
(command) => command.path.length === path.length && startsWithPath(command.path, path)
|
|
644
|
+
);
|
|
544
645
|
}
|
|
545
646
|
function sortedCommandsByPathLength(commands) {
|
|
546
|
-
return [...commands].sort(
|
|
647
|
+
return [...commands].sort(
|
|
648
|
+
(left, right) => right.path.length - left.path.length
|
|
649
|
+
);
|
|
547
650
|
}
|
|
548
651
|
function sortedCommandsForHelp(commands) {
|
|
549
|
-
return [...commands].sort(
|
|
652
|
+
return [...commands].sort(
|
|
653
|
+
(left, right) => commandHelpOrder(left) - commandHelpOrder(right)
|
|
654
|
+
);
|
|
550
655
|
}
|
|
551
656
|
function commandHelpOrder(command) {
|
|
552
657
|
const index = HELP_COMMAND_ORDER.indexOf(command.id);
|
|
@@ -565,7 +670,11 @@ async function resolveCliDocumentation(source) {
|
|
|
565
670
|
return resolveNornDocumentation({
|
|
566
671
|
source,
|
|
567
672
|
build: { ...NORN_BUILD_INFO, version: await readPackageVersion() },
|
|
568
|
-
cacheRoot: resolveDocumentationCacheRoot({
|
|
673
|
+
cacheRoot: resolveDocumentationCacheRoot({
|
|
674
|
+
platform: process.platform,
|
|
675
|
+
home: homedir(),
|
|
676
|
+
environment: process.env
|
|
677
|
+
})
|
|
569
678
|
});
|
|
570
679
|
}
|
|
571
680
|
async function versionInfo() {
|
|
@@ -573,11 +682,15 @@ async function versionInfo() {
|
|
|
573
682
|
}
|
|
574
683
|
async function readPackageVersion() {
|
|
575
684
|
try {
|
|
576
|
-
const packageJson = JSON.parse(
|
|
577
|
-
|
|
685
|
+
const packageJson = JSON.parse(
|
|
686
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8")
|
|
687
|
+
);
|
|
688
|
+
if (typeof packageJson.version !== "string")
|
|
689
|
+
throw new Error("Invalid Norn package version");
|
|
578
690
|
return packageJson.version;
|
|
579
691
|
} catch (error) {
|
|
580
|
-
if (isNodeError(error) && error.code === "ENOENT")
|
|
692
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
693
|
+
return NORN_BUILD_INFO.version;
|
|
581
694
|
throw error;
|
|
582
695
|
}
|
|
583
696
|
}
|
|
@@ -587,10 +700,14 @@ async function upgradeNorn(args) {
|
|
|
587
700
|
const currentVersion = await readPackageVersion();
|
|
588
701
|
const build = NORN_BUILD_INFO;
|
|
589
702
|
if (build.kind !== "github-release-binary") {
|
|
590
|
-
writeJson({
|
|
703
|
+
writeJson({
|
|
704
|
+
upgrade: unsupportedUpgrade(build, currentVersion, build.upgrade.reason)
|
|
705
|
+
});
|
|
591
706
|
return;
|
|
592
707
|
}
|
|
593
|
-
writeJson({
|
|
708
|
+
writeJson({
|
|
709
|
+
upgrade: await upgradeGithubReleaseBinary(build, currentVersion, dryRun)
|
|
710
|
+
});
|
|
594
711
|
}
|
|
595
712
|
function unsupportedUpgrade(build, currentVersion, reason) {
|
|
596
713
|
return {
|
|
@@ -604,13 +721,27 @@ function unsupportedUpgrade(build, currentVersion, reason) {
|
|
|
604
721
|
async function upgradeGithubReleaseBinary(build, currentVersion, dryRun) {
|
|
605
722
|
const plan = githubReleaseBinaryUpgradePlan(build, currentVersion, dryRun);
|
|
606
723
|
if (dryRun) return { ...plan, status: "planned" };
|
|
607
|
-
if (process.platform === "win32")
|
|
724
|
+
if (process.platform === "win32")
|
|
725
|
+
return unsupportedUpgrade(
|
|
726
|
+
build,
|
|
727
|
+
currentVersion,
|
|
728
|
+
"Replacing a running Windows executable is not supported yet."
|
|
729
|
+
);
|
|
608
730
|
const binary = await downloadReleaseAsset(plan.downloadUrl);
|
|
609
731
|
const checksumText = await downloadReleaseText(plan.checksumUrl);
|
|
610
|
-
const expectedChecksum = parseSha256Checksum(
|
|
732
|
+
const expectedChecksum = parseSha256Checksum(
|
|
733
|
+
checksumText,
|
|
734
|
+
build.checksumAssetName
|
|
735
|
+
);
|
|
611
736
|
const actualChecksum = createHash("sha256").update(Buffer.from(binary)).digest("hex");
|
|
612
|
-
if (actualChecksum !== expectedChecksum)
|
|
613
|
-
|
|
737
|
+
if (actualChecksum !== expectedChecksum)
|
|
738
|
+
throw new Error(
|
|
739
|
+
`Downloaded Norn binary checksum mismatch: expected ${expectedChecksum}, got ${actualChecksum}`
|
|
740
|
+
);
|
|
741
|
+
const tempPath = join(
|
|
742
|
+
dirname(plan.targetPath),
|
|
743
|
+
`.norn-upgrade-${process.pid}-${build.assetName}`
|
|
744
|
+
);
|
|
614
745
|
try {
|
|
615
746
|
await writeFile(tempPath, binary);
|
|
616
747
|
await chmod(tempPath, 493);
|
|
@@ -642,17 +773,24 @@ function githubReleaseDownloadUrl(build, assetName) {
|
|
|
642
773
|
}
|
|
643
774
|
async function downloadReleaseAsset(url) {
|
|
644
775
|
const response = await fetch(url);
|
|
645
|
-
if (!response.ok)
|
|
776
|
+
if (!response.ok)
|
|
777
|
+
throw new Error(
|
|
778
|
+
`Failed to download Norn release asset: ${url} returned ${response.status}`
|
|
779
|
+
);
|
|
646
780
|
return new Uint8Array(await response.arrayBuffer());
|
|
647
781
|
}
|
|
648
782
|
async function downloadReleaseText(url) {
|
|
649
783
|
const response = await fetch(url);
|
|
650
|
-
if (!response.ok)
|
|
784
|
+
if (!response.ok)
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Failed to download Norn release metadata: ${url} returned ${response.status}`
|
|
787
|
+
);
|
|
651
788
|
return response.text();
|
|
652
789
|
}
|
|
653
790
|
function parseSha256Checksum(text, checksumAssetName) {
|
|
654
791
|
const checksum = text.trim().split(/\s+/)[0] ?? "";
|
|
655
|
-
if (!/^[a-f0-9]{64}$/i.test(checksum))
|
|
792
|
+
if (!/^[a-f0-9]{64}$/i.test(checksum))
|
|
793
|
+
throw new Error(`Invalid Norn checksum asset: ${checksumAssetName}`);
|
|
656
794
|
return checksum.toLowerCase();
|
|
657
795
|
}
|
|
658
796
|
function requiredArg(command, args, index, label) {
|
|
@@ -661,18 +799,30 @@ function requiredArg(command, args, index, label) {
|
|
|
661
799
|
return value;
|
|
662
800
|
}
|
|
663
801
|
function assertNoExtraArgs(command, args) {
|
|
664
|
-
if (args.length > 0)
|
|
802
|
+
if (args.length > 0)
|
|
803
|
+
throw new Error(
|
|
804
|
+
`${command} does not accept CLI arguments: ${args.join(" ")}`
|
|
805
|
+
);
|
|
665
806
|
}
|
|
666
807
|
function assertKnownFlags(command, args, flags) {
|
|
667
808
|
const allowedFlags = new Set(flags);
|
|
668
809
|
const unsupportedFlags = args.filter((arg) => !allowedFlags.has(arg));
|
|
669
|
-
if (unsupportedFlags.length > 0)
|
|
810
|
+
if (unsupportedFlags.length > 0)
|
|
811
|
+
throw new Error(
|
|
812
|
+
`${command} has unsupported flags or arguments: ${unsupportedFlags.join(" ")}`
|
|
813
|
+
);
|
|
670
814
|
}
|
|
671
815
|
async function listWorkflows(args) {
|
|
672
816
|
assertKnownFlags("workflows list", args, ["--entrypoints", "--all"]);
|
|
673
817
|
const entrypointsOnly = workflowListEntrypointsOnly(args);
|
|
674
|
-
const { workflows, isComplete, diagnostics } = await discoverNornProject(
|
|
675
|
-
|
|
818
|
+
const { workflows, isComplete, diagnostics } = await discoverNornProject(
|
|
819
|
+
process.cwd()
|
|
820
|
+
);
|
|
821
|
+
writeJson({
|
|
822
|
+
workflows: entrypointsOnly ? workflows.filter((workflow) => workflow.isEntrypoint) : workflows,
|
|
823
|
+
isComplete,
|
|
824
|
+
diagnostics
|
|
825
|
+
});
|
|
676
826
|
}
|
|
677
827
|
async function inspectWorkflow(workflowId) {
|
|
678
828
|
writeJson(await inspectNornWorkflow({ cwd: process.cwd(), workflowId }));
|
|
@@ -680,25 +830,39 @@ async function inspectWorkflow(workflowId) {
|
|
|
680
830
|
function workflowListEntrypointsOnly(args) {
|
|
681
831
|
const entrypoints = args.includes("--entrypoints");
|
|
682
832
|
const all = args.includes("--all");
|
|
683
|
-
if (entrypoints && all)
|
|
833
|
+
if (entrypoints && all)
|
|
834
|
+
throw new Error("Use either --entrypoints or --all, not both");
|
|
684
835
|
return !all;
|
|
685
836
|
}
|
|
686
837
|
async function initProject() {
|
|
687
838
|
const projectRoot = process.cwd();
|
|
688
839
|
const projectPath = resolve(projectRoot, NORN_PROJECT_FILE_NAME);
|
|
689
|
-
if (await isFile(projectPath))
|
|
840
|
+
if (await isFile(projectPath))
|
|
841
|
+
throw new Error(`Norn project already exists: ${projectPath}`);
|
|
690
842
|
await mkdir(resolve(projectRoot, RUNS_ROOT), { recursive: true });
|
|
691
|
-
await writeFile(
|
|
692
|
-
|
|
843
|
+
await writeFile(
|
|
844
|
+
projectPath,
|
|
845
|
+
`${JSON.stringify({ version: 1, workflows: [], includes: [], config: {} }, null, 2)}
|
|
846
|
+
`,
|
|
847
|
+
"utf8"
|
|
848
|
+
);
|
|
693
849
|
await ensureGitignoreExcludesRunState(projectRoot);
|
|
694
|
-
writeJson({
|
|
850
|
+
writeJson({
|
|
851
|
+
project: {
|
|
852
|
+
path: projectPath,
|
|
853
|
+
root: projectRoot,
|
|
854
|
+
runsRoot: resolve(projectRoot, RUNS_ROOT)
|
|
855
|
+
}
|
|
856
|
+
});
|
|
695
857
|
}
|
|
696
858
|
async function listCurrentProjectRuns() {
|
|
697
859
|
const project = await findNornProject(process.cwd());
|
|
698
860
|
return listRuns(project.projectRoot);
|
|
699
861
|
}
|
|
700
862
|
async function inspectProject() {
|
|
701
|
-
const { project, isComplete, diagnostics } = await discoverNornProject(
|
|
863
|
+
const { project, isComplete, diagnostics } = await discoverNornProject(
|
|
864
|
+
process.cwd()
|
|
865
|
+
);
|
|
702
866
|
writeJson({ project, isComplete, diagnostics });
|
|
703
867
|
}
|
|
704
868
|
async function startRun(workflowId, commandArgs) {
|
|
@@ -711,13 +875,33 @@ async function startRun(workflowId, commandArgs) {
|
|
|
711
875
|
Value.Decode(workflow.args, args);
|
|
712
876
|
const configOverride = input.config;
|
|
713
877
|
const id = randomUUID();
|
|
714
|
-
const name = generateRunName(
|
|
878
|
+
const name = generateRunName(
|
|
879
|
+
new Set((await listRuns(project.projectRoot)).map((run) => run.name))
|
|
880
|
+
);
|
|
715
881
|
const runRoot = resolve(project.projectRoot, RUNS_ROOT, id);
|
|
716
882
|
await mkdir(runRoot, { recursive: true });
|
|
717
883
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
718
|
-
await writeRunLaunchRequest(runRoot, {
|
|
884
|
+
await writeRunLaunchRequest(runRoot, {
|
|
885
|
+
version: 2,
|
|
886
|
+
type: "run",
|
|
887
|
+
id,
|
|
888
|
+
name,
|
|
889
|
+
workflowId,
|
|
890
|
+
args,
|
|
891
|
+
configOverride,
|
|
892
|
+
createdAt
|
|
893
|
+
});
|
|
719
894
|
await startDetachedExecuteRun(id, project.projectRoot);
|
|
720
|
-
writeJson({
|
|
895
|
+
writeJson({
|
|
896
|
+
run: startedRunInfo({
|
|
897
|
+
id,
|
|
898
|
+
name,
|
|
899
|
+
workflow,
|
|
900
|
+
runRoot,
|
|
901
|
+
projectRoot: project.projectRoot,
|
|
902
|
+
createdAt
|
|
903
|
+
})
|
|
904
|
+
});
|
|
721
905
|
}
|
|
722
906
|
async function resumeRun(run, args) {
|
|
723
907
|
assertNoStructuredInputArgs("runs resume", args);
|
|
@@ -730,7 +914,14 @@ async function resumeRun(run, args) {
|
|
|
730
914
|
const runInfo = await getRunInfo(runRoot);
|
|
731
915
|
assertRunVersion(runInfo.version);
|
|
732
916
|
const args2 = await parseResumeArgs(runInfo, input.args);
|
|
733
|
-
request = {
|
|
917
|
+
request = {
|
|
918
|
+
version: 2,
|
|
919
|
+
type: "resume",
|
|
920
|
+
id: runInfo.id,
|
|
921
|
+
requestId: randomUUID(),
|
|
922
|
+
args: args2,
|
|
923
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
924
|
+
};
|
|
734
925
|
await writeRunResumeRequest(runRoot, request);
|
|
735
926
|
} finally {
|
|
736
927
|
await lease.release();
|
|
@@ -745,16 +936,33 @@ async function resumeRun(run, args) {
|
|
|
745
936
|
}
|
|
746
937
|
async function parseResumeArgs(runInfo, args) {
|
|
747
938
|
if (runInfo.status === "interrupted") {
|
|
748
|
-
if (args === void 0)
|
|
749
|
-
|
|
939
|
+
if (args === void 0)
|
|
940
|
+
throw new Error(
|
|
941
|
+
`Interrupted workflow resume requires args: ${runInfo.name}`
|
|
942
|
+
);
|
|
943
|
+
if (!runInfo.currentWorkflowId)
|
|
944
|
+
throw new Error(`Run has no current workflow: ${runInfo.name}`);
|
|
750
945
|
const project = await loadNornProject(process.cwd());
|
|
751
946
|
const workflow = project.registry.workflowById(runInfo.currentWorkflowId);
|
|
752
|
-
if (!workflow)
|
|
753
|
-
|
|
947
|
+
if (!workflow)
|
|
948
|
+
throw new Error(
|
|
949
|
+
`Unknown workflow for resumed run: ${runInfo.currentWorkflowId}`
|
|
950
|
+
);
|
|
951
|
+
Value.Decode(
|
|
952
|
+
workflow.args,
|
|
953
|
+
mergeInterruptedWorkflowArgs(
|
|
954
|
+
runInfo.interruption?.args,
|
|
955
|
+
args,
|
|
956
|
+
runInfo.interruption?.fields
|
|
957
|
+
)
|
|
958
|
+
);
|
|
754
959
|
return args;
|
|
755
960
|
}
|
|
756
961
|
if (runInfo.status === "pendingResume") {
|
|
757
|
-
if (args !== void 0)
|
|
962
|
+
if (args !== void 0)
|
|
963
|
+
throw new Error(
|
|
964
|
+
`Pending-resume workflows do not accept args: ${runInfo.name}`
|
|
965
|
+
);
|
|
758
966
|
return void 0;
|
|
759
967
|
}
|
|
760
968
|
throw new Error(`Run must be rolled back before resuming: ${runInfo.name}`);
|
|
@@ -769,7 +977,8 @@ async function waitForInactiveRun(projectRoot, run) {
|
|
|
769
977
|
runRoot ??= await resolveWaitableRunRoot(projectRoot, run);
|
|
770
978
|
try {
|
|
771
979
|
const runInfo = await readRunInspection(runRoot);
|
|
772
|
-
if (runInfo.status !== "running" || runInfo.health === "unhealthy")
|
|
980
|
+
if (runInfo.status !== "running" || runInfo.health === "unhealthy")
|
|
981
|
+
return runInfo;
|
|
773
982
|
} catch (error) {
|
|
774
983
|
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
775
984
|
}
|
|
@@ -780,7 +989,10 @@ async function resolveWaitableRunRoot(sessionCwd, run) {
|
|
|
780
989
|
try {
|
|
781
990
|
return await resolveRunRoot(sessionCwd, run);
|
|
782
991
|
} catch (error) {
|
|
783
|
-
for (const candidate of [
|
|
992
|
+
for (const candidate of [
|
|
993
|
+
resolve(sessionCwd, run),
|
|
994
|
+
resolve(sessionCwd, RUNS_ROOT, run)
|
|
995
|
+
]) {
|
|
784
996
|
if (await isDirectory(candidate)) return candidate;
|
|
785
997
|
}
|
|
786
998
|
throw error;
|
|
@@ -813,31 +1025,55 @@ async function ensureGitignoreExcludesRunState(projectRoot) {
|
|
|
813
1025
|
}
|
|
814
1026
|
if (currentText.split(/\r?\n/).includes(runStatePattern)) return;
|
|
815
1027
|
const separator = currentText.length === 0 || currentText.endsWith("\n") ? "" : "\n";
|
|
816
|
-
await writeFile(
|
|
817
|
-
|
|
1028
|
+
await writeFile(
|
|
1029
|
+
gitignorePath,
|
|
1030
|
+
`${currentText}${separator}${runStatePattern}
|
|
1031
|
+
`,
|
|
1032
|
+
"utf8"
|
|
1033
|
+
);
|
|
818
1034
|
}
|
|
819
1035
|
async function executeRun(runId) {
|
|
820
1036
|
const abortController = new AbortController();
|
|
821
|
-
process.once(
|
|
822
|
-
|
|
1037
|
+
process.once(
|
|
1038
|
+
"SIGTERM",
|
|
1039
|
+
() => abortController.abort(new NornRunStoppedError())
|
|
1040
|
+
);
|
|
1041
|
+
process.once(
|
|
1042
|
+
"SIGINT",
|
|
1043
|
+
() => abortController.abort(new NornRunStoppedError())
|
|
1044
|
+
);
|
|
823
1045
|
const location = await findNornProject(process.cwd());
|
|
824
1046
|
const runRoot = resolve(location.projectRoot, RUNS_ROOT, runId);
|
|
825
1047
|
const request = await readOptionalRunLaunchRequest(runRoot) ?? await readRunResumeRequest(runRoot);
|
|
826
1048
|
try {
|
|
827
|
-
const agentDir = resolveNornAgentDirectory({
|
|
1049
|
+
const agentDir = resolveNornAgentDirectory({
|
|
1050
|
+
home: homedir(),
|
|
1051
|
+
environment: process.env
|
|
1052
|
+
});
|
|
828
1053
|
process.chdir(await prepareRunWorkerDirectory(runRoot));
|
|
829
1054
|
const project = await loadNornProject(location.projectRoot);
|
|
830
|
-
const engine = new NornEngine({
|
|
1055
|
+
const engine = new NornEngine({
|
|
1056
|
+
cwd: project.projectRoot,
|
|
1057
|
+
agentDir,
|
|
1058
|
+
signal: abortController.signal,
|
|
1059
|
+
gateMode: "pause",
|
|
1060
|
+
config: project.projectConfig
|
|
1061
|
+
});
|
|
831
1062
|
engine.registerWorkflows(project.definitions);
|
|
832
1063
|
if (request.type === "run") {
|
|
833
1064
|
const workflow = project.registry.workflowById(request.workflowId);
|
|
834
1065
|
if (!workflow) throw new Error(`Unknown workflow: ${request.workflowId}`);
|
|
835
|
-
await engine.runWorkflow(workflow, request.args, {
|
|
1066
|
+
await engine.runWorkflow(workflow, request.args, {
|
|
1067
|
+
id: request.id,
|
|
1068
|
+
name: request.name,
|
|
1069
|
+
configOverride: request.configOverride
|
|
1070
|
+
});
|
|
836
1071
|
} else {
|
|
837
1072
|
await engine.resumeRequestedWorkflow({ runRoot, request });
|
|
838
1073
|
}
|
|
839
1074
|
} finally {
|
|
840
|
-
if (request.type === "run")
|
|
1075
|
+
if (request.type === "run")
|
|
1076
|
+
await rm(join(runRoot, "launch-request.json"), { force: true });
|
|
841
1077
|
else await removeResumeRequest({ runRoot, request });
|
|
842
1078
|
}
|
|
843
1079
|
}
|
|
@@ -854,7 +1090,10 @@ async function rollbackRun(run, args) {
|
|
|
854
1090
|
assertNoExtraArgs("runs rollback", args.slice(1));
|
|
855
1091
|
const project = await findNornProject(process.cwd());
|
|
856
1092
|
const runRoot = await resolveRunRoot(project.projectRoot, run);
|
|
857
|
-
const engine = new NornEngine({
|
|
1093
|
+
const engine = new NornEngine({
|
|
1094
|
+
cwd: project.projectRoot,
|
|
1095
|
+
gateMode: "pause"
|
|
1096
|
+
});
|
|
858
1097
|
writeJson({ run: await engine.rollbackRun(runRoot, checkpointId) });
|
|
859
1098
|
}
|
|
860
1099
|
async function inspectRun(run) {
|
|
@@ -874,7 +1113,8 @@ async function deleteRun(run) {
|
|
|
874
1113
|
const project = await findNornProject(process.cwd());
|
|
875
1114
|
const runRoot = await resolveRunRoot(project.projectRoot, run);
|
|
876
1115
|
const runInfo = await getRunInfo(runRoot);
|
|
877
|
-
if (runInfo.status === "running")
|
|
1116
|
+
if (runInfo.status === "running")
|
|
1117
|
+
throw new Error(`Stop run before deleting it: ${runInfo.name}`);
|
|
878
1118
|
await rm(runRoot, { recursive: true, force: true });
|
|
879
1119
|
return { id: runInfo.id, name: runInfo.name, path: runInfo.path };
|
|
880
1120
|
}
|
|
@@ -890,7 +1130,8 @@ async function writeRunLogs(run, options) {
|
|
|
890
1130
|
let written = 0;
|
|
891
1131
|
while (true) {
|
|
892
1132
|
const events = await readRunEvents(runRoot);
|
|
893
|
-
for (const event of events.slice(written))
|
|
1133
|
+
for (const event of events.slice(written))
|
|
1134
|
+
process.stdout.write(`${JSON.stringify(event)}
|
|
894
1135
|
`);
|
|
895
1136
|
written = events.length;
|
|
896
1137
|
if (!options.follow) return;
|
|
@@ -912,7 +1153,8 @@ async function startDetachedExecuteRun(runId, projectRoot) {
|
|
|
912
1153
|
child.unref();
|
|
913
1154
|
}
|
|
914
1155
|
function detachedExecuteRunArgs(runId) {
|
|
915
|
-
if (NORN_BUILD_INFO.kind === "github-release-binary")
|
|
1156
|
+
if (NORN_BUILD_INFO.kind === "github-release-binary")
|
|
1157
|
+
return ["execute-run", runId];
|
|
916
1158
|
const scriptPath = process.argv[1];
|
|
917
1159
|
return scriptPath && isAbsolute(scriptPath) && /\.(?:c?m?js|ts)$/.test(scriptPath) ? [scriptPath, "execute-run", runId] : ["execute-run", runId];
|
|
918
1160
|
}
|
|
@@ -922,7 +1164,10 @@ function startedRunInfo(input) {
|
|
|
922
1164
|
id: input.id,
|
|
923
1165
|
name: input.name,
|
|
924
1166
|
path: input.runRoot,
|
|
925
|
-
paths: {
|
|
1167
|
+
paths: {
|
|
1168
|
+
project: input.projectRoot,
|
|
1169
|
+
workspace: join(input.runRoot, "current", "workspace")
|
|
1170
|
+
},
|
|
926
1171
|
entrypointWorkflowId: input.workflow.id,
|
|
927
1172
|
currentWorkflowId: input.workflow.id,
|
|
928
1173
|
status: "running",
|
|
@@ -941,7 +1186,9 @@ async function readOptionalRunLaunchRequest(runRoot) {
|
|
|
941
1186
|
}
|
|
942
1187
|
async function readRunEvents(runRoot) {
|
|
943
1188
|
try {
|
|
944
|
-
const manifest = JSON.parse(
|
|
1189
|
+
const manifest = JSON.parse(
|
|
1190
|
+
await readFile(join(runRoot, "current", "manifest.json"), "utf8")
|
|
1191
|
+
);
|
|
945
1192
|
return manifest.events ?? [];
|
|
946
1193
|
} catch (error) {
|
|
947
1194
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
@@ -979,16 +1226,23 @@ function parseResumeRunInput(value) {
|
|
|
979
1226
|
return { args: input.args };
|
|
980
1227
|
}
|
|
981
1228
|
function parseStructuredInputObject(command, value) {
|
|
982
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1229
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1230
|
+
throw new Error(`${command} reads a JSON object from stdin`);
|
|
983
1231
|
return value;
|
|
984
1232
|
}
|
|
985
1233
|
function assertStructuredInputKeys(command, input, allowedKeys) {
|
|
986
1234
|
const allowed = new Set(allowedKeys);
|
|
987
1235
|
const unexpected = Object.keys(input).filter((key) => !allowed.has(key));
|
|
988
|
-
if (unexpected.length > 0)
|
|
1236
|
+
if (unexpected.length > 0)
|
|
1237
|
+
throw new Error(
|
|
1238
|
+
`${command} stdin JSON has unsupported keys: ${unexpected.join(", ")}`
|
|
1239
|
+
);
|
|
989
1240
|
}
|
|
990
1241
|
function assertNoStructuredInputArgs(command, args) {
|
|
991
|
-
if (args.length > 0)
|
|
1242
|
+
if (args.length > 0)
|
|
1243
|
+
throw new Error(
|
|
1244
|
+
`${command} reads structured input from stdin, not CLI arguments: ${args.join(" ")}`
|
|
1245
|
+
);
|
|
992
1246
|
}
|
|
993
1247
|
function sendSignal(processGroupId, pid, signal) {
|
|
994
1248
|
try {
|