@vimhead.dev/norn-cli 0.1.0-tip.35359392805.1 → 0.1.0-tip.35390859630.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 (111) hide show
  1. package/assets/README.md +17 -26
  2. package/assets/docs/README.md +3 -3
  3. package/assets/docs/agents.md +12 -1
  4. package/assets/docs/cli.md +5 -5
  5. package/assets/docs/composition.md +21 -21
  6. package/assets/docs/persistence.md +7 -10
  7. package/assets/docs/projects.md +30 -18
  8. package/assets/docs/providers.md +1 -1
  9. package/assets/docs/recovery.md +13 -9
  10. package/assets/docs/resources.md +4 -19
  11. package/assets/docs/schemas.md +1 -1
  12. package/assets/docs/workflows.md +95 -7
  13. package/assets/examples/agent-then-analysis/README.md +4 -4
  14. package/assets/examples/agent-then-analysis/input.json +1 -1
  15. package/assets/examples/agent-then-analysis/norn.project.json +1 -1
  16. package/assets/examples/agent-then-analysis/plugin.ts +55 -68
  17. package/assets/examples/caller-selected-continuation/README.md +5 -5
  18. package/assets/examples/caller-selected-continuation/caller.ts +35 -38
  19. package/assets/examples/caller-selected-continuation/input.json +2 -2
  20. package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
  21. package/assets/examples/caller-selected-continuation/producer.ts +15 -23
  22. package/assets/examples/coordinating-multiple-agents/README.md +2 -2
  23. package/assets/examples/coordinating-multiple-agents/input.json +1 -1
  24. package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
  25. package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
  26. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
  27. package/assets/examples/getting-started/norn.project.json +1 -1
  28. package/assets/examples/getting-started/plugin.ts +16 -26
  29. package/assets/examples/minimal-workflow/README.md +8 -8
  30. package/assets/examples/minimal-workflow/norn.project.json +1 -1
  31. package/assets/examples/minimal-workflow/plugin.ts +11 -25
  32. package/assets/examples/shared-state/README.md +10 -2
  33. package/assets/examples/shared-state/input.json +1 -1
  34. package/assets/examples/shared-state/norn.project.json +1 -1
  35. package/assets/examples/shared-state/plugin.ts +36 -41
  36. package/assets/examples/shared-state/shared-state.ts +75 -0
  37. package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
  38. package/assets/examples/worktree-development-loop/README.md +6 -7
  39. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  40. package/assets/examples/worktree-development-loop/plugin.ts +6 -26
  41. package/assets/examples/worktree-development-loop/scope.ts +4 -0
  42. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
  43. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
  44. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
  45. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
  46. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
  47. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
  48. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
  49. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
  50. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
  51. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
  52. package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
  53. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
  54. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
  55. package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
  56. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
  57. package/assets/package.json +1 -1
  58. package/assets/packages/cli/src/cli.ts +35 -34
  59. package/assets/packages/cli/src/client.ts +9 -16
  60. package/assets/packages/cli/src/documentation-intro.ts +1 -1
  61. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  62. package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
  63. package/assets/packages/cli/src/internal/engine.ts +38 -62
  64. package/assets/packages/cli/src/internal/errors.ts +4 -4
  65. package/assets/packages/cli/src/internal/launch-request.ts +6 -6
  66. package/assets/packages/cli/src/internal/run-state.ts +31 -21
  67. package/assets/packages/cli/src/internal/run.ts +2 -5
  68. package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
  69. package/assets/packages/cli/src/workflow-loader.ts +214 -0
  70. package/assets/packages/core/src/workflow-transition.ts +2 -2
  71. package/assets/packages/sdk/src/api.ts +133 -289
  72. package/assets/packages/sdk/src/index.ts +1 -1
  73. package/assets/packages/sdk/src/schema.ts +3 -3
  74. package/assets/tests/workflow-ref.test.ts +50 -51
  75. package/dist/cli.js +33 -32
  76. package/dist/client.d.ts +3 -4
  77. package/dist/client.js +3 -8
  78. package/dist/documentation-intro.js +1 -1
  79. package/dist/generated-build-info.d.ts +2 -2
  80. package/dist/generated-build-info.js +2 -2
  81. package/dist/internal/agent-response-tool.js +3 -3
  82. package/dist/internal/engine.d.ts +4 -8
  83. package/dist/internal/engine.js +36 -49
  84. package/dist/internal/errors.d.ts +3 -3
  85. package/dist/internal/errors.js +1 -1
  86. package/dist/internal/launch-request.d.ts +4 -4
  87. package/dist/internal/launch-request.js +2 -2
  88. package/dist/internal/run-state.d.ts +9 -8
  89. package/dist/internal/run-state.js +29 -20
  90. package/dist/internal/run.d.ts +1 -3
  91. package/dist/internal/run.js +3 -5
  92. package/dist/internal/workflow-registry.d.ts +25 -17
  93. package/dist/internal/workflow-registry.js +107 -72
  94. package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
  95. package/dist/workflow-loader.js +235 -0
  96. package/package.json +2 -2
  97. package/assets/examples/worktree-development-loop/manifest.ts +0 -26
  98. package/assets/examples/worktree-development-loop/state.ts +0 -23
  99. package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
  100. package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
  101. package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
  102. package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
  103. package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
  104. package/assets/packages/cli/src/internal/run-resources.ts +0 -23
  105. package/assets/packages/cli/src/internal/state-store.ts +0 -83
  106. package/assets/packages/cli/src/plugin-loader.ts +0 -400
  107. package/dist/internal/run-resources.d.ts +0 -6
  108. package/dist/internal/run-resources.js +0 -26
  109. package/dist/internal/state-store.d.ts +0 -23
  110. package/dist/internal/state-store.js +0 -103
  111. package/dist/plugin-loader.js +0 -341
