@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
@@ -1,7 +1,21 @@
1
1
  // src/internal/run-store.ts
2
2
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
3
3
  import { constants } from "node:fs";
4
- import { access, chmod as chmod2, lstat, mkdir as mkdir2, mkdtemp, readFile, readdir, readlink, rename as rename2, rm as rm2, symlink, utimes, writeFile as writeFile2 } from "node:fs/promises";
4
+ import {
5
+ access,
6
+ chmod as chmod2,
7
+ lstat,
8
+ mkdir as mkdir2,
9
+ mkdtemp,
10
+ readFile,
11
+ readdir,
12
+ readlink,
13
+ rename as rename2,
14
+ rm as rm2,
15
+ symlink,
16
+ utimes,
17
+ writeFile as writeFile2
18
+ } from "node:fs/promises";
5
19
  import { dirname as dirname2, isAbsolute, join, relative, resolve, sep } from "node:path";
6
20
  import { promisify } from "node:util";
7
21
  import { gunzip, gzip } from "node:zlib";
@@ -76,7 +90,10 @@ var NornRunStore = class _NornRunStore {
76
90
  }
77
91
  async snapshotCurrent(message) {
78
92
  const previous = await this.readCheckpointHistory();
79
- const checkpoint = this.createCheckpoint(message, previous.checkpoints.length + 1);
93
+ const checkpoint = this.createCheckpoint(
94
+ message,
95
+ previous.checkpoints.length + 1
96
+ );
80
97
  const checkpoints = [...previous.checkpoints, checkpoint];
81
98
  await this.createSnapshot(checkpoint, checkpoints);
82
99
  await writeJsonAtomically(this.checkpointsPath, checkpoints);
@@ -84,19 +101,28 @@ var NornRunStore = class _NornRunStore {
84
101
  await writeTextAtomically(this.currentRefPath, `${checkpoint.id}
85
102
  `);
86
103
  } catch (error) {
87
- if (previous.content === void 0) await rm2(this.checkpointsPath, { force: true });
104
+ if (previous.content === void 0)
105
+ await rm2(this.checkpointsPath, { force: true });
88
106
  else await writeTextAtomically(this.checkpointsPath, previous.content);
89
107
  throw error;
90
108
  }
91
109
  return checkpoint;
92
110
  }
93
111
  async restoreSnapshot(ref, prepare) {
94
- const checkpoint = (await this.listCheckpoints()).find((entry) => entry.id === ref);
112
+ const checkpoint = (await this.listCheckpoints()).find(
113
+ (entry) => entry.id === ref
114
+ );
95
115
  if (!checkpoint) throw new Error(`Unknown active run checkpoint: ${ref}`);
96
- await this.restoreSnapshotManifest(await this.readSnapshot(checkpoint.id), prepare);
116
+ await this.restoreSnapshotManifest(
117
+ await this.readSnapshot(checkpoint.id),
118
+ prepare
119
+ );
97
120
  }
98
121
  async restoreCurrentSnapshot() {
99
- await this.restoreSnapshotManifest(await this.readSnapshot(await this.currentSnapshotRef()), void 0);
122
+ await this.restoreSnapshotManifest(
123
+ await this.readSnapshot(await this.currentSnapshotRef()),
124
+ void 0
125
+ );
100
126
  }
101
127
  async listCheckpoints() {
102
128
  return (await this.readCheckpointHistory()).checkpoints;
@@ -105,10 +131,12 @@ var NornRunStore = class _NornRunStore {
105
131
  try {
106
132
  const content = await readFile(this.checkpointsPath, "utf8");
107
133
  const checkpoints = parseNornRunCheckpoints(JSON.parse(content));
108
- for (const checkpoint of checkpoints) this.assertCheckpointPathMatchesId(checkpoint);
134
+ for (const checkpoint of checkpoints)
135
+ this.assertCheckpointPathMatchesId(checkpoint);
109
136
  return { checkpoints, content };
110
137
  } catch (error) {
111
- if (isNodeError(error) && error.code === "ENOENT") return { checkpoints: [] };
138
+ if (isNodeError(error) && error.code === "ENOENT")
139
+ return { checkpoints: [] };
112
140
  throw error;
113
141
  }
114
142
  }
@@ -131,15 +159,33 @@ var NornRunStore = class _NornRunStore {
131
159
  }
132
160
  createCheckpoint(message, index) {
133
161
  const id = `cp_${randomUUID2().replaceAll("-", "")}`;
134
- return { id, path: this.snapshotRelativePath(id), index, message, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
162
+ return {
163
+ id,
164
+ path: this.snapshotRelativePath(id),
165
+ index,
166
+ message,
167
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
168
+ };
135
169
  }
136
170
  async createSnapshot(checkpoint, checkpoints) {
137
- const entries = (await this.snapshotEntries(this.currentRoot)).filter((entry) => entry.path !== CHECKPOINTS_FILE_NAME);
138
- const historyBytes = Buffer.from(`${JSON.stringify(checkpoints, null, 2)}
139
- `);
171
+ const entries = (await this.snapshotEntries(this.currentRoot)).filter(
172
+ (entry) => entry.path !== CHECKPOINTS_FILE_NAME
173
+ );
174
+ const historyBytes = Buffer.from(
175
+ `${JSON.stringify(checkpoints, null, 2)}
176
+ `
177
+ );
140
178
  const historyHash = hashBuffer(historyBytes);
141
179
  await this.storeObject(historyHash, historyBytes);
142
- entries.push({ path: CHECKPOINTS_FILE_NAME, type: "file", sha256: historyHash, size: historyBytes.length, mode: 384, mtimeMs: Date.now(), compression: "gzip" });
180
+ entries.push({
181
+ path: CHECKPOINTS_FILE_NAME,
182
+ type: "file",
183
+ sha256: historyHash,
184
+ size: historyBytes.length,
185
+ mode: 384,
186
+ mtimeMs: Date.now(),
187
+ compression: "gzip"
188
+ });
143
189
  entries.sort((left, right) => left.path.localeCompare(right.path));
144
190
  const snapshot = {
145
191
  version: SNAPSHOT_VERSION,
@@ -161,22 +207,45 @@ var NornRunStore = class _NornRunStore {
161
207
  async collectSnapshotEntries(absolutePath, snapshotPath, entries) {
162
208
  const stat2 = await lstat(absolutePath);
163
209
  if (stat2.isDirectory()) {
164
- if (snapshotPath.length > 0) entries.push({ path: snapshotPath, type: "directory", mode: stat2.mode });
210
+ if (snapshotPath.length > 0)
211
+ entries.push({
212
+ path: snapshotPath,
213
+ type: "directory",
214
+ mode: stat2.mode
215
+ });
165
216
  const children = await readdir(absolutePath, { withFileTypes: true });
166
- for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
167
- await this.collectSnapshotEntries(join(absolutePath, child.name), joinSnapshotPath(snapshotPath, child.name), entries);
217
+ for (const child of children.sort(
218
+ (left, right) => left.name.localeCompare(right.name)
219
+ )) {
220
+ await this.collectSnapshotEntries(
221
+ join(absolutePath, child.name),
222
+ joinSnapshotPath(snapshotPath, child.name),
223
+ entries
224
+ );
168
225
  }
169
226
  return;
170
227
  }
171
228
  if (stat2.isSymbolicLink()) {
172
- entries.push({ path: snapshotPath, type: "symlink", target: await readlink(absolutePath) });
229
+ entries.push({
230
+ path: snapshotPath,
231
+ type: "symlink",
232
+ target: await readlink(absolutePath)
233
+ });
173
234
  return;
174
235
  }
175
236
  if (!stat2.isFile()) return;
176
237
  const bytes = await readFile(absolutePath);
177
238
  const sha256 = hashBuffer(bytes);
178
239
  await this.storeObject(sha256, bytes);
179
- entries.push({ path: snapshotPath, type: "file", sha256, size: stat2.size, mode: stat2.mode, mtimeMs: stat2.mtimeMs, compression: "gzip" });
240
+ entries.push({
241
+ path: snapshotPath,
242
+ type: "file",
243
+ sha256,
244
+ size: stat2.size,
245
+ mode: stat2.mode,
246
+ mtimeMs: stat2.mtimeMs,
247
+ compression: "gzip"
248
+ });
180
249
  }
181
250
  async storeObject(sha256, bytes) {
182
251
  const path = this.objectPath(sha256);
@@ -214,25 +283,34 @@ var NornRunStore = class _NornRunStore {
214
283
  await rename2(previousCurrent, this.currentRoot);
215
284
  throw error;
216
285
  }
217
- await rm2(previousCurrent, { recursive: true, force: true }).catch(() => void 0);
286
+ await rm2(previousCurrent, { recursive: true, force: true }).catch(
287
+ () => void 0
288
+ );
218
289
  } finally {
219
- await rm2(stagedRunRoot, { recursive: true, force: true }).catch(() => void 0);
290
+ await rm2(stagedRunRoot, { recursive: true, force: true }).catch(
291
+ () => void 0
292
+ );
220
293
  }
221
294
  }
222
295
  async materializeSnapshot(snapshot, destination) {
223
296
  const entriesByPath = /* @__PURE__ */ new Map();
224
297
  for (const entry of snapshot.entries) {
225
298
  this.materializedPath(destination, entry.path);
226
- if (entriesByPath.has(entry.path)) throw new Error(`Duplicate snapshot path: ${entry.path}`);
299
+ if (entriesByPath.has(entry.path))
300
+ throw new Error(`Duplicate snapshot path: ${entry.path}`);
227
301
  entriesByPath.set(entry.path, entry);
228
302
  }
229
303
  for (const entry of snapshot.entries) {
230
304
  let parent = dirname2(entry.path);
231
305
  while (parent !== ".") {
232
- if (entriesByPath.get(parent)?.type !== "directory") throw new Error(`Snapshot parent is not a directory: ${parent}`);
306
+ if (entriesByPath.get(parent)?.type !== "directory")
307
+ throw new Error(`Snapshot parent is not a directory: ${parent}`);
233
308
  parent = dirname2(parent);
234
309
  }
235
- if (entry.type === "directory") await mkdir2(this.materializedPath(destination, entry.path), { recursive: true });
310
+ if (entry.type === "directory")
311
+ await mkdir2(this.materializedPath(destination, entry.path), {
312
+ recursive: true
313
+ });
236
314
  }
237
315
  for (const entry of snapshot.entries) {
238
316
  if (entry.type === "directory") continue;
@@ -241,26 +319,36 @@ var NornRunStore = class _NornRunStore {
241
319
  await symlink(entry.target, path);
242
320
  continue;
243
321
  }
244
- const bytes = await gunzipBuffer(await readFile(this.objectPath(entry.sha256)));
322
+ const bytes = await gunzipBuffer(
323
+ await readFile(this.objectPath(entry.sha256))
324
+ );
245
325
  const actualSha256 = hashBuffer(bytes);
246
- if (actualSha256 !== entry.sha256) throw new Error(`CAS object checksum mismatch: ${entry.sha256}`);
326
+ if (actualSha256 !== entry.sha256)
327
+ throw new Error(`CAS object checksum mismatch: ${entry.sha256}`);
247
328
  await writeFile2(path, bytes);
248
329
  await chmod2(path, entry.mode);
249
330
  await utimes(path, new Date(entry.mtimeMs), new Date(entry.mtimeMs));
250
331
  }
251
332
  for (const entry of [...snapshot.entries].reverse()) {
252
- if (entry.type === "directory") await chmod2(this.materializedPath(destination, entry.path), entry.mode);
333
+ if (entry.type === "directory")
334
+ await chmod2(this.materializedPath(destination, entry.path), entry.mode);
253
335
  }
254
336
  }
255
337
  materializedPath(destination, snapshotPath) {
256
- if (snapshotPath.length === 0 || isAbsolute(snapshotPath) || snapshotPath.split(/[\\/]/).some((part) => part === ".." || part === "." || part === "")) throw new Error(`Invalid snapshot path: ${snapshotPath}`);
338
+ if (snapshotPath.length === 0 || isAbsolute(snapshotPath) || snapshotPath.split(/[\\/]/).some((part) => part === ".." || part === "." || part === ""))
339
+ throw new Error(`Invalid snapshot path: ${snapshotPath}`);
257
340
  const path = resolve(destination, ...snapshotPath.split("/"));
258
341
  const pathFromCurrent = relative(destination, path);
259
- if (pathFromCurrent === ".." || pathFromCurrent.startsWith(`..${sep}`)) throw new Error(`Snapshot path escapes current checkout: ${snapshotPath}`);
342
+ if (pathFromCurrent === ".." || pathFromCurrent.startsWith(`..${sep}`))
343
+ throw new Error(
344
+ `Snapshot path escapes current checkout: ${snapshotPath}`
345
+ );
260
346
  return path;
261
347
  }
262
348
  async readSnapshot(id) {
263
- return parseRunSnapshotManifest(JSON.parse(await readFile(this.snapshotPath(id), "utf8")));
349
+ return parseRunSnapshotManifest(
350
+ JSON.parse(await readFile(this.snapshotPath(id), "utf8"))
351
+ );
264
352
  }
265
353
  snapshotPath(id) {
266
354
  return join(this.snapshotsRoot, `${id}.json`);
@@ -270,10 +358,18 @@ var NornRunStore = class _NornRunStore {
270
358
  }
271
359
  assertCheckpointPathMatchesId(checkpoint) {
272
360
  const expectedPath = this.snapshotRelativePath(checkpoint.id);
273
- if (checkpoint.path !== expectedPath) throw new Error(`Run checkpoint path does not match id: ${checkpoint.id}`);
361
+ if (checkpoint.path !== expectedPath)
362
+ throw new Error(
363
+ `Run checkpoint path does not match id: ${checkpoint.id}`
364
+ );
274
365
  }
275
366
  objectPath(sha256) {
276
- return join(this.objectsRoot, sha256.slice(0, 2), sha256.slice(2, 4), `${sha256}.gz`);
367
+ return join(
368
+ this.objectsRoot,
369
+ sha256.slice(0, 2),
370
+ sha256.slice(2, 4),
371
+ `${sha256}.gz`
372
+ );
277
373
  }
278
374
  };
279
375
  function hashBuffer(bytes) {
@@ -283,14 +379,23 @@ function joinSnapshotPath(parent, child) {
283
379
  return parent.length === 0 ? child : `${parent}/${child}`;
284
380
  }
285
381
  function parseRunSnapshotManifest(value) {
286
- if (!value || typeof value !== "object") throw new Error("Invalid run snapshot manifest");
382
+ if (!value || typeof value !== "object")
383
+ throw new Error("Invalid run snapshot manifest");
287
384
  const snapshot = value;
288
- if (snapshot.version !== SNAPSHOT_VERSION) throw new Error(`Unsupported run snapshot version: ${String(snapshot.version)}`);
289
- if (typeof snapshot.id !== "string" || snapshot.id.length === 0) throw new Error("Invalid run snapshot id");
290
- if (typeof snapshot.index !== "number" || !Number.isInteger(snapshot.index) || snapshot.index < 1) throw new Error("Invalid run snapshot index");
291
- if (typeof snapshot.message !== "string") throw new Error("Invalid run snapshot message");
292
- if (typeof snapshot.createdAt !== "string") throw new Error("Invalid run snapshot timestamp");
293
- if (!Array.isArray(snapshot.entries)) throw new Error("Invalid run snapshot entries");
385
+ if (snapshot.version !== SNAPSHOT_VERSION)
386
+ throw new Error(
387
+ `Unsupported run snapshot version: ${String(snapshot.version)}`
388
+ );
389
+ if (typeof snapshot.id !== "string" || snapshot.id.length === 0)
390
+ throw new Error("Invalid run snapshot id");
391
+ if (typeof snapshot.index !== "number" || !Number.isInteger(snapshot.index) || snapshot.index < 1)
392
+ throw new Error("Invalid run snapshot index");
393
+ if (typeof snapshot.message !== "string")
394
+ throw new Error("Invalid run snapshot message");
395
+ if (typeof snapshot.createdAt !== "string")
396
+ throw new Error("Invalid run snapshot timestamp");
397
+ if (!Array.isArray(snapshot.entries))
398
+ throw new Error("Invalid run snapshot entries");
294
399
  return snapshot;
295
400
  }
296
401
  function parseNornRunCheckpoints(value) {
@@ -298,14 +403,21 @@ function parseNornRunCheckpoints(value) {
298
403
  return value.map(parseNornRunCheckpoint);
299
404
  }
300
405
  function parseNornRunCheckpoint(value) {
301
- if (!value || typeof value !== "object") throw new Error("Invalid run checkpoint");
406
+ if (!value || typeof value !== "object")
407
+ throw new Error("Invalid run checkpoint");
302
408
  const checkpoint = value;
303
- if (typeof checkpoint.id !== "string" || checkpoint.id.length === 0) throw new Error("Invalid run checkpoint id");
304
- if (typeof checkpoint.path !== "string" || checkpoint.path.length === 0) throw new Error("Invalid run checkpoint path");
305
- if (checkpoint.path.split(/[\\/]/).includes("..") || resolve(checkpoint.path) === checkpoint.path) throw new Error(`Invalid run checkpoint path: ${checkpoint.path}`);
306
- if (typeof checkpoint.index !== "number" || !Number.isInteger(checkpoint.index) || checkpoint.index < 1) throw new Error("Invalid run checkpoint index");
307
- if (typeof checkpoint.message !== "string") throw new Error("Invalid run checkpoint message");
308
- if (typeof checkpoint.createdAt !== "string") throw new Error("Invalid run checkpoint timestamp");
409
+ if (typeof checkpoint.id !== "string" || checkpoint.id.length === 0)
410
+ throw new Error("Invalid run checkpoint id");
411
+ if (typeof checkpoint.path !== "string" || checkpoint.path.length === 0)
412
+ throw new Error("Invalid run checkpoint path");
413
+ if (checkpoint.path.split(/[\\/]/).includes("..") || resolve(checkpoint.path) === checkpoint.path)
414
+ throw new Error(`Invalid run checkpoint path: ${checkpoint.path}`);
415
+ if (typeof checkpoint.index !== "number" || !Number.isInteger(checkpoint.index) || checkpoint.index < 1)
416
+ throw new Error("Invalid run checkpoint index");
417
+ if (typeof checkpoint.message !== "string")
418
+ throw new Error("Invalid run checkpoint message");
419
+ if (typeof checkpoint.createdAt !== "string")
420
+ throw new Error("Invalid run checkpoint timestamp");
309
421
  return checkpoint;
310
422
  }
311
423
  export {
@@ -16,8 +16,12 @@ async function prepareRunWorkerDirectory(runRoot) {
16
16
  if (!isNodeError(error) || error.code !== "EEXIST") throw error;
17
17
  }
18
18
  const metadata = await lstat(directory);
19
- if (!metadata.isDirectory() || metadata.isSymbolicLink()) throw new Error(`Run worker cwd must be a directory, not a symlink: ${directory}`);
20
- if ((await readdir(directory)).length !== 0) throw new Error(`Run worker cwd must be empty: ${directory}`);
19
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
20
+ throw new Error(
21
+ `Run worker cwd must be a directory, not a symlink: ${directory}`
22
+ );
23
+ if ((await readdir(directory)).length !== 0)
24
+ throw new Error(`Run worker cwd must be empty: ${directory}`);
21
25
  await chmod(directory, 365);
22
26
  return directory;
23
27
  }
@@ -1,25 +1,60 @@
1
1
  // src/internal/workflow-registry.ts
2
- import { isWorkflowComplete, isWorkflowDeclaration, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
3
- import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
2
+ import {
3
+ isWorkflowComplete,
4
+ isWorkflowDeclaration,
5
+ isWorkflowFail,
6
+ isWorkflowNext
7
+ } from "@vimhead.dev/norn";
8
+ import {
9
+ assertWorkflowMetadata,
10
+ inspectSchema,
11
+ isPlainObject,
12
+ schemaShape,
13
+ schemaType,
14
+ unwrapSchema
15
+ } from "@vimhead.dev/norn/schema";
4
16
  import { Value } from "typebox/value";
5
17
  function decodeConfiguration(input) {
6
18
  if (!input.schema) {
7
- if (input.value !== void 0) throw new Error(`Norn config provided without config schema: ${input.key}`);
8
- return { key: input.key, schema: void 0, input: void 0, value: void 0 };
19
+ if (input.value !== void 0)
20
+ throw new Error(
21
+ `Norn config provided without config schema: ${input.key}`
22
+ );
23
+ return {
24
+ key: input.key,
25
+ schema: void 0,
26
+ input: void 0,
27
+ value: void 0
28
+ };
9
29
  }
10
30
  const value = input.value === void 0 && schemaType(unwrapSchema(input.schema)) === "object" ? {} : input.value;
11
- return { key: input.key, schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
31
+ return {
32
+ key: input.key,
33
+ schema: input.schema,
34
+ input: value,
35
+ value: Value.Decode(input.schema, value)
36
+ };
12
37
  }
13
38
  function assertCompatibleConfigurationOwners(workflows) {
14
39
  const owners = /* @__PURE__ */ new Map();
15
40
  for (const workflow of workflows) {
16
41
  for (const owner of [
17
42
  { key: workflow.id, kind: "workflow", definition: workflow },
18
- ...workflow.scope ? [{ key: workflow.scope.id, kind: "scope", definition: workflow.scope }] : []
43
+ ...workflow.scope ? [
44
+ {
45
+ key: workflow.scope.id,
46
+ kind: "scope",
47
+ definition: workflow.scope
48
+ }
49
+ ] : []
19
50
  ]) {
20
51
  const previous = owners.get(owner.key);
21
- if (previous && previous.kind !== owner.kind) throw new Error(`Ambiguous configuration key: ${owner.key}`);
22
- if (previous && owner.kind === "scope" && previous.definition !== owner.definition) throw new Error(`Duplicate workflow scope id: ${owner.key}; import one shared scope definition`);
52
+ if (previous && previous.kind !== owner.kind)
53
+ throw new Error(`Ambiguous configuration key: ${owner.key}`);
54
+ if (previous && owner.kind === "scope" && previous.definition !== owner.definition)
55
+ throw new Error(
56
+ `Duplicate workflow scope id: ${owner.key}; import one shared scope definition`
57
+ );
23
58
  owners.set(owner.key, owner);
24
59
  }
25
60
  }
@@ -30,25 +65,40 @@ var NornWorkflowRegistry = class {
30
65
  register(input) {
31
66
  const { workflow, config, source } = input;
32
67
  assertWorkflowDefinition(workflow);
33
- if (this.entries.has(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
34
- assertCompatibleConfigurationOwners([...this.sortedEntries().map((entry2) => entry2.workflow), workflow]);
68
+ if (this.entries.has(workflow.id))
69
+ throw new Error(`Workflow already registered: ${workflow.id}`);
70
+ assertCompatibleConfigurationOwners([
71
+ ...this.sortedEntries().map((entry2) => entry2.workflow),
72
+ workflow
73
+ ]);
35
74
  const entry = {
36
75
  workflow,
37
76
  source,
38
77
  scopeId: workflow.scope?.id,
39
- configuration: decodeConfiguration({ key: workflow.id, schema: workflow.config, value: configurationInput(config, workflow.id) })
78
+ configuration: decodeConfiguration({
79
+ key: workflow.id,
80
+ schema: workflow.config,
81
+ value: configurationInput(config, workflow.id)
82
+ })
40
83
  };
41
84
  if (workflow.scope && !this.scopes.has(workflow.scope.id)) {
42
85
  this.scopes.set(workflow.scope.id, {
43
86
  definition: workflow.scope,
44
- configuration: decodeConfiguration({ key: workflow.scope.id, schema: workflow.scope.config, value: configurationInput(config, workflow.scope.id) })
87
+ configuration: decodeConfiguration({
88
+ key: workflow.scope.id,
89
+ schema: workflow.scope.config,
90
+ value: configurationInput(config, workflow.scope.id)
91
+ })
45
92
  });
46
93
  }
47
94
  this.entries.set(workflow.id, entry);
48
95
  return () => {
49
96
  if (this.entries.get(workflow.id) !== entry) return;
50
97
  this.entries.delete(workflow.id);
51
- if (entry.scopeId && ![...this.entries.values()].some((other) => other.scopeId === entry.scopeId)) this.scopes.delete(entry.scopeId);
98
+ if (entry.scopeId && ![...this.entries.values()].some(
99
+ (other) => other.scopeId === entry.scopeId
100
+ ))
101
+ this.scopes.delete(entry.scopeId);
52
102
  };
53
103
  }
54
104
  list(options = {}) {
@@ -71,12 +121,25 @@ var NornWorkflowRegistry = class {
71
121
  }
72
122
  configurationInfos() {
73
123
  return [
74
- ...this.sortedEntries().map((entry) => ({ configuration: entry.configuration, scopeId: null })),
75
- ...[...this.scopes.values()].map((scope) => ({ configuration: scope.configuration, scopeId: scope.definition.id }))
76
- ].map(({ configuration, scopeId }) => ({ key: configuration.key, scopeId, configSchema: configuration.schema ? inspectSchema(configuration.schema) : null, config: configuration.value }));
124
+ ...this.sortedEntries().map((entry) => ({
125
+ configuration: entry.configuration,
126
+ scopeId: null
127
+ })),
128
+ ...[...this.scopes.values()].map((scope) => ({
129
+ configuration: scope.configuration,
130
+ scopeId: scope.definition.id
131
+ }))
132
+ ].map(({ configuration, scopeId }) => ({
133
+ key: configuration.key,
134
+ scopeId,
135
+ configSchema: configuration.schema ? inspectSchema(configuration.schema) : null,
136
+ config: configuration.value
137
+ }));
77
138
  }
78
139
  launchableEntries() {
79
- return this.sortedEntries().filter(({ workflow }) => workflow.entrypoint !== false);
140
+ return this.sortedEntries().filter(
141
+ ({ workflow }) => workflow.entrypoint !== false
142
+ );
80
143
  }
81
144
  workflowById(workflowId) {
82
145
  return this.entries.get(workflowId)?.workflow;
@@ -84,19 +147,27 @@ var NornWorkflowRegistry = class {
84
147
  async describeGate(input) {
85
148
  const { workflow } = input;
86
149
  const entry = this.requireEntry(workflow.id);
87
- if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
150
+ if (!workflow.gate)
151
+ throw new Error(`Workflow is not gated: ${workflow.id}`);
88
152
  const context = this.createExecutionContext({ ...input, entry });
89
153
  const description = await entry.workflow.gate?.describe?.(context) ?? workflow.id;
90
- if (typeof description !== "string" || description.trim().length === 0) throw new Error(`Workflow gate description must not be empty: ${workflow.id}`);
154
+ if (typeof description !== "string" || description.trim().length === 0)
155
+ throw new Error(
156
+ `Workflow gate description must not be empty: ${workflow.id}`
157
+ );
91
158
  return description.trim();
92
159
  }
93
160
  async execute(input) {
94
161
  const { workflow } = input;
95
162
  const entry = this.requireEntry(workflow.id);
96
- const result = await entry.workflow.execute(this.createExecutionContext({ ...input, entry }));
163
+ const result = await entry.workflow.execute(
164
+ this.createExecutionContext({ ...input, entry })
165
+ );
97
166
  if (isWorkflowNext(result)) return result;
98
- if (isWorkflowComplete(result)) return { type: "complete", workflow, metadata: result.metadata };
99
- if (isWorkflowFail(result)) return { type: "fail", workflow, metadata: result.metadata };
167
+ if (isWorkflowComplete(result))
168
+ return { type: "complete", workflow, metadata: result.metadata };
169
+ if (isWorkflowFail(result))
170
+ return { type: "fail", workflow, metadata: result.metadata };
100
171
  throw new Error(`Workflow returned invalid control result: ${workflow.id}`);
101
172
  }
102
173
  createExecutionContext(input) {
@@ -105,7 +176,12 @@ var NornWorkflowRegistry = class {
105
176
  return {
106
177
  args: Value.Decode(entry.workflow.args, input.args),
107
178
  config: parseExecutionConfig(entry.configuration, configOverride),
108
- ...scope ? { scope: { id: scope.definition.id, config: parseExecutionConfig(scope.configuration, configOverride) } } : {},
179
+ ...scope ? {
180
+ scope: {
181
+ id: scope.definition.id,
182
+ config: parseExecutionConfig(scope.configuration, configOverride)
183
+ }
184
+ } : {},
109
185
  paths,
110
186
  run: execution.run,
111
187
  agents: execution.agents,
@@ -119,39 +195,54 @@ var NornWorkflowRegistry = class {
119
195
  return entry;
120
196
  }
121
197
  sortedEntries() {
122
- return [...this.entries.values()].sort((left, right) => left.workflow.id.localeCompare(right.workflow.id));
198
+ return [...this.entries.values()].sort(
199
+ (left, right) => left.workflow.id.localeCompare(right.workflow.id)
200
+ );
123
201
  }
124
202
  };
125
203
  function configurationInput(config, key) {
126
204
  return Object.hasOwn(config, key) ? config[key] : void 0;
127
205
  }
128
206
  function parseExecutionConfig(configuration, overrides) {
129
- if (overrides !== void 0 && !isPlainObject(overrides)) throw new Error("Run config override must be an object keyed by workflow or scope id");
207
+ if (overrides !== void 0 && !isPlainObject(overrides))
208
+ throw new Error(
209
+ "Run config override must be an object keyed by workflow or scope id"
210
+ );
130
211
  const override = overrides ? configurationInput(overrides, configuration.key) : void 0;
131
212
  if (override === void 0) return configuration.value;
132
- if (!configuration.schema) throw new Error(`Run config override provided without config schema: ${configuration.key}`);
213
+ if (!configuration.schema)
214
+ throw new Error(
215
+ `Run config override provided without config schema: ${configuration.key}`
216
+ );
133
217
  const input = isPlainObject(configuration.input) && isPlainObject(override) ? deepMerge(configuration.input, override) : override;
134
218
  return Value.Decode(configuration.schema, input);
135
219
  }
136
220
  function deepMerge(base, override) {
137
221
  const result = { ...base };
138
- for (const [key, value] of Object.entries(override)) result[key] = isPlainObject(result[key]) && isPlainObject(value) ? deepMerge(result[key], value) : value;
222
+ for (const [key, value] of Object.entries(override))
223
+ result[key] = isPlainObject(result[key]) && isPlainObject(value) ? deepMerge(result[key], value) : value;
139
224
  return result;
140
225
  }
141
226
  function assertWorkflowDefinition(workflow) {
142
227
  assertWorkflowMetadata(workflow);
143
- if (!isWorkflowDeclaration(workflow)) throw new Error("Invalid workflow definition");
228
+ if (!isWorkflowDeclaration(workflow))
229
+ throw new Error("Invalid workflow definition");
144
230
  assertGateWorkflow(workflow);
145
231
  }
146
232
  function assertGateWorkflow(workflow) {
147
233
  if (!workflow.gate) return;
148
- if (workflow.gate.enabled !== true) throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
149
- if (workflow.gate.describe !== void 0 && typeof workflow.gate.describe !== "function") throw new Error(`Invalid gate description callback: ${workflow.id}`);
234
+ if (workflow.gate.enabled !== true)
235
+ throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
236
+ if (workflow.gate.describe !== void 0 && typeof workflow.gate.describe !== "function")
237
+ throw new Error(`Invalid gate description callback: ${workflow.id}`);
150
238
  if (!workflow.gate.fields) return;
151
239
  const argsSchema = unwrapSchema(workflow.args);
152
- if (schemaType(argsSchema) !== "object") throw new Error(`Workflow gate fields require object args: ${workflow.id}`);
240
+ if (schemaType(argsSchema) !== "object")
241
+ throw new Error(`Workflow gate fields require object args: ${workflow.id}`);
153
242
  const shape = schemaShape(argsSchema);
154
- for (const field of workflow.gate.fields) if (!Object.hasOwn(shape, field)) throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
243
+ for (const field of workflow.gate.fields)
244
+ if (!Object.hasOwn(shape, field))
245
+ throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
155
246
  }
156
247
  function workflowInfo(entry) {
157
248
  return {
@@ -1,7 +1,8 @@
1
1
  // src/internal/working-directory.ts
2
2
  import { isAbsolute, resolve } from "node:path";
3
3
  function requireAbsoluteWorkingDirectory(cwd) {
4
- if (typeof cwd !== "string" || !isAbsolute(cwd)) throw new Error("Working directory (cwd) must be an absolute path");
4
+ if (typeof cwd !== "string" || !isAbsolute(cwd))
5
+ throw new Error("Working directory (cwd) must be an absolute path");
5
6
  return resolve(cwd);
6
7
  }
7
8
  export {