@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
|
@@ -2,7 +2,11 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { requireAbsoluteWorkingDirectory } from "./working-directory.ts";
|
|
4
4
|
import type { WriteStream } from "node:fs";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
NornCommandRunInput,
|
|
7
|
+
NornCommandRunResult,
|
|
8
|
+
NornLogRef,
|
|
9
|
+
} from "@vimhead.dev/norn";
|
|
6
10
|
import { errorMessage } from "./errors.ts";
|
|
7
11
|
import { safeFileName } from "./file-names.ts";
|
|
8
12
|
import type { NornRunLogs } from "./logs.ts";
|
|
@@ -21,9 +25,25 @@ export class NornCommandRunner {
|
|
|
21
25
|
const cwd = requireAbsoluteWorkingDirectory(commandInput.cwd);
|
|
22
26
|
const startedAtMs = Date.now();
|
|
23
27
|
const invocationId = randomUUID();
|
|
24
|
-
const stdoutRef = commandLog({
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const stdoutRef = commandLog({
|
|
29
|
+
label: commandInput.label,
|
|
30
|
+
invocationId,
|
|
31
|
+
stream: "stdout",
|
|
32
|
+
});
|
|
33
|
+
const stderrRef = commandLog({
|
|
34
|
+
label: commandInput.label,
|
|
35
|
+
invocationId,
|
|
36
|
+
stream: "stderr",
|
|
37
|
+
});
|
|
38
|
+
await this.input.logger.record({
|
|
39
|
+
type: "command.started",
|
|
40
|
+
invocationId,
|
|
41
|
+
label: commandInput.label,
|
|
42
|
+
command: commandInput.command,
|
|
43
|
+
cwd,
|
|
44
|
+
stdoutLogId: stdoutRef.id,
|
|
45
|
+
stderrLogId: stderrRef.id,
|
|
46
|
+
});
|
|
27
47
|
let result: SpawnCommandResult;
|
|
28
48
|
const stdoutLog = await this.input.logs.createWriteStream(stdoutRef);
|
|
29
49
|
const stderrLog = await this.input.logs.createWriteStream(stderrRef);
|
|
@@ -38,7 +58,13 @@ export class NornCommandRunner {
|
|
|
38
58
|
stderrStream: stderrLog.stream,
|
|
39
59
|
});
|
|
40
60
|
} catch (error) {
|
|
41
|
-
await this.input.logger.record({
|
|
61
|
+
await this.input.logger.record({
|
|
62
|
+
type: "command.failed",
|
|
63
|
+
invocationId,
|
|
64
|
+
label: commandInput.label,
|
|
65
|
+
durationMs: Date.now() - startedAtMs,
|
|
66
|
+
error: errorMessage(error),
|
|
67
|
+
});
|
|
42
68
|
throw error;
|
|
43
69
|
}
|
|
44
70
|
const commandResult = {
|
|
@@ -83,15 +109,24 @@ type SpawnCommandResult = {
|
|
|
83
109
|
killed: boolean;
|
|
84
110
|
};
|
|
85
111
|
|
|
86
|
-
async function spawnCommand(
|
|
112
|
+
async function spawnCommand(
|
|
113
|
+
input: SpawnCommandInput,
|
|
114
|
+
): Promise<SpawnCommandResult> {
|
|
87
115
|
const command = typeof input.command === "string" ? "bash" : input.command[0];
|
|
88
|
-
const args =
|
|
116
|
+
const args =
|
|
117
|
+
typeof input.command === "string"
|
|
118
|
+
? ["-c", input.command]
|
|
119
|
+
: input.command.slice(1);
|
|
89
120
|
let killed = false;
|
|
90
121
|
const stdout = new BoundedTextBuffer();
|
|
91
122
|
const stderr = new BoundedTextBuffer();
|
|
92
123
|
|
|
93
124
|
return new Promise((resolvePromise, reject) => {
|
|
94
|
-
const child = spawn(command, args, {
|
|
125
|
+
const child = spawn(command, args, {
|
|
126
|
+
cwd: input.cwd,
|
|
127
|
+
env: input.env,
|
|
128
|
+
shell: false,
|
|
129
|
+
});
|
|
95
130
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
96
131
|
let escalation: ReturnType<typeof setTimeout> | undefined;
|
|
97
132
|
let isSettled = false;
|
|
@@ -104,25 +139,33 @@ async function spawnCommand(input: SpawnCommandInput): Promise<SpawnCommandResul
|
|
|
104
139
|
if (isSettled) return;
|
|
105
140
|
isSettled = true;
|
|
106
141
|
cleanup();
|
|
107
|
-
void closeStreams(input.stdoutStream, input.stderrStream).then(
|
|
142
|
+
void closeStreams(input.stdoutStream, input.stderrStream).then(
|
|
143
|
+
() => resolvePromise(result),
|
|
144
|
+
reject,
|
|
145
|
+
);
|
|
108
146
|
};
|
|
109
147
|
const fail = (error: Error) => {
|
|
110
148
|
if (isSettled) return;
|
|
111
149
|
isSettled = true;
|
|
112
150
|
cleanup();
|
|
113
|
-
void closeStreams(input.stdoutStream, input.stderrStream).then(
|
|
151
|
+
void closeStreams(input.stdoutStream, input.stderrStream).then(
|
|
152
|
+
() => reject(error),
|
|
153
|
+
reject,
|
|
154
|
+
);
|
|
114
155
|
};
|
|
115
156
|
const killChild = () => {
|
|
116
157
|
if (isSettled || killed) return;
|
|
117
158
|
killed = true;
|
|
118
159
|
child.kill("SIGTERM");
|
|
119
160
|
escalation = setTimeout(() => {
|
|
120
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
161
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
162
|
+
child.kill("SIGKILL");
|
|
121
163
|
}, 5_000);
|
|
122
164
|
escalation.unref?.();
|
|
123
165
|
};
|
|
124
166
|
|
|
125
|
-
if (input.timeoutMs !== undefined)
|
|
167
|
+
if (input.timeoutMs !== undefined)
|
|
168
|
+
timeout = setTimeout(killChild, input.timeoutMs);
|
|
126
169
|
if (input.signal?.aborted) killChild();
|
|
127
170
|
input.signal?.addEventListener("abort", killChild, { once: true });
|
|
128
171
|
|
|
@@ -137,12 +180,25 @@ async function spawnCommand(input: SpawnCommandInput): Promise<SpawnCommandResul
|
|
|
137
180
|
input.stderrStream.write(text);
|
|
138
181
|
});
|
|
139
182
|
child.on("error", fail);
|
|
140
|
-
child.on("close", (code) =>
|
|
183
|
+
child.on("close", (code) =>
|
|
184
|
+
finish({
|
|
185
|
+
exitCode: code,
|
|
186
|
+
stdoutTail: stdout.value(),
|
|
187
|
+
stderrTail: stderr.value(),
|
|
188
|
+
killed,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
141
191
|
});
|
|
142
192
|
}
|
|
143
193
|
|
|
144
|
-
function commandLog(input: {
|
|
145
|
-
|
|
194
|
+
function commandLog(input: {
|
|
195
|
+
readonly label: string;
|
|
196
|
+
readonly invocationId: string;
|
|
197
|
+
readonly stream: "stdout" | "stderr";
|
|
198
|
+
}): NornLogRef {
|
|
199
|
+
return {
|
|
200
|
+
id: `commands/${safeFileName(input.label)}-${input.invocationId}.${input.stream}`,
|
|
201
|
+
};
|
|
146
202
|
}
|
|
147
203
|
|
|
148
204
|
class BoundedTextBuffer {
|
|
@@ -152,7 +208,8 @@ class BoundedTextBuffer {
|
|
|
152
208
|
|
|
153
209
|
append(value: string): void {
|
|
154
210
|
this.text += value;
|
|
155
|
-
if (this.text.length > this.maxChars)
|
|
211
|
+
if (this.text.length > this.maxChars)
|
|
212
|
+
this.text = this.text.slice(this.text.length - this.maxChars);
|
|
156
213
|
}
|
|
157
214
|
|
|
158
215
|
value(): string {
|
|
@@ -161,8 +218,13 @@ class BoundedTextBuffer {
|
|
|
161
218
|
}
|
|
162
219
|
|
|
163
220
|
async function closeStreams(...streams: WriteStream[]): Promise<void> {
|
|
164
|
-
await Promise.all(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
221
|
+
await Promise.all(
|
|
222
|
+
streams.map(
|
|
223
|
+
(stream) =>
|
|
224
|
+
new Promise<void>((resolvePromise, reject) => {
|
|
225
|
+
stream.on("error", reject);
|
|
226
|
+
stream.end(resolvePromise);
|
|
227
|
+
}),
|
|
228
|
+
),
|
|
229
|
+
);
|
|
168
230
|
}
|
|
@@ -17,33 +17,64 @@ export const DOCUMENTATION_PATHS = {
|
|
|
17
17
|
examples: "examples",
|
|
18
18
|
} as const;
|
|
19
19
|
|
|
20
|
-
export function validateDocumentationBundle(
|
|
21
|
-
|
|
20
|
+
export function validateDocumentationBundle(
|
|
21
|
+
bundle: NornDocumentationBundle,
|
|
22
|
+
): void {
|
|
23
|
+
if (typeof bundle.version !== "string" || bundle.version.length === 0)
|
|
24
|
+
throw new Error("Documentation bundle version must not be empty");
|
|
22
25
|
const paths = new Set<string>();
|
|
23
26
|
for (const file of bundle.files) {
|
|
24
|
-
if (typeof file.content !== "string")
|
|
25
|
-
|
|
27
|
+
if (typeof file.content !== "string")
|
|
28
|
+
throw new Error(`Documentation asset must contain text: ${file.path}`);
|
|
29
|
+
if (
|
|
30
|
+
typeof file.path !== "string" ||
|
|
31
|
+
!/^[a-zA-Z0-9._/-]+$/.test(file.path) ||
|
|
32
|
+
file.path
|
|
33
|
+
.split("/")
|
|
34
|
+
.some(
|
|
35
|
+
(part) =>
|
|
36
|
+
part === "" ||
|
|
37
|
+
part === "." ||
|
|
38
|
+
part === ".." ||
|
|
39
|
+
part.endsWith(".") ||
|
|
40
|
+
/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part),
|
|
41
|
+
)
|
|
42
|
+
) {
|
|
26
43
|
throw new Error(`Invalid documentation asset path: ${file.path}`);
|
|
27
44
|
}
|
|
28
45
|
const portablePath = file.path.toLowerCase();
|
|
29
|
-
if (paths.has(portablePath))
|
|
46
|
+
if (paths.has(portablePath))
|
|
47
|
+
throw new Error(`Duplicate documentation asset path: ${file.path}`);
|
|
30
48
|
paths.add(portablePath);
|
|
31
49
|
}
|
|
32
50
|
for (const path of paths) {
|
|
33
51
|
const parts = path.split("/");
|
|
34
52
|
while (parts.pop() && parts.length > 0) {
|
|
35
|
-
if (paths.has(parts.join("/")))
|
|
53
|
+
if (paths.has(parts.join("/")))
|
|
54
|
+
throw new Error(`Documentation file/directory collision: ${path}`);
|
|
36
55
|
}
|
|
37
56
|
}
|
|
38
57
|
for (const path of [DOCUMENTATION_PATHS.readme, DOCUMENTATION_PATHS.index]) {
|
|
39
|
-
if (!bundle.files.some(file => file.path === path))
|
|
58
|
+
if (!bundle.files.some((file) => file.path === path))
|
|
59
|
+
throw new Error(`Missing documentation asset: ${path}`);
|
|
40
60
|
}
|
|
41
|
-
if (!bundle.files.some(file => file.path.startsWith("examples/")))
|
|
61
|
+
if (!bundle.files.some((file) => file.path.startsWith("examples/")))
|
|
62
|
+
throw new Error("Missing documentation examples");
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
export function hashDocumentationBundle(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
export function hashDocumentationBundle(
|
|
66
|
+
bundle: NornDocumentationBundle,
|
|
67
|
+
): string {
|
|
68
|
+
return createHash("sha256")
|
|
69
|
+
.update(
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
version: bundle.version,
|
|
72
|
+
files: bundle.files
|
|
73
|
+
.map((file) => ({ path: file.path, content: file.content }))
|
|
74
|
+
.sort((left, right) =>
|
|
75
|
+
left.path < right.path ? -1 : left.path > right.path ? 1 : 0,
|
|
76
|
+
),
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
.digest("hex");
|
|
49
80
|
}
|