@@ -11,12 +11,11 @@ import { clearRunResumeRequest, matchesRunResumeRequest, readOptionalRunResumeRe
11
11
  import { NornRunLogs } from "./logs.js";
12
12
  import { NornRunLease } from "./run-lease.js";
13
13
  import { NornRunLogger } from "./run-log.js";
14
- import { initializeRunResources } from "./run-resources.js";
15
- import { getRunInfo, mergeInterruptedWorkflowParams, NornRunStateStore, resolveRunRoot } from "./run-state.js";
14
+ import { NornRunResources } from "../resources.js";
15
+ import { assertRunVersion, getRunInfo, mergeInterruptedWorkflowArgs, NornRunStateStore, resolveRunRoot } from "./run-state.js";
16
16
  import { NornRunStore, runCurrentRoot } from "./run-store.js";
17
17
  import { NornRunContext } from "./run.js";
18
- import { NornMemoryWorkflowState } from "./state-store.js";
19
- import { decodePluginConfiguration, NornWorkflowRegistry } from "./workflow-registry.js";
18
+ import { NornWorkflowRegistry } from "./workflow-registry.js";
20
19
  var RUNS_DIR_NAME = ".norn";
21
20
  var MANIFEST_FILE_NAME = "manifest.json";
22
21
  var NornEngine = class {
@@ -26,22 +25,18 @@ var NornEngine = class {
26
25
  }
27
26
  input;
28
27
  registry = new NornWorkflowRegistry();
29
- registrarState = new NornMemoryWorkflowState();
30
- disposersByPlugin = /* @__PURE__ */ new Map();
31
28
  responseCollector;
32
29
  activeRuns = /* @__PURE__ */ new Map();
33
- registerPlugin(plugin) {
34
- this.disposePlugin(plugin.manifest.id);
35
- const implementation = this.resolvePluginImplementation(plugin.implementation);
36
- const configuration = decodePluginConfiguration({ pluginId: plugin.manifest.id, schema: plugin.manifest.config, value: this.input.config?.[plugin.manifest.id] });
37
- const disposers = Object.entries(plugin.manifest.workflows).map(([key, workflow]) => {
38
- const workflowImplementation = implementation.workflows[key];
39
- if (!workflowImplementation) throw new Error(`Missing implementation for workflow ${plugin.manifest.id}.${key}`);
40
- return this.registry.register(workflow, workflowImplementation, { plugin: { id: plugin.manifest.id }, configuration });
41
- });
42
- this.disposersByPlugin.set(plugin.manifest.id, disposers);
30
+ registerWorkflows(workflows) {
31
+ const disposers = [];
32
+ try {
33
+ for (const workflow of workflows) disposers.push(this.registry.register({ workflow, config: this.input.config ?? {}, source: void 0 }));
34
+ } catch (error) {
35
+ for (const dispose of disposers.reverse()) dispose();
36
+ throw error;
37
+ }
43
38
  return () => {
44
- if (this.disposersByPlugin.get(plugin.manifest.id) === disposers) this.disposePlugin(plugin.manifest.id);
39
+ for (const dispose of disposers) dispose();
45
40
  };
46
41
  }
47
42
  listWorkflows() {
@@ -50,11 +45,8 @@ var NornEngine = class {
50
45
  visibleWorkflowEntries() {
51
46
  return this.registry.launchableEntries();
52
47
  }
53
- resolvePluginImplementation(implementationInput) {
54
- return typeof implementationInput === "function" ? implementationInput({ cwd: this.input.cwd, state: this.registrarState }) : implementationInput;
55
- }
56
- async runWorkflow(workflow, params, options) {
57
- const session = await this.createRunSession(workflow, params, options);
48
+ async runWorkflow(workflow, args, options) {
49
+ const session = await this.createRunSession(workflow, args, options);
58
50
  const current = session.state.currentState().current;
59
51
  if (!current) throw new Error("New run is missing its initial workflow step");
60
52
  return this.runScheduler(session, toWorkflowStep(workflow, current));
@@ -65,6 +57,7 @@ var NornEngine = class {
65
57
  }
66
58
  async rollbackRun(path, checkpointId) {
67
59
  const runRoot = await resolveRunRoot(this.input.cwd, path);
60
+ assertRunVersion((await getRunInfo(runRoot)).version);
68
61
  const lease = await NornRunLease.acquire(runRoot);
69
62
  try {
70
63
  const pendingRequest = await readOptionalRunResumeRequest(runRoot);
@@ -80,19 +73,19 @@ var NornEngine = class {
80
73
  await lease.release();
81
74
  }
82
75
  }
83
- async resumeWorkflow(path, params) {
84
- return this.resumeWorkflowExecution({ path, params, expectedRequest: void 0 });
76
+ async resumeWorkflow(path, args) {
77
+ return this.resumeWorkflowExecution({ path, args, expectedRequest: void 0 });
85
78
  }
86
79
  async resumeRequestedWorkflow(input) {
87
- return this.resumeWorkflowExecution({ path: input.runRoot, params: input.request.params, expectedRequest: input.request });
80
+ return this.resumeWorkflowExecution({ path: input.runRoot, args: input.request.args, expectedRequest: input.request });
88
81
  }
89
82
  async resumeWorkflowExecution(input) {
90
- const { params, expectedRequest } = input;
83
+ const { args, expectedRequest } = input;
91
84
  const runRoot = await resolveRunRoot(this.input.cwd, input.path);
92
85
  const initialStateStore = await NornRunStateStore.load(runRoot);
93
86
  const initialState = initialStateStore.currentState();
94
- if (initialState.status === "interrupted" && params === void 0) throw new Error(`Interrupted workflow resume requires params: ${runRoot}`);
95
- if (initialState.status === "pendingResume" && params !== void 0) throw new Error(`Pending-resume workflows do not accept params: ${runRoot}`);
87
+ if (initialState.status === "interrupted" && args === void 0) throw new Error(`Interrupted workflow resume requires args: ${runRoot}`);
88
+ if (initialState.status === "pendingResume" && args !== void 0) throw new Error(`Pending-resume workflows do not accept args: ${runRoot}`);
96
89
  if (initialState.status !== "interrupted" && initialState.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
97
90
  const lease = await NornRunLease.acquire(runRoot);
98
91
  let isLeaseOwnedByScheduler = false;
@@ -107,9 +100,9 @@ var NornEngine = class {
107
100
  const workflow = this.registry.workflowById(current.workflowId);
108
101
  if (!workflow) throw new Error(`Unknown workflow for resumed run: ${current.workflowId}`);
109
102
  if (state.status === "interrupted") {
110
- const rawParams = mergeInterruptedWorkflowParams(current.params, params, current.interruption?.fields);
111
- Value.Decode(workflow.params, rawParams);
112
- await session.state.replaceCurrentParams(rawParams);
103
+ const rawArgs = mergeInterruptedWorkflowArgs(current.args, args, current.interruption?.fields);
104
+ Value.Decode(workflow.args, rawArgs);
105
+ await session.state.replaceCurrentArgs(rawArgs);
113
106
  await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.run.cwd });
114
107
  const resumedCurrent = session.state.currentState().current;
115
108
  if (!resumedCurrent) throw new Error(`Run is not resumable: ${runRoot}`);
@@ -137,12 +130,12 @@ var NornEngine = class {
137
130
  throwIfRunAborted(session.activeRun);
138
131
  const stepRuntime = session.run.forWorkflow(currentStep.workflow);
139
132
  if (shouldPauseForGate(currentStep)) {
140
- const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, currentStep.params, session.state.currentState().configOverride);
133
+ const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, currentStep.args, session.state.currentState().configOverride);
141
134
  const interruption = { description, fields: currentStep.workflow.gate?.fields };
142
- await session.state.interruptCurrent(currentStep.params, interruption);
135
+ await session.state.interruptCurrent(currentStep.args, interruption);
143
136
  await recordRunEvent(session, { type: "run.interrupted", workflowId: currentStep.workflow.id });
144
137
  await commitRunBoundary(session, `run interrupted: ${currentStep.workflow.id}`);
145
- return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, currentStep.params, interruption);
138
+ return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, currentStep.args, interruption);
146
139
  }
147
140
  await session.state.startStep(toRunStateStep(currentStep));
148
141
  const stepResult = await this.executeWorkflowStep(session, currentStep, stepRuntime);
@@ -184,7 +177,7 @@ var NornEngine = class {
184
177
  try {
185
178
  await assertWorkspaceBoundary(session, run.workspace);
186
179
  await recordRunEvent(session, { type: "workflow.started", workflowId: step.workflow.id });
187
- const result = await this.registry.execute(step.workflow, run, step.params, session.state.currentState().configOverride);
180
+ const result = await this.registry.execute(step.workflow, run, step.args, session.state.currentState().configOverride);
188
181
  await assertWorkspaceBoundary(session, run.workspace);
189
182
  const durationMs = Date.now() - startedAtMs;
190
183
  if (result.type === "complete") await recordRunEvent(session, { type: "workflow.completed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
@@ -206,11 +199,11 @@ var NornEngine = class {
206
199
  if (!workflow) throw new Error(`Unknown next workflow: ${next.workflowId}`);
207
200
  return {
208
201
  workflow,
209
- params: next.params,
202
+ args: next.args,
210
203
  interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : void 0
211
204
  };
212
205
  }
213
- async createRunSession(workflow, params, options) {
206
+ async createRunSession(workflow, args, options) {
214
207
  const id = options?.id ?? randomUUID();
215
208
  const name = options?.name ?? id;
216
209
  const runRoot = defaultRunRoot(this.input.cwd, id);
@@ -242,7 +235,7 @@ var NornEngine = class {
242
235
  configOverride: options?.configOverride,
243
236
  current: {
244
237
  workflowId: workflow.id,
245
- params,
238
+ args,
246
239
  cwd,
247
240
  env: {},
248
241
  interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : void 0
@@ -284,7 +277,7 @@ var NornEngine = class {
284
277
  await mkdir(input.workspace, { recursive: true });
285
278
  await mkdir(artifactsRoot, { recursive: true });
286
279
  await mkdir(logsRoot, { recursive: true });
287
- const { resources, state } = await initializeRunResources(dirname(input.currentRoot));
280
+ const resources = await NornRunResources.initialize(dirname(input.currentRoot));
288
281
  return new NornRunContext({
289
282
  id: input.id,
290
283
  runRoot: input.currentRoot,
@@ -296,7 +289,6 @@ var NornEngine = class {
296
289
  agentDir: this.input.agentDir,
297
290
  responseCollector: this.responseCollector,
298
291
  resources,
299
- state,
300
292
  logger: input.logger,
301
293
  artifacts: new NornArtifacts(artifactsRoot, resources.files),
302
294
  logs: new NornRunLogs(logsRoot, resources.files)
@@ -332,11 +324,6 @@ var NornEngine = class {
332
324
  activeRun.dispose();
333
325
  activeRun.finish();
334
326
  }
335
- disposePlugin(pluginId) {
336
- const disposers = this.disposersByPlugin.get(pluginId) ?? [];
337
- for (const dispose of [...disposers].reverse()) dispose();
338
- this.disposersByPlugin.delete(pluginId);
339
- }
340
327
  };
341
328
  function defaultRunRoot(cwd, id) {
342
329
  return join(cwd, RUNS_DIR_NAME, "runs", id);
@@ -347,14 +334,14 @@ function workflowDefaultCwd(projectRoot, workspace, workflow) {
347
334
  function toWorkflowStep(workflow, step) {
348
335
  return {
349
336
  workflow,
350
- params: step.params,
337
+ args: step.args,
351
338
  interruption: step.interruption
352
339
  };
353
340
  }
354
341
  function toRunStateStep(step) {
355
342
  return {
356
343
  workflowId: step.workflow.id,
357
- params: step.params,
344
+ args: step.args,
358
345
  cwd: "",
359
346
  env: {},
360
347
  interruption: step.interruption
@@ -372,7 +359,7 @@ function throwIfRunAborted(activeRun) {
372
359
  function isRunStopped(session, error) {
373
360
  return error instanceof NornRunStoppedError || session.activeRun.controller.signal.reason instanceof NornRunStoppedError;
374
361
  }
375
- function interruptedLaunchResult(run, workflow, params, interruption) {
362
+ function interruptedLaunchResult(run, workflow, args, interruption) {
376
363
  if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
377
364
  if (!interruption.description) throw new Error(`Interrupted workflow is missing description: ${workflow.id}`);
378
365
  return {
@@ -382,7 +369,7 @@ function interruptedLaunchResult(run, workflow, params, interruption) {
382
369
  workspace: run.workspace,
383
370
  cwd: run.cwd,
384
371
  workflowId: workflow.id,
385
- interruption: { workflowId: workflow.id, params, description: interruption.description, fields: interruption.fields }
372
+ interruption: { workflowId: workflow.id, args, description: interruption.description, fields: interruption.fields }
386
373
  };
387
374
  }
388
375
  async function recordRunEvent(session, event) {
@@ -1,11 +1,11 @@
1
- import type { NornPluginDiagnostic } from "@vimhead.dev/norn";
1
+ import type { NornWorkflowDiagnostic } from "@vimhead.dev/norn";
2
2
  import { AssertError } from "typebox/value";
3
3
  export declare class NornProjectLoadError extends Error {
4
4
  readonly code = "NORN_PROJECT_INVALID";
5
5
  readonly isComplete = false;
6
- readonly diagnostics: readonly NornPluginDiagnostic[];
6
+ readonly diagnostics: readonly NornWorkflowDiagnostic[];
7
7
  constructor(input: {
8
- readonly diagnostics: readonly NornPluginDiagnostic[];
8
+ readonly diagnostics: readonly NornWorkflowDiagnostic[];
9
9
  });
10
10
  }
11
11
  export declare class NornRunStoppedError extends Error {
@@ -6,7 +6,7 @@ var NornProjectLoadError = class extends Error {
6
6
  diagnostics;
7
7
  constructor(input) {
8
8
  const details = input.diagnostics.map(
9
- (diagnostic) => `${diagnostic.stage}: ${diagnostic.pluginPath} (declared in ${diagnostic.configPath}${diagnostic.workflowId ? `; workflow ${diagnostic.workflowId}` : ""}): ${diagnostic.message}`
9
+ (diagnostic) => `${diagnostic.stage}: ${diagnostic.modulePath} (declared in ${diagnostic.configPath}${diagnostic.workflowId ? `; workflow ${diagnostic.workflowId}` : ""}): ${diagnostic.message}`
10
10
  );
11
11
  super(`Norn project is incomplete (${input.diagnostics.length} diagnostics); execution is blocked.
12
12
  ${details.join("\n")}`);
@@ -3,21 +3,21 @@ export declare const LAUNCH_REQUEST_FILE_NAME = "launch-request.json";
3
3
  export declare const RESUME_REQUEST_FILE_NAME = "resume-request.json";
4
4
  export declare const RESUME_START_GRACE_MS = 60000;
5
5
  export type NornRunLaunchRequest = {
6
- readonly version: 1;
6
+ readonly version: 2;
7
7
  readonly type: "run";
8
8
  readonly id: string;
9
9
  readonly name: string;
10
10
  readonly workflowId: string;
11
- readonly params: unknown;
11
+ readonly args: unknown;
12
12
  readonly configOverride?: unknown;
13
13
  readonly createdAt: string;
14
14
  };
15
15
  export type NornRunResumeRequest = {
16
- readonly version: 1;
16
+ readonly version: 2;
17
17
  readonly type: "resume";
18
18
  readonly id: string;
19
19
  readonly requestId?: string;
20
- readonly params?: unknown;
20
+ readonly args?: unknown;
21
21
  readonly createdAt: string;
22
22
  };
23
23
  export declare function writeRunLaunchRequest(runRoot: string, request: NornRunLaunchRequest): Promise<void>;
@@ -80,7 +80,7 @@ async function clearRunResumeRequest(input) {
80
80
  function parseRunLaunchRequest(value) {
81
81
  if (!value || typeof value !== "object") throw new Error("Invalid workflow launch request");
82
82
  const request = value;
83
- if (request.version !== 1 || request.type !== "run") throw new Error("Unsupported workflow launch request");
83
+ if (request.version !== 2 || request.type !== "run") throw new Error("Unsupported workflow launch request");
84
84
  if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow launch request id");
85
85
  if (typeof request.name !== "string" || request.name.length === 0) throw new Error("Invalid workflow launch request name");
86
86
  if (typeof request.workflowId !== "string" || request.workflowId.length === 0) throw new Error("Invalid workflow launch request workflow id");
@@ -90,7 +90,7 @@ function parseRunLaunchRequest(value) {
90
90
  function parseRunResumeRequest(value) {
91
91
  if (!value || typeof value !== "object") throw new Error("Invalid workflow resume request");
92
92
  const request = value;
93
- if (request.version !== 1 || request.type !== "resume") throw new Error("Unsupported workflow resume request");
93
+ if (request.version !== 2 || request.type !== "resume") throw new Error("Unsupported workflow resume request");
94
94
  if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow resume request id");
95
95
  if (request.requestId !== void 0 && (typeof request.requestId !== "string" || request.requestId.length === 0)) throw new Error("Invalid workflow resume request token");
96
96
  if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt))) throw new Error("Invalid workflow resume request timestamp");
@@ -1,10 +1,10 @@
1
1
  import type { NornRunStatus, NornRunOutcomeMetadata, NornRunInfo } from "@vimhead.dev/norn";
2
2
  export declare const RUN_STATE_FILE_NAME = "run-state.json";
3
- export declare function mergeInterruptedWorkflowParams(currentParams: unknown, params: unknown, fields: readonly string[] | undefined): unknown;
4
- declare const RUN_STATE_VERSION = 1;
3
+ export declare function mergeInterruptedWorkflowArgs(currentArgs: unknown, args: unknown, fields: readonly string[] | undefined): unknown;
4
+ declare const RUN_STATE_VERSION = 2;
5
5
  type NornRunWorkflowStep = {
6
6
  readonly workflowId: string;
7
- readonly params: unknown;
7
+ readonly args: unknown;
8
8
  readonly cwd: string;
9
9
  readonly env: Record<string, string>;
10
10
  readonly interruption?: RuntimeInterruption;
@@ -39,7 +39,7 @@ type NornRunWorkflowOutcome = {
39
39
  readonly metadata?: NornRunOutcomeMetadata;
40
40
  };
41
41
  export type NornRunState = {
42
- readonly version: typeof RUN_STATE_VERSION;
42
+ readonly version: 1 | typeof RUN_STATE_VERSION;
43
43
  readonly id: string;
44
44
  readonly name: string;
45
45
  readonly entrypointWorkflowId: string;
@@ -76,20 +76,21 @@ export declare class NornRunStateStore {
76
76
  failRun(workflowId: string, metadata: NornRunOutcomeMetadata & {
77
77
  readonly summary: string;
78
78
  }): Promise<void>;
79
- interruptCurrent(params: unknown, interruption: {
79
+ interruptCurrent(args: unknown, interruption: {
80
80
  readonly description: string;
81
81
  readonly fields?: readonly string[];
82
82
  }): Promise<void>;
83
83
  prepareForResumeAfterRollback(): Promise<void>;
84
84
  stopCurrent(): Promise<void>;
85
- replaceCurrentParams(params: unknown): Promise<void>;
86
- failCurrent(errorOrMetadata: string | NornRunOutcomeMetadata & {
85
+ replaceCurrentArgs(args: unknown): Promise<void>;
86
+ failCurrent(errorOrMetadata: string | (NornRunOutcomeMetadata & {
87
87
  readonly summary: string;
88
- }): Promise<void>;
88
+ })): Promise<void>;
89
89
  private update;
90
90
  private write;
91
91
  }
92
92
  export declare function listRuns(sessionCwd: string): Promise<NornRunInfo[]>;
93
93
  export declare function getRunInfo(runRoot: string): Promise<NornRunInfo>;
94
94
  export declare function resolveRunRoot(sessionCwd: string, run: string): Promise<string>;
95
+ export declare function assertRunVersion(version: unknown): void;
95
96
  export {};
@@ -45,15 +45,15 @@ import { jsonValueSchema } from "@vimhead.dev/norn/schema";
45
45
  import { Value } from "typebox/value";
46
46
  var RUN_STATE_FILE_NAME = "run-state.json";
47
47
  var LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
48
- function mergeInterruptedWorkflowParams(currentParams, params, fields) {
49
- if (!isRecord(currentParams) || !isRecord(params)) return params;
48
+ function mergeInterruptedWorkflowArgs(currentArgs, args, fields) {
49
+ if (!isRecord(currentArgs) || !isRecord(args)) return args;
50
50
  if (fields) {
51
- const unsupportedFields = Object.keys(params).filter((field) => !fields.includes(field));
52
- if (unsupportedFields.length > 0) throw new Error(`Interrupted workflow params cannot update non-gate fields: ${unsupportedFields.join(", ")}`);
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(", ")}`);
53
53
  }
54
- return { ...currentParams, ...params };
54
+ return { ...currentArgs, ...args };
55
55
  }
56
- var RUN_STATE_VERSION = 1;
56
+ var RUN_STATE_VERSION = 2;
57
57
  var NornRunStateStore = class _NornRunStateStore {
58
58
  path;
59
59
  state;
@@ -87,7 +87,7 @@ var NornRunStateStore = class _NornRunStateStore {
87
87
  static async load(runRoot) {
88
88
  const currentRoot = runCurrentRoot(runRoot);
89
89
  const files = createRunFileCoordinator(runRoot);
90
- 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) })));
90
+ 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"));
91
91
  return new _NornRunStateStore({ path: join(currentRoot, RUN_STATE_FILE_NAME), state, files });
92
92
  }
93
93
  currentState() {
@@ -125,11 +125,11 @@ var NornRunStateStore = class _NornRunStateStore {
125
125
  failed: { workflowId, error: metadata.summary, metadata, failedAt }
126
126
  });
127
127
  }
128
- async interruptCurrent(params, interruption) {
128
+ async interruptCurrent(args, interruption) {
129
129
  if (!this.state.current) throw new Error("Cannot interrupt run without current step");
130
130
  await this.update({
131
131
  status: "interrupted",
132
- current: { ...this.state.current, params, interruption: { status: "pending", ...interruption } },
132
+ current: { ...this.state.current, args, interruption: { status: "pending", ...interruption } },
133
133
  outcome: null,
134
134
  failed: null
135
135
  });
@@ -144,11 +144,11 @@ var NornRunStateStore = class _NornRunStateStore {
144
144
  if (!this.state.current) throw new Error("Cannot stop run without current step");
145
145
  await this.update({ status: "stopped", outcome: null, failed: null });
146
146
  }
147
- async replaceCurrentParams(params) {
147
+ async replaceCurrentArgs(args) {
148
148
  if (!this.state.current) throw new Error("Cannot resume run without current step");
149
149
  await this.update({
150
150
  status: "running",
151
- current: { ...this.state.current, params, interruption: { ...this.state.current.interruption, status: "satisfied" } },
151
+ current: { ...this.state.current, args, interruption: { ...this.state.current.interruption, status: "satisfied" } },
152
152
  outcome: null,
153
153
  failed: null
154
154
  });
@@ -165,15 +165,15 @@ var NornRunStateStore = class _NornRunStateStore {
165
165
  }
166
166
  async update(patch) {
167
167
  await this.files.withExclusiveLock(this.path, async (path) => {
168
- const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME) }));
168
+ const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME) }), "execute");
169
169
  const next = { ...current, ...patch, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
170
- if (next.current?.params !== void 0) Value.Assert(jsonValueSchema, next.current.params);
170
+ if (next.current?.args !== void 0) Value.Assert(jsonValueSchema, next.current.args);
171
171
  await writeJsonAtomically(path, next);
172
172
  this.state = next;
173
173
  });
174
174
  }
175
175
  async write() {
176
- if (this.state.current?.params !== void 0) Value.Assert(jsonValueSchema, this.state.current.params);
176
+ if (this.state.current?.args !== void 0) Value.Assert(jsonValueSchema, this.state.current.args);
177
177
  await mkdir2(dirname2(this.path), { recursive: true });
178
178
  await this.files.withExclusiveLock(this.path, (path) => writeJsonAtomically(path, this.state));
179
179
  }
@@ -192,8 +192,13 @@ async function listRuns(sessionCwd) {
192
192
  }
193
193
  async function getRunInfo(runRoot) {
194
194
  try {
195
- const resumeRequest = await readOptionalRunResumeRequest(runRoot);
196
- const state = parseNornRunState(await readRunStateJson(runCurrentRoot(runRoot)));
195
+ const pendingResume = await readOptionalRunResumeRequest(runRoot).then(
196
+ (request) => ({ status: "fulfilled", request }),
197
+ (error) => ({ status: "rejected", error })
198
+ );
199
+ const state = parseNornRunState(await readRunStateJson(runCurrentRoot(runRoot)), "inspect");
200
+ if (state.version === RUN_STATE_VERSION && pendingResume.status === "rejected") throw pendingResume.error;
201
+ const resumeRequest = state.version === RUN_STATE_VERSION && pendingResume.status === "fulfilled" ? pendingResume.request : void 0;
197
202
  return {
198
203
  version: state.version,
199
204
  id: state.id,
@@ -277,10 +282,13 @@ async function readRunStateFile(input) {
277
282
  function isRecord(value) {
278
283
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
279
284
  }
280
- function parseNornRunState(value) {
285
+ function assertRunVersion(version) {
286
+ 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.`);
287
+ }
288
+ function parseNornRunState(value, mode) {
281
289
  if (!value || typeof value !== "object") throw new Error("Invalid run state");
282
290
  const state = value;
283
- if (state.version !== RUN_STATE_VERSION) throw new Error(`Unsupported run state version: ${String(state.version)}`);
291
+ if (mode === "execute" || state.version !== 1) assertRunVersion(state.version);
284
292
  if (typeof state.id !== "string" || state.id.length === 0) throw new Error("Invalid run state id");
285
293
  const normalizedState = state;
286
294
  if (typeof normalizedState.name !== "string" || normalizedState.name.length === 0) normalizedState.name = state.id;
@@ -306,7 +314,7 @@ function runInterruption(state) {
306
314
  if (state.status !== "interrupted" || !state.current?.interruption) return void 0;
307
315
  return {
308
316
  workflowId: state.current.workflowId,
309
- params: state.current.params,
317
+ args: state.version === 1 ? state.current.params : state.current.args,
310
318
  description: state.current.interruption.description ?? "",
311
319
  fields: state.current.interruption.fields
312
320
  };
@@ -320,8 +328,9 @@ function runFailure(state) {
320
328
  export {
321
329
  NornRunStateStore,
322
330
  RUN_STATE_FILE_NAME,
331
+ assertRunVersion,
323
332
  getRunInfo,
324
333
  listRuns,
325
- mergeInterruptedWorkflowParams,
334
+ mergeInterruptedWorkflowArgs,
326
335
  resolveRunRoot
327
336
  };
@@ -18,7 +18,6 @@ type DefaultNornRunInput = {
18
18
  readonly agentDir?: string;
19
19
  readonly responseCollector: NornAgentResponseCollector;
20
20
  readonly resources: NornRunResources;
21
- readonly state: NornRun["state"];
22
21
  readonly logger: NornRunLogger;
23
22
  readonly artifacts: NornArtifacts;
24
23
  readonly logs: NornRunLogs;
@@ -26,7 +25,6 @@ type DefaultNornRunInput = {
26
25
  export declare class NornRunContext implements NornProjectRun {
27
26
  private readonly input;
28
27
  readonly resources: NornRunResources;
29
- readonly state: NornRun["state"];
30
28
  readonly artifacts: NornRun["artifacts"];
31
29
  readonly logs: NornRun["logs"];
32
30
  readonly commands: NornRun["commands"];
@@ -41,7 +39,7 @@ export declare class NornRunContext implements NornProjectRun {
41
39
  private createAgentRunner;
42
40
  path(relativePath: string): string;
43
41
  projectPath(relativePath: string): string;
44
- next(workflowId: string, params: unknown): ReturnType<NornRun["next"]>;
42
+ next(workflowId: string, args: unknown): ReturnType<NornRun["next"]>;
45
43
  complete(metadata?: NornRunOutcomeMetadata): ReturnType<NornRun["complete"]>;
46
44
  fail(metadata: NornRunOutcomeMetadata & {
47
45
  readonly summary: string;
@@ -4,7 +4,7 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
4
4
  // ../core/src/workflow-transition.ts
5
5
  function createWorkflowTransition(input) {
6
6
  if (typeof input.workflowId !== "string" || input.workflowId.trim().length === 0) throw new Error("Workflow transition requires a nonempty workflow ID");
7
- return { type: "next", workflowId: input.workflowId, params: input.params };
7
+ return { type: "next", workflowId: input.workflowId, args: input.args };
8
8
  }
9
9
 
10
10
  // src/internal/run.ts
@@ -18,7 +18,6 @@ var NornRunContext = class _NornRunContext {
18
18
  this.projectRoot = input.projectRoot;
19
19
  this.cwd = input.cwd;
20
20
  this.resources = input.resources;
21
- this.state = input.state;
22
21
  this.artifacts = input.artifacts;
23
22
  this.logs = {
24
23
  read: (log) => this.input.logs.read(log)
@@ -40,7 +39,6 @@ var NornRunContext = class _NornRunContext {
40
39
  }
41
40
  input;
42
41
  resources;
43
- state;
44
42
  artifacts;
45
43
  logs;
46
44
  commands;
@@ -82,8 +80,8 @@ var NornRunContext = class _NornRunContext {
82
80
  projectPath(relativePath) {
83
81
  return this.resolveFromRoot(this.projectRoot, this.projectRoot, relativePath);
84
82
  }
85
- next(workflowId, params) {
86
- return createWorkflowTransition({ workflowId, params });
83
+ next(workflowId, args) {
84
+ return createWorkflowTransition({ workflowId, args });
87
85
  }
88
86
  complete(metadata) {
89
87
  return { type: "complete", metadata };
@@ -1,21 +1,23 @@
1
- import { type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRunFor, type NornRunNext, type NornWorkflowImplementation, type NornWorkflowPluginInfo } from "@vimhead.dev/norn";
2
- import { type TSchema } from "typebox";
3
- export type NornRegisteredWorkflow = {
4
- workflow: NornAnyWorkflowDeclaration;
5
- implementation: NornWorkflowImplementation<NornAnyWorkflowDeclaration, unknown>;
6
- configuration?: NornRegisteredConfiguration;
7
- plugin?: NornWorkflowPluginInfo;
8
- };
1
+ import { type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRunFor, type NornRunNext, type NornWorkflowSource, type NornProjectConfigurationInfo } from "@vimhead.dev/norn";
2
+ import type { TSchema } from "typebox";
9
3
  export type NornRegisteredConfiguration = {
10
- readonly schema: TSchema;
4
+ readonly key: string;
5
+ readonly schema: TSchema | undefined;
11
6
  readonly input: unknown;
12
7
  readonly value: unknown;
13
8
  };
14
- export declare function decodePluginConfiguration(input: {
15
- readonly pluginId: string;
9
+ export type NornRegisteredWorkflow = {
10
+ readonly workflow: NornAnyWorkflowDeclaration;
11
+ readonly configuration: NornRegisteredConfiguration;
12
+ readonly scopeId: string | undefined;
13
+ readonly source: NornWorkflowSource | undefined;
14
+ };
15
+ export declare function decodeConfiguration(input: {
16
+ readonly key: string;
16
17
  readonly schema: TSchema | undefined;
17
18
  readonly value: unknown;
18
- }): NornRegisteredConfiguration | undefined;
19
+ }): NornRegisteredConfiguration;
20
+ export declare function assertCompatibleConfigurationOwners(workflows: readonly NornAnyWorkflowDeclaration[]): void;
19
21
  export type NornWorkflowStepResult = NornRunNext | {
20
22
  readonly type: "complete";
21
23
  readonly workflow: NornAnyWorkflowDeclaration;
@@ -27,17 +29,23 @@ export type NornWorkflowStepResult = NornRunNext | {
27
29
  };
28
30
  export declare class NornWorkflowRegistry {
29
31
  private readonly entries;
30
- register<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, implementation: NornWorkflowImplementation<TWorkflow, unknown>, metadata?: {
31
- readonly plugin?: NornWorkflowPluginInfo;
32
- readonly configuration?: NornRegisteredConfiguration;
32
+ private readonly scopes;
33
+ register(input: {
34
+ readonly workflow: NornAnyWorkflowDeclaration;
35
+ readonly config: Readonly<Record<string, unknown>>;
36
+ readonly source: NornWorkflowSource | undefined;
33
37
  }): NornDispose;
34
38
  list(options?: {
35
39
  readonly entrypointsOnly?: boolean;
36
40
  }): NornRegisteredWorkflowInfo[];
37
41
  inspect(workflowId: string): NornInspectedWorkflowInfo | undefined;
42
+ configurationInfos(): NornProjectConfigurationInfo[];
38
43
  launchableEntries(): NornRegisteredWorkflow[];
39
44
  workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined;
40
- describeGate<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, params: unknown, configOverride?: unknown): Promise<string>;
41
- execute<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, params: unknown, configOverride?: unknown): Promise<NornWorkflowStepResult>;
45
+ describeGate<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, args: unknown, configOverride?: unknown): Promise<string>;
46
+ execute<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, args: unknown, configOverride?: unknown): Promise<NornWorkflowStepResult>;
47
+ private createExecutionContext;
48
+ private requireEntry;
42
49
  private sortedEntries;
43
50
  }
51
+ export declare function assertWorkflowDefinition(workflow: NornAnyWorkflowDeclaration): void;