@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
@@ -80,7 +80,8 @@ var NornRunLease = class _NornRunLease {
80
80
  async assertOwned() {
81
81
  if (this.isReleased) throw new Error("Run lease has been released");
82
82
  const owner = await readRunLeaseOwner(this.ownerPath);
83
- if (!owner || owner.token !== this.token) throw new Error("Run lease was lost");
83
+ if (!owner || owner.token !== this.token)
84
+ throw new Error("Run lease was lost");
84
85
  }
85
86
  async release() {
86
87
  if (this.isReleased) return;
@@ -88,7 +89,8 @@ var NornRunLease = class _NornRunLease {
88
89
  if (this.heartbeat) clearInterval(this.heartbeat);
89
90
  await this.heartbeatChain.catch(() => void 0);
90
91
  const owner = await readRunLeaseOwner(this.ownerPath);
91
- if (owner?.token === this.token) await rm2(this.lockRoot, { recursive: true, force: true });
92
+ if (owner?.token === this.token)
93
+ await rm2(this.lockRoot, { recursive: true, force: true });
92
94
  }
93
95
  get ownerPath() {
94
96
  return join(this.lockRoot, OWNER_FILE_NAME);
@@ -105,12 +107,18 @@ var NornRunLease = class _NornRunLease {
105
107
  async refreshHeartbeat() {
106
108
  if (this.isReleased) return;
107
109
  const owner = await readRunLeaseOwner(this.ownerPath);
108
- if (!owner || owner.token !== this.token) throw new Error("Run lease was lost");
110
+ if (!owner || owner.token !== this.token)
111
+ throw new Error("Run lease was lost");
109
112
  await this.writeOwner({ acquiredAt: owner.acquiredAt });
110
113
  }
111
114
  async writeOwner(input) {
112
115
  const now = (/* @__PURE__ */ new Date()).toISOString();
113
- await writeJsonAtomically(this.ownerPath, { token: this.token, acquiredAt: input.acquiredAt, heartbeatAt: now, ...this.processOwner });
116
+ await writeJsonAtomically(this.ownerPath, {
117
+ token: this.token,
118
+ acquiredAt: input.acquiredAt,
119
+ heartbeatAt: now,
120
+ ...this.processOwner
121
+ });
114
122
  }
115
123
  };
116
124
  async function getRunLeaseHealth(runRoot) {
@@ -140,13 +148,25 @@ async function readRunLeaseOwner(path) {
140
148
  function parseRunLeaseOwner(value) {
141
149
  if (!value || typeof value !== "object") return void 0;
142
150
  const owner = value;
143
- if (typeof owner.token !== "string" || owner.token.length === 0) return void 0;
144
- if (typeof owner.acquiredAt !== "string" || Number.isNaN(Date.parse(owner.acquiredAt))) return void 0;
145
- if (typeof owner.heartbeatAt !== "string" || Number.isNaN(Date.parse(owner.heartbeatAt))) return void 0;
151
+ if (typeof owner.token !== "string" || owner.token.length === 0)
152
+ return void 0;
153
+ if (typeof owner.acquiredAt !== "string" || Number.isNaN(Date.parse(owner.acquiredAt)))
154
+ return void 0;
155
+ if (typeof owner.heartbeatAt !== "string" || Number.isNaN(Date.parse(owner.heartbeatAt)))
156
+ return void 0;
146
157
  const pid = typeof owner.pid === "number" && Number.isInteger(owner.pid) ? owner.pid : 0;
147
158
  const processGroupId = typeof owner.processGroupId === "number" && Number.isInteger(owner.processGroupId) ? owner.processGroupId : pid;
148
- const command = Array.isArray(owner.command) ? owner.command.filter((value2) => typeof value2 === "string") : [];
149
- return { token: owner.token, acquiredAt: owner.acquiredAt, heartbeatAt: owner.heartbeatAt, pid, processGroupId, command };
159
+ const command = Array.isArray(owner.command) ? owner.command.filter(
160
+ (value2) => typeof value2 === "string"
161
+ ) : [];
162
+ return {
163
+ token: owner.token,
164
+ acquiredAt: owner.acquiredAt,
165
+ heartbeatAt: owner.heartbeatAt,
166
+ pid,
167
+ processGroupId,
168
+ command
169
+ };
150
170
  }
151
171
  function isRunLeaseStale(owner) {
152
172
  return Date.now() - Date.parse(owner.heartbeatAt) > HEARTBEAT_TTL_MS;
@@ -47,23 +47,35 @@ var NornRunLogger = class _NornRunLogger {
47
47
  return logger;
48
48
  }
49
49
  async record(event) {
50
- await this.input.files.withExclusiveLock(this.input.manifestPath, async (path) => {
51
- let manifest;
52
- try {
53
- manifest = parseManifest(await readFile(path, "utf8"));
54
- } catch (error) {
55
- if (this.isInitialized || !isNodeError(error) || error.code !== "ENOENT") throw error;
56
- manifest = { ...this.input.manifest, events: [] };
50
+ await this.input.files.withExclusiveLock(
51
+ this.input.manifestPath,
52
+ async (path) => {
53
+ let manifest;
54
+ try {
55
+ manifest = parseManifest(await readFile(path, "utf8"));
56
+ } catch (error) {
57
+ if (this.isInitialized || !isNodeError(error) || error.code !== "ENOENT")
58
+ throw error;
59
+ manifest = { ...this.input.manifest, events: [] };
60
+ }
61
+ if (manifest.id !== this.input.manifest.id)
62
+ throw new Error("Run manifest identity changed");
63
+ await writeJsonAtomically(path, {
64
+ ...manifest,
65
+ events: [
66
+ ...manifest.events,
67
+ { ...event, at: (/* @__PURE__ */ new Date()).toISOString() }
68
+ ]
69
+ });
70
+ this.isInitialized = true;
57
71
  }
58
- if (manifest.id !== this.input.manifest.id) throw new Error("Run manifest identity changed");
59
- await writeJsonAtomically(path, { ...manifest, events: [...manifest.events, { ...event, at: (/* @__PURE__ */ new Date()).toISOString() }] });
60
- this.isInitialized = true;
61
- });
72
+ );
62
73
  }
63
74
  };
64
75
  function parseManifest(content) {
65
76
  const manifest = JSON.parse(content);
66
- if (!manifest || typeof manifest !== "object" || typeof manifest.id !== "string" || !Array.isArray(manifest.events)) throw new Error("Invalid run manifest");
77
+ if (!manifest || typeof manifest !== "object" || typeof manifest.id !== "string" || !Array.isArray(manifest.events))
78
+ throw new Error("Invalid run manifest");
67
79
  return manifest;
68
80
  }
69
81
  export {
@@ -8,7 +8,11 @@ function isNodeError(error) {
8
8
  }
9
9
 
10
10
  // src/internal/run-state.ts
11
- import { readRunLaunchRequest, readOptionalRunResumeRequest, RESUME_START_GRACE_MS } from "./launch-request.js";
11
+ import {
12
+ readRunLaunchRequest,
13
+ readOptionalRunResumeRequest,
14
+ RESUME_START_GRACE_MS
15
+ } from "./launch-request.js";
12
16
  import { getRunLeaseHealth } from "./run-lease.js";
13
17
 
14
18
  // ../core/src/atomic-files.ts
@@ -39,7 +43,9 @@ async function writeTextAtomically(path, content) {
39
43
  }
40
44
 
41
45
  // src/internal/run-state.ts
42
- import { createRunFileCoordinator } from "./file-coordinator.js";
46
+ import {
47
+ createRunFileCoordinator
48
+ } from "./file-coordinator.js";
43
49
  import { runCurrentRoot } from "./run-store.js";
44
50
  import { jsonValueSchema } from "@vimhead.dev/norn/schema";
45
51
  import { Value } from "typebox/value";
@@ -48,8 +54,13 @@ var LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
48
54
  function mergeInterruptedWorkflowArgs(currentArgs, args, fields) {
49
55
  if (!isRecord(currentArgs) || !isRecord(args)) return args;
50
56
  if (fields) {
51
- const unsupportedFields = Object.keys(args).filter((field) => !fields.includes(field));
52
- if (unsupportedFields.length > 0) throw new Error(`Interrupted workflow args cannot update non-gate fields: ${unsupportedFields.join(", ")}`);
57
+ const unsupportedFields = Object.keys(args).filter(
58
+ (field) => !fields.includes(field)
59
+ );
60
+ if (unsupportedFields.length > 0)
61
+ throw new Error(
62
+ `Interrupted workflow args cannot update non-gate fields: ${unsupportedFields.join(", ")}`
63
+ );
53
64
  }
54
65
  return { ...currentArgs, ...args };
55
66
  }
@@ -81,27 +92,53 @@ var NornRunStateStore = class _NornRunStateStore {
81
92
  startedAt: now,
82
93
  updatedAt: now
83
94
  };
84
- const store = new _NornRunStateStore({ path: join(runCurrentRoot(runRoot), RUN_STATE_FILE_NAME), state, files: createRunFileCoordinator(runRoot) });
95
+ const store = new _NornRunStateStore({
96
+ path: join(runCurrentRoot(runRoot), RUN_STATE_FILE_NAME),
97
+ state,
98
+ files: createRunFileCoordinator(runRoot)
99
+ });
85
100
  await store.write();
86
101
  return store;
87
102
  }
88
103
  static async load(runRoot) {
89
104
  const currentRoot = runCurrentRoot(runRoot);
90
105
  const files = createRunFileCoordinator(runRoot);
91
- const state = await files.withExclusiveLock(join(currentRoot, RUN_STATE_FILE_NAME), async (path) => parseNornRunState(await readRunStateFile({ path, legacyPath: join(currentRoot, LEGACY_RUN_STATE_FILE_NAME) }), "execute"));
92
- return new _NornRunStateStore({ path: join(currentRoot, RUN_STATE_FILE_NAME), state, files });
106
+ const state = await files.withExclusiveLock(
107
+ join(currentRoot, RUN_STATE_FILE_NAME),
108
+ async (path) => parseNornRunState(
109
+ await readRunStateFile({
110
+ path,
111
+ legacyPath: join(currentRoot, LEGACY_RUN_STATE_FILE_NAME)
112
+ }),
113
+ "execute"
114
+ )
115
+ );
116
+ return new _NornRunStateStore({
117
+ path: join(currentRoot, RUN_STATE_FILE_NAME),
118
+ state,
119
+ files
120
+ });
93
121
  }
94
122
  currentState() {
95
123
  return this.state;
96
124
  }
97
125
  async startStep(step) {
98
- await this.update({ status: "running", current: step, outcome: null, failed: null });
126
+ await this.update({
127
+ status: "running",
128
+ current: step,
129
+ outcome: null,
130
+ failed: null
131
+ });
99
132
  }
100
133
  async completeWithNext(completedWorkflowId, next) {
101
134
  await this.update({
102
135
  status: "running",
103
136
  current: next,
104
- lastCompleted: { workflowId: completedWorkflowId, completedAt: (/* @__PURE__ */ new Date()).toISOString(), outcome: { type: "next", workflowId: next.workflowId } },
137
+ lastCompleted: {
138
+ workflowId: completedWorkflowId,
139
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
140
+ outcome: { type: "next", workflowId: next.workflowId }
141
+ },
105
142
  outcome: null,
106
143
  failed: null
107
144
  });
@@ -121,62 +158,115 @@ var NornRunStateStore = class _NornRunStateStore {
121
158
  await this.update({
122
159
  status: "failed",
123
160
  current: null,
124
- lastCompleted: { workflowId, completedAt: failedAt, outcome: { type: "fail" } },
125
- outcome: { workflowId, completedAt: failedAt, status: "failed", metadata },
161
+ lastCompleted: {
162
+ workflowId,
163
+ completedAt: failedAt,
164
+ outcome: { type: "fail" }
165
+ },
166
+ outcome: {
167
+ workflowId,
168
+ completedAt: failedAt,
169
+ status: "failed",
170
+ metadata
171
+ },
126
172
  failed: { workflowId, error: metadata.summary, metadata, failedAt }
127
173
  });
128
174
  }
129
175
  async interruptCurrent(args, interruption) {
130
- if (!this.state.current) throw new Error("Cannot interrupt run without current step");
176
+ if (!this.state.current)
177
+ throw new Error("Cannot interrupt run without current step");
131
178
  await this.update({
132
179
  status: "interrupted",
133
- current: { ...this.state.current, args, interruption: { status: "pending", ...interruption } },
180
+ current: {
181
+ ...this.state.current,
182
+ args,
183
+ interruption: { status: "pending", ...interruption }
184
+ },
134
185
  outcome: null,
135
186
  failed: null
136
187
  });
137
188
  }
138
189
  async prepareForResumeAfterRollback() {
139
- if (!this.state.current) throw new Error("Cannot resume a checkpoint without a current step");
190
+ if (!this.state.current)
191
+ throw new Error("Cannot resume a checkpoint without a current step");
140
192
  if (this.state.status === "interrupted") return;
141
- if (this.state.status !== "running") throw new Error(`Cannot resume checkpoint with ${this.state.status} status`);
193
+ if (this.state.status !== "running")
194
+ throw new Error(
195
+ `Cannot resume checkpoint with ${this.state.status} status`
196
+ );
142
197
  await this.update({ status: "pendingResume", outcome: null, failed: null });
143
198
  }
144
199
  async stopCurrent() {
145
- if (!this.state.current) throw new Error("Cannot stop run without current step");
200
+ if (!this.state.current)
201
+ throw new Error("Cannot stop run without current step");
146
202
  await this.update({ status: "stopped", outcome: null, failed: null });
147
203
  }
148
204
  async replaceCurrentArgs(args) {
149
- if (!this.state.current) throw new Error("Cannot resume run without current step");
205
+ if (!this.state.current)
206
+ throw new Error("Cannot resume run without current step");
150
207
  await this.update({
151
208
  status: "running",
152
- current: { ...this.state.current, args, interruption: { ...this.state.current.interruption, status: "satisfied" } },
209
+ current: {
210
+ ...this.state.current,
211
+ args,
212
+ interruption: {
213
+ ...this.state.current.interruption,
214
+ status: "satisfied"
215
+ }
216
+ },
153
217
  outcome: null,
154
218
  failed: null
155
219
  });
156
220
  }
157
221
  async failCurrent(errorOrMetadata) {
158
- if (!this.state.current) throw new Error("Cannot fail run without current step");
222
+ if (!this.state.current)
223
+ throw new Error("Cannot fail run without current step");
159
224
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
160
225
  const metadata = typeof errorOrMetadata === "string" ? { summary: errorOrMetadata } : errorOrMetadata;
161
226
  await this.update({
162
227
  status: "failed",
163
- outcome: { workflowId: this.state.current.workflowId, completedAt: failedAt, status: "failed", metadata },
164
- failed: { workflowId: this.state.current.workflowId, error: metadata.summary, metadata, failedAt }
228
+ outcome: {
229
+ workflowId: this.state.current.workflowId,
230
+ completedAt: failedAt,
231
+ status: "failed",
232
+ metadata
233
+ },
234
+ failed: {
235
+ workflowId: this.state.current.workflowId,
236
+ error: metadata.summary,
237
+ metadata,
238
+ failedAt
239
+ }
165
240
  });
166
241
  }
167
242
  async update(patch) {
168
243
  await this.files.withExclusiveLock(this.path, async (path) => {
169
- const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME) }), "execute");
170
- const next = { ...current, ...patch, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
171
- if (next.current?.args !== void 0) Value.Assert(jsonValueSchema, next.current.args);
244
+ const current = parseNornRunState(
245
+ await readRunStateFile({
246
+ path,
247
+ legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME)
248
+ }),
249
+ "execute"
250
+ );
251
+ const next = {
252
+ ...current,
253
+ ...patch,
254
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
255
+ };
256
+ if (next.current?.args !== void 0)
257
+ Value.Assert(jsonValueSchema, next.current.args);
172
258
  await writeJsonAtomically(path, next);
173
259
  this.state = next;
174
260
  });
175
261
  }
176
262
  async write() {
177
- if (this.state.current?.args !== void 0) Value.Assert(jsonValueSchema, this.state.current.args);
263
+ if (this.state.current?.args !== void 0)
264
+ Value.Assert(jsonValueSchema, this.state.current.args);
178
265
  await mkdir2(dirname2(this.path), { recursive: true });
179
- await this.files.withExclusiveLock(this.path, (path) => writeJsonAtomically(path, this.state));
266
+ await this.files.withExclusiveLock(
267
+ this.path,
268
+ (path) => writeJsonAtomically(path, this.state)
269
+ );
180
270
  }
181
271
  };
182
272
  async function listRuns(sessionCwd) {
@@ -188,7 +278,9 @@ async function listRuns(sessionCwd) {
188
278
  if (isNodeError(error) && error.code === "ENOENT") return [];
189
279
  throw error;
190
280
  }
191
- const runs = await Promise.all(entries.filter((entry) => entry.isDirectory()).map((entry) => readNornRunInfo(resolve(runsRoot, entry.name))));
281
+ const runs = await Promise.all(
282
+ entries.filter((entry) => entry.isDirectory()).map((entry) => readNornRunInfo(resolve(runsRoot, entry.name)))
283
+ );
192
284
  return runs.filter((run) => run !== void 0).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
193
285
  }
194
286
  async function getRunInfo(runRoot) {
@@ -197,15 +289,22 @@ async function getRunInfo(runRoot) {
197
289
  (request) => ({ status: "fulfilled", request }),
198
290
  (error) => ({ status: "rejected", error })
199
291
  );
200
- const state = parseNornRunState(await readRunStateJson(runCurrentRoot(runRoot)), "inspect");
201
- if (state.version === RUN_STATE_VERSION && pendingResume.status === "rejected") throw pendingResume.error;
292
+ const state = parseNornRunState(
293
+ await readRunStateJson(runCurrentRoot(runRoot)),
294
+ "inspect"
295
+ );
296
+ if (state.version === RUN_STATE_VERSION && pendingResume.status === "rejected")
297
+ throw pendingResume.error;
202
298
  const resumeRequest = state.version === RUN_STATE_VERSION && pendingResume.status === "fulfilled" ? pendingResume.request : void 0;
203
299
  return {
204
300
  version: state.version,
205
301
  id: state.id,
206
302
  name: state.name,
207
303
  path: runRoot,
208
- paths: { project: state.projectRoot ?? resolve(runRoot, "../../.."), workspace: state.workspace },
304
+ paths: {
305
+ project: state.projectRoot ?? resolve(runRoot, "../../.."),
306
+ workspace: state.workspace
307
+ },
209
308
  entrypointWorkflowId: state.entrypointWorkflowId,
210
309
  currentWorkflowId: state.current?.workflowId,
211
310
  status: resumeRequest ? "running" : state.status,
@@ -226,17 +325,21 @@ async function resolveRunRoot(sessionCwd, run) {
226
325
  try {
227
326
  if (await readNornRunInfo(candidate)) return candidate;
228
327
  } catch (error) {
229
- if (!isNodeError(error) && (!(error instanceof Error) || !error.message.includes("run state"))) throw error;
328
+ if (!isNodeError(error) && (!(error instanceof Error) || !error.message.includes("run state")))
329
+ throw error;
230
330
  }
231
331
  }
232
332
  const runs = await listRuns(sessionCwd);
233
- const match = runs.find((entry) => entry.id === run || entry.name === run || entry.path === run || entry.path.endsWith(`/${run}`));
333
+ const match = runs.find(
334
+ (entry) => entry.id === run || entry.name === run || entry.path === run || entry.path.endsWith(`/${run}`)
335
+ );
234
336
  if (!match) throw new Error(`Unknown run: ${run}`);
235
337
  return match.path;
236
338
  }
237
339
  function runRootCandidates(sessionCwd, run) {
238
340
  const candidates = [isAbsolute(run) ? run : resolve(sessionCwd, run)];
239
- if (!isAbsolute(run)) candidates.push(resolve(sessionCwd, ".norn", "runs", run));
341
+ if (!isAbsolute(run))
342
+ candidates.push(resolve(sessionCwd, ".norn", "runs", run));
240
343
  return Array.from(new Set(candidates));
241
344
  }
242
345
  async function getPendingResumeHealth(runRoot, requestedAt) {
@@ -262,7 +365,10 @@ async function getLaunchedRunInfo(runRoot) {
262
365
  id: launchRequest.id,
263
366
  name: launchRequest.name,
264
367
  path: runRoot,
265
- paths: { project: resolve(runRoot, "../../.."), workspace: join(runCurrentRoot(resolve(runRoot)), "workspace") },
368
+ paths: {
369
+ project: resolve(runRoot, "../../.."),
370
+ workspace: join(runCurrentRoot(resolve(runRoot)), "workspace")
371
+ },
266
372
  entrypointWorkflowId: launchRequest.workflowId,
267
373
  currentWorkflowId: launchRequest.workflowId,
268
374
  status: "running",
@@ -272,7 +378,10 @@ async function getLaunchedRunInfo(runRoot) {
272
378
  };
273
379
  }
274
380
  async function readRunStateJson(currentRoot) {
275
- return readRunStateFile({ path: join(currentRoot, RUN_STATE_FILE_NAME), legacyPath: join(currentRoot, LEGACY_RUN_STATE_FILE_NAME) });
381
+ return readRunStateFile({
382
+ path: join(currentRoot, RUN_STATE_FILE_NAME),
383
+ legacyPath: join(currentRoot, LEGACY_RUN_STATE_FILE_NAME)
384
+ });
276
385
  }
277
386
  async function readRunStateFile(input) {
278
387
  try {
@@ -286,35 +395,50 @@ function isRecord(value) {
286
395
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
287
396
  }
288
397
  function assertRunVersion(version) {
289
- if (version !== RUN_STATE_VERSION) throw new Error(`Incompatible saved run state version ${String(version)}; this alpha requires version ${RUN_STATE_VERSION}. Use the original Norn version to resume this run, or start a new run. Saved files have not been migrated.`);
398
+ if (version !== RUN_STATE_VERSION)
399
+ throw new Error(
400
+ `Incompatible saved run state version ${String(version)}; this alpha requires version ${RUN_STATE_VERSION}. Use the original Norn version to resume this run, or start a new run. Saved files have not been migrated.`
401
+ );
290
402
  }
291
403
  function parseNornRunState(value, mode) {
292
404
  if (!value || typeof value !== "object") throw new Error("Invalid run state");
293
405
  const state = value;
294
- if (mode === "execute" || state.version !== 1) assertRunVersion(state.version);
295
- if (typeof state.id !== "string" || state.id.length === 0) throw new Error("Invalid run state id");
406
+ if (mode === "execute" || state.version !== 1)
407
+ assertRunVersion(state.version);
408
+ if (typeof state.id !== "string" || state.id.length === 0)
409
+ throw new Error("Invalid run state id");
296
410
  const normalizedState = state;
297
- if (typeof normalizedState.name !== "string" || normalizedState.name.length === 0) normalizedState.name = state.id;
298
- if (typeof normalizedState.entrypointWorkflowId !== "string" && typeof normalizedState.rootWorkflowId === "string") normalizedState.entrypointWorkflowId = normalizedState.rootWorkflowId;
299
- if (typeof state.entrypointWorkflowId !== "string" || state.entrypointWorkflowId.length === 0) throw new Error("Invalid run state entrypoint workflow id");
300
- if (typeof state.workspace !== "string" || state.workspace.length === 0) throw new Error("Invalid run state workspace");
301
- if (state.status !== "running" && state.status !== "interrupted" && state.status !== "stopped" && state.status !== "pendingResume" && state.status !== "completed" && state.status !== "failed") throw new Error("Invalid run state status");
411
+ if (typeof normalizedState.name !== "string" || normalizedState.name.length === 0)
412
+ normalizedState.name = state.id;
413
+ if (typeof normalizedState.entrypointWorkflowId !== "string" && typeof normalizedState.rootWorkflowId === "string")
414
+ normalizedState.entrypointWorkflowId = normalizedState.rootWorkflowId;
415
+ if (typeof state.entrypointWorkflowId !== "string" || state.entrypointWorkflowId.length === 0)
416
+ throw new Error("Invalid run state entrypoint workflow id");
417
+ if (typeof state.workspace !== "string" || state.workspace.length === 0)
418
+ throw new Error("Invalid run state workspace");
419
+ if (state.status !== "running" && state.status !== "interrupted" && state.status !== "stopped" && state.status !== "pendingResume" && state.status !== "completed" && state.status !== "failed")
420
+ throw new Error("Invalid run state status");
302
421
  if (state.status === "interrupted") assertInterruptedNornRunState(state);
303
- if (state.status === "stopped" || state.status === "pendingResume") assertResumableNornRunState(state);
304
- if (typeof state.startedAt !== "string" || typeof state.updatedAt !== "string") throw new Error("Invalid run state timestamps");
422
+ if (state.status === "stopped" || state.status === "pendingResume")
423
+ assertResumableNornRunState(state);
424
+ if (typeof state.startedAt !== "string" || typeof state.updatedAt !== "string")
425
+ throw new Error("Invalid run state timestamps");
305
426
  return state;
306
427
  }
307
428
  function assertInterruptedNornRunState(state) {
308
429
  if (!state.current) throw new Error("Invalid interrupted run current step");
309
430
  const interruption = state.current.interruption;
310
- if (!interruption || interruption.status !== "pending") throw new Error("Invalid run interruption");
311
- if (typeof interruption.description !== "string" || interruption.description.length === 0) throw new Error("Invalid run interruption description");
431
+ if (!interruption || interruption.status !== "pending")
432
+ throw new Error("Invalid run interruption");
433
+ if (typeof interruption.description !== "string" || interruption.description.length === 0)
434
+ throw new Error("Invalid run interruption description");
312
435
  }
313
436
  function assertResumableNornRunState(state) {
314
437
  if (!state.current) throw new Error("Invalid resumable run current step");
315
438
  }
316
439
  function runInterruption(state) {
317
- if (state.status !== "interrupted" || !state.current?.interruption) return void 0;
440
+ if (state.status !== "interrupted" || !state.current?.interruption)
441
+ return void 0;
318
442
  return {
319
443
  workflowId: state.current.workflowId,
320
444
  args: state.version === 1 ? state.current.params : state.current.args,