@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.
- package/assets/README.md +17 -26
- package/assets/docs/README.md +3 -3
- package/assets/docs/agents.md +12 -1
- package/assets/docs/cli.md +5 -5
- package/assets/docs/composition.md +21 -21
- package/assets/docs/persistence.md +7 -10
- package/assets/docs/projects.md +30 -18
- package/assets/docs/providers.md +1 -1
- package/assets/docs/recovery.md +13 -9
- package/assets/docs/resources.md +4 -19
- package/assets/docs/schemas.md +1 -1
- package/assets/docs/workflows.md +95 -7
- package/assets/examples/agent-then-analysis/README.md +4 -4
- package/assets/examples/agent-then-analysis/input.json +1 -1
- package/assets/examples/agent-then-analysis/norn.project.json +1 -1
- package/assets/examples/agent-then-analysis/plugin.ts +55 -68
- package/assets/examples/caller-selected-continuation/README.md +5 -5
- package/assets/examples/caller-selected-continuation/caller.ts +35 -38
- package/assets/examples/caller-selected-continuation/input.json +2 -2
- package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
- package/assets/examples/caller-selected-continuation/producer.ts +15 -23
- package/assets/examples/coordinating-multiple-agents/README.md +2 -2
- package/assets/examples/coordinating-multiple-agents/input.json +1 -1
- package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
- package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
- package/assets/examples/getting-started/norn.project.json +1 -1
- package/assets/examples/getting-started/plugin.ts +16 -26
- package/assets/examples/minimal-workflow/README.md +8 -8
- package/assets/examples/minimal-workflow/norn.project.json +1 -1
- package/assets/examples/minimal-workflow/plugin.ts +11 -25
- package/assets/examples/shared-state/README.md +10 -2
- package/assets/examples/shared-state/input.json +1 -1
- package/assets/examples/shared-state/norn.project.json +1 -1
- package/assets/examples/shared-state/plugin.ts +36 -41
- package/assets/examples/shared-state/shared-state.ts +75 -0
- package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
- package/assets/examples/worktree-development-loop/README.md +6 -7
- package/assets/examples/worktree-development-loop/norn.project.json +1 -1
- package/assets/examples/worktree-development-loop/plugin.ts +6 -26
- package/assets/examples/worktree-development-loop/scope.ts +4 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
- package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
- package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
- package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
- package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/cli.ts +35 -34
- package/assets/packages/cli/src/client.ts +9 -16
- package/assets/packages/cli/src/documentation-intro.ts +1 -1
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
- package/assets/packages/cli/src/internal/engine.ts +38 -62
- package/assets/packages/cli/src/internal/errors.ts +4 -4
- package/assets/packages/cli/src/internal/launch-request.ts +6 -6
- package/assets/packages/cli/src/internal/run-state.ts +31 -21
- package/assets/packages/cli/src/internal/run.ts +2 -5
- package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
- package/assets/packages/cli/src/workflow-loader.ts +214 -0
- package/assets/packages/core/src/workflow-transition.ts +2 -2
- package/assets/packages/sdk/src/api.ts +133 -289
- package/assets/packages/sdk/src/index.ts +1 -1
- package/assets/packages/sdk/src/schema.ts +3 -3
- package/assets/tests/workflow-ref.test.ts +50 -51
- package/dist/cli.js +33 -32
- package/dist/client.d.ts +3 -4
- package/dist/client.js +3 -8
- package/dist/documentation-intro.js +1 -1
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/agent-response-tool.js +3 -3
- package/dist/internal/engine.d.ts +4 -8
- package/dist/internal/engine.js +36 -49
- package/dist/internal/errors.d.ts +3 -3
- package/dist/internal/errors.js +1 -1
- package/dist/internal/launch-request.d.ts +4 -4
- package/dist/internal/launch-request.js +2 -2
- package/dist/internal/run-state.d.ts +9 -8
- package/dist/internal/run-state.js +29 -20
- package/dist/internal/run.d.ts +1 -3
- package/dist/internal/run.js +3 -5
- package/dist/internal/workflow-registry.d.ts +25 -17
- package/dist/internal/workflow-registry.js +107 -72
- package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
- package/dist/workflow-loader.js +235 -0
- package/package.json +2 -2
- package/assets/examples/worktree-development-loop/manifest.ts +0 -26
- package/assets/examples/worktree-development-loop/state.ts +0 -23
- package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
- package/assets/packages/cli/src/internal/run-resources.ts +0 -23
- package/assets/packages/cli/src/internal/state-store.ts +0 -83
- package/assets/packages/cli/src/plugin-loader.ts +0 -400
- package/dist/internal/run-resources.d.ts +0 -6
- package/dist/internal/run-resources.js +0 -26
- package/dist/internal/state-store.d.ts +0 -23
- package/dist/internal/state-store.js +0 -103
- package/dist/plugin-loader.js +0 -341
|
@@ -3,16 +3,15 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
import type {
|
|
5
5
|
DeletedNornRunInfo,
|
|
6
|
-
|
|
6
|
+
NornWorkflowDiagnostic,
|
|
7
7
|
NornProjectInspection,
|
|
8
8
|
NornWorkflowCatalogInfo,
|
|
9
9
|
NornWorkflowInspection,
|
|
10
10
|
NornRunCheckpoint,
|
|
11
11
|
NornRunInfo,
|
|
12
12
|
NornRunMetrics,
|
|
13
|
-
NornWorkflowStateReader,
|
|
14
13
|
} from "@vimhead.dev/norn";
|
|
15
|
-
import { loadNornProject } from "./
|
|
14
|
+
import { loadNornProject } from "./workflow-loader.ts";
|
|
16
15
|
import { NornProjectLoadError } from "./internal/errors.ts";
|
|
17
16
|
import type { NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
|
|
18
17
|
|
|
@@ -32,10 +31,9 @@ export type NornClient = {
|
|
|
32
31
|
inspect(workflowId: string): Promise<NornWorkflowInspection>;
|
|
33
32
|
entries(): Promise<readonly NornRegisteredWorkflow[]>;
|
|
34
33
|
};
|
|
35
|
-
readonly state: NornWorkflowStateReader;
|
|
36
34
|
readonly runs: {
|
|
37
|
-
start(input: { readonly workflowId: string; readonly
|
|
38
|
-
resume(input: { readonly run: string; readonly
|
|
35
|
+
start(input: { readonly workflowId: string; readonly args: unknown; readonly configOverride?: unknown }): Promise<NornRunInfo>;
|
|
36
|
+
resume(input: { readonly run: string; readonly args?: unknown }): Promise<NornRunInfo>;
|
|
39
37
|
wait(run: string): Promise<NornRunInfo>;
|
|
40
38
|
list(): Promise<NornRunInfo[]>;
|
|
41
39
|
inspect(run: string): Promise<NornRunInfo>;
|
|
@@ -61,7 +59,6 @@ export function createNornClient(input: NornClientInput = {}): NornClient {
|
|
|
61
59
|
inspect: async (workflowId) => processRunner.readJson<NornWorkflowInspection>(["workflows", "inspect", workflowId]),
|
|
62
60
|
entries: async () => (await catalog.load()).workflows,
|
|
63
61
|
},
|
|
64
|
-
state: catalog.state,
|
|
65
62
|
runs: {
|
|
66
63
|
start: async (startInput) => (await processRunner.readJson<{ run: NornRunInfo }>(["runs", "start", startInput.workflowId], JSON.stringify(runStartInput(startInput)))).run,
|
|
67
64
|
resume: async (resumeInput) => (await processRunner.readJson<{ run: NornRunInfo }>(["runs", "resume", resumeInput.run], runResumeStdin(resumeInput))).run,
|
|
@@ -82,10 +79,6 @@ export function createNornClient(input: NornClientInput = {}): NornClient {
|
|
|
82
79
|
|
|
83
80
|
class NornWorkflowCatalog {
|
|
84
81
|
private loaded: ReturnType<typeof loadNornProject> | undefined;
|
|
85
|
-
readonly state: NornWorkflowStateReader = {
|
|
86
|
-
get: async (state) => (await this.load()).state.get(state),
|
|
87
|
-
getOptional: async (state) => (await this.load()).state.getOptional(state),
|
|
88
|
-
};
|
|
89
82
|
|
|
90
83
|
constructor(private readonly cwd: string) {}
|
|
91
84
|
|
|
@@ -107,7 +100,7 @@ class NornProcessRunner {
|
|
|
107
100
|
|
|
108
101
|
async readJson<T>(args: readonly string[], stdin?: string): Promise<T> {
|
|
109
102
|
const result = await this.spawn(args, stdin);
|
|
110
|
-
const parsed = JSON.parse(result.stdout) as T | { error?: { code?: string; message?: string; diagnostics?:
|
|
103
|
+
const parsed = JSON.parse(result.stdout) as T | { error?: { code?: string; message?: string; diagnostics?: NornWorkflowDiagnostic[] } };
|
|
111
104
|
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
112
105
|
if (parsed.error?.code === "NORN_PROJECT_INVALID" && Array.isArray(parsed.error.diagnostics)) throw new NornProjectLoadError({ diagnostics: parsed.error.diagnostics });
|
|
113
106
|
throw new Error(parsed.error?.message ?? "Norn command failed");
|
|
@@ -151,10 +144,10 @@ class NornProcessRunner {
|
|
|
151
144
|
}
|
|
152
145
|
}
|
|
153
146
|
|
|
154
|
-
function runStartInput(input: { readonly
|
|
155
|
-
return input.configOverride === undefined ? {
|
|
147
|
+
function runStartInput(input: { readonly args: unknown; readonly configOverride?: unknown }): { readonly args: unknown; readonly config?: unknown } {
|
|
148
|
+
return input.configOverride === undefined ? { args: input.args } : { args: input.args, config: input.configOverride };
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
function runResumeStdin(input: { readonly
|
|
159
|
-
return input.
|
|
151
|
+
function runResumeStdin(input: { readonly args?: unknown }): string | undefined {
|
|
152
|
+
return input.args === undefined ? undefined : JSON.stringify({ args: input.args });
|
|
160
153
|
}
|
|
@@ -24,7 +24,7 @@ export function renderNornDocumentationIntro(input: {
|
|
|
24
24
|
"",
|
|
25
25
|
"Live CLI contracts (argument arrays appended to the runtime argv above):",
|
|
26
26
|
'- ["workflows", "list"] — currently registered entrypoints.',
|
|
27
|
-
'- ["workflows", "inspect", "<workflow-id>"] — current instructions,
|
|
27
|
+
'- ["workflows", "inspect", "<workflow-id>"] — current instructions, args schema, and source.',
|
|
28
28
|
'- ["help"] — command usage.',
|
|
29
29
|
].join("\n");
|
|
30
30
|
}
|
|
@@ -2,8 +2,8 @@ import type { NornBuildInfo } from "./build-info.ts";
|
|
|
2
2
|
|
|
3
3
|
export const NORN_GENERATED_BUILD_INFO = {
|
|
4
4
|
"kind": "npm-registry",
|
|
5
|
-
"version": "0.1.0-tip.
|
|
6
|
-
"commit": "
|
|
5
|
+
"version": "0.1.0-tip.35390859630.1",
|
|
6
|
+
"commit": "7a7976170d62c342c29c6479444394d542c3f22b",
|
|
7
7
|
"packageSpec": "@vimhead.dev/norn-cli@tip",
|
|
8
8
|
"upgrade": {
|
|
9
9
|
"supported": false,
|
|
@@ -71,12 +71,12 @@ export class NornAgentResponseToolFactory {
|
|
|
71
71
|
label: Type.String({ description: "The exact workflow agent label provided in the prompt." }),
|
|
72
72
|
response: Type.Unknown({ description: "The structured response object requested by the workflow." }),
|
|
73
73
|
}),
|
|
74
|
-
async execute(_toolCallId,
|
|
74
|
+
async execute(_toolCallId, args) {
|
|
75
75
|
try {
|
|
76
|
-
const captured = responseCollector.capture(
|
|
76
|
+
const captured = responseCollector.capture(args.runId, args.label, args.response);
|
|
77
77
|
return {
|
|
78
78
|
content: [{ type: "text", text: `Recorded workflow agent response for ${captured.label}.` }],
|
|
79
|
-
details: { valid: true, runId:
|
|
79
|
+
details: { valid: true, runId: args.runId, label: captured.label, response: captured.response } as AgentResponseToolDetails,
|
|
80
80
|
terminate: true,
|
|
81
81
|
};
|
|
82
82
|
} catch (error) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type NornAnyWorkflowDeclaration,
|
|
3
|
-
type NornAnyWorkflowPluginManifest,
|
|
4
3
|
type NornDispose,
|
|
5
4
|
type NornInterruptedRunResult,
|
|
6
5
|
type NornRunCheckpoint,
|
|
@@ -8,9 +7,6 @@ import {
|
|
|
8
7
|
type NornRunNext,
|
|
9
8
|
type NornRunResult,
|
|
10
9
|
type NornRunStartOptions,
|
|
11
|
-
type NornWorkflowPlugin,
|
|
12
|
-
type NornWorkflowPluginImplementation,
|
|
13
|
-
type NornWorkflowPluginImplementationInput,
|
|
14
10
|
} from "@vimhead.dev/norn";
|
|
15
11
|
import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
|
|
16
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -24,12 +20,11 @@ import { clearRunResumeRequest, matchesRunResumeRequest, readOptionalRunResumeRe
|
|
|
24
20
|
import { NornRunLogs } from "./logs.ts";
|
|
25
21
|
import { NornRunLease } from "./run-lease.ts";
|
|
26
22
|
import { NornRunLogger } from "./run-log.ts";
|
|
27
|
-
import {
|
|
28
|
-
import { getRunInfo,
|
|
23
|
+
import { NornRunResources } from "../resources.ts";
|
|
24
|
+
import { assertRunVersion, getRunInfo, mergeInterruptedWorkflowArgs, NornRunStateStore, resolveRunRoot, type NornRunState } from "./run-state.ts";
|
|
29
25
|
import { NornRunStore, runCurrentRoot } from "./run-store.ts";
|
|
30
26
|
import { NornRunContext } from "./run.ts";
|
|
31
|
-
import {
|
|
32
|
-
import { decodePluginConfiguration, NornWorkflowRegistry, type NornRegisteredWorkflow, type NornWorkflowStepResult } from "./workflow-registry.ts";
|
|
27
|
+
import { NornWorkflowRegistry, type NornRegisteredWorkflow, type NornWorkflowStepResult } from "./workflow-registry.ts";
|
|
33
28
|
|
|
34
29
|
const RUNS_DIR_NAME = ".norn";
|
|
35
30
|
const MANIFEST_FILE_NAME = "manifest.json";
|
|
@@ -62,14 +57,12 @@ type ActiveRun = {
|
|
|
62
57
|
|
|
63
58
|
type WorkflowStep = {
|
|
64
59
|
readonly workflow: NornAnyWorkflowDeclaration;
|
|
65
|
-
readonly
|
|
60
|
+
readonly args: unknown;
|
|
66
61
|
readonly interruption?: NonNullable<NornRunState["current"]>["interruption"];
|
|
67
62
|
};
|
|
68
63
|
|
|
69
64
|
export class NornEngine {
|
|
70
65
|
private readonly registry = new NornWorkflowRegistry();
|
|
71
|
-
private readonly registrarState = new NornMemoryWorkflowState();
|
|
72
|
-
private readonly disposersByPlugin = new Map<string, NornDispose[]>();
|
|
73
66
|
private readonly responseCollector: NornAgentResponseCollector;
|
|
74
67
|
private readonly activeRuns = new Map<string, ActiveRun>();
|
|
75
68
|
|
|
@@ -77,19 +70,15 @@ export class NornEngine {
|
|
|
77
70
|
this.responseCollector = input.responseCollector ?? new NornAgentResponseCollector();
|
|
78
71
|
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.disposersByPlugin.set(plugin.manifest.id, disposers);
|
|
90
|
-
return () => {
|
|
91
|
-
if (this.disposersByPlugin.get(plugin.manifest.id) === disposers) this.disposePlugin(plugin.manifest.id);
|
|
92
|
-
};
|
|
73
|
+
registerWorkflows(workflows: readonly NornAnyWorkflowDeclaration[]): NornDispose {
|
|
74
|
+
const disposers: NornDispose[] = [];
|
|
75
|
+
try {
|
|
76
|
+
for (const workflow of workflows) disposers.push(this.registry.register({ workflow, config: this.input.config ?? {}, source: undefined }));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
return () => { for (const dispose of disposers) dispose(); };
|
|
93
82
|
}
|
|
94
83
|
|
|
95
84
|
listWorkflows() {
|
|
@@ -100,20 +89,12 @@ export class NornEngine {
|
|
|
100
89
|
return this.registry.launchableEntries();
|
|
101
90
|
}
|
|
102
91
|
|
|
103
|
-
private resolvePluginImplementation<TManifest extends NornAnyWorkflowPluginManifest>(
|
|
104
|
-
implementationInput: NornWorkflowPluginImplementationInput<TManifest>,
|
|
105
|
-
): NornWorkflowPluginImplementation<TManifest> {
|
|
106
|
-
return typeof implementationInput === "function"
|
|
107
|
-
? implementationInput({ cwd: this.input.cwd, state: this.registrarState })
|
|
108
|
-
: implementationInput;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
92
|
async runWorkflow<TWorkflow extends NornAnyWorkflowDeclaration>(
|
|
112
93
|
workflow: TWorkflow,
|
|
113
|
-
|
|
94
|
+
args: unknown,
|
|
114
95
|
options: NornRunStartOptions | undefined,
|
|
115
96
|
): Promise<NornRunResult> {
|
|
116
|
-
const session = await this.createRunSession(workflow,
|
|
97
|
+
const session = await this.createRunSession(workflow, args, options);
|
|
117
98
|
const current = session.state.currentState().current;
|
|
118
99
|
if (!current) throw new Error("New run is missing its initial workflow step");
|
|
119
100
|
return this.runScheduler(session, toWorkflowStep(workflow, current));
|
|
@@ -126,6 +107,7 @@ export class NornEngine {
|
|
|
126
107
|
|
|
127
108
|
async rollbackRun(path: string, checkpointId: string): Promise<NornRunInfo> {
|
|
128
109
|
const runRoot = await resolveRunRoot(this.input.cwd, path);
|
|
110
|
+
assertRunVersion((await getRunInfo(runRoot)).version);
|
|
129
111
|
const lease = await NornRunLease.acquire(runRoot);
|
|
130
112
|
try {
|
|
131
113
|
const pendingRequest = await readOptionalRunResumeRequest(runRoot);
|
|
@@ -142,21 +124,21 @@ export class NornEngine {
|
|
|
142
124
|
}
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
async resumeWorkflow(path: string,
|
|
146
|
-
return this.resumeWorkflowExecution({ path,
|
|
127
|
+
async resumeWorkflow(path: string, args?: unknown): Promise<NornRunResult> {
|
|
128
|
+
return this.resumeWorkflowExecution({ path, args, expectedRequest: undefined });
|
|
147
129
|
}
|
|
148
130
|
|
|
149
131
|
async resumeRequestedWorkflow(input: { readonly runRoot: string; readonly request: NornRunResumeRequest }): Promise<NornRunResult> {
|
|
150
|
-
return this.resumeWorkflowExecution({ path: input.runRoot,
|
|
132
|
+
return this.resumeWorkflowExecution({ path: input.runRoot, args: input.request.args, expectedRequest: input.request });
|
|
151
133
|
}
|
|
152
134
|
|
|
153
|
-
private async resumeWorkflowExecution(input: { readonly path: string; readonly
|
|
154
|
-
const {
|
|
135
|
+
private async resumeWorkflowExecution(input: { readonly path: string; readonly args: unknown; readonly expectedRequest: NornRunResumeRequest | undefined }): Promise<NornRunResult> {
|
|
136
|
+
const { args, expectedRequest } = input;
|
|
155
137
|
const runRoot = await resolveRunRoot(this.input.cwd, input.path);
|
|
156
138
|
const initialStateStore = await NornRunStateStore.load(runRoot);
|
|
157
139
|
const initialState = initialStateStore.currentState();
|
|
158
|
-
if (initialState.status === "interrupted" &&
|
|
159
|
-
if (initialState.status === "pendingResume" &&
|
|
140
|
+
if (initialState.status === "interrupted" && args === undefined) throw new Error(`Interrupted workflow resume requires args: ${runRoot}`);
|
|
141
|
+
if (initialState.status === "pendingResume" && args !== undefined) throw new Error(`Pending-resume workflows do not accept args: ${runRoot}`);
|
|
160
142
|
if (initialState.status !== "interrupted" && initialState.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
|
|
161
143
|
|
|
162
144
|
const lease = await NornRunLease.acquire(runRoot);
|
|
@@ -172,9 +154,9 @@ export class NornEngine {
|
|
|
172
154
|
const workflow = this.registry.workflowById(current.workflowId);
|
|
173
155
|
if (!workflow) throw new Error(`Unknown workflow for resumed run: ${current.workflowId}`);
|
|
174
156
|
if (state.status === "interrupted") {
|
|
175
|
-
const
|
|
176
|
-
Value.Decode(workflow.
|
|
177
|
-
await session.state.
|
|
157
|
+
const rawArgs = mergeInterruptedWorkflowArgs(current.args, args, current.interruption?.fields);
|
|
158
|
+
Value.Decode(workflow.args, rawArgs);
|
|
159
|
+
await session.state.replaceCurrentArgs(rawArgs);
|
|
178
160
|
await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.run.cwd });
|
|
179
161
|
const resumedCurrent = session.state.currentState().current;
|
|
180
162
|
if (!resumedCurrent) throw new Error(`Run is not resumable: ${runRoot}`);
|
|
@@ -203,12 +185,12 @@ export class NornEngine {
|
|
|
203
185
|
throwIfRunAborted(session.activeRun);
|
|
204
186
|
const stepRuntime = session.run.forWorkflow(currentStep.workflow);
|
|
205
187
|
if (shouldPauseForGate(currentStep)) {
|
|
206
|
-
const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, currentStep.
|
|
188
|
+
const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, currentStep.args, session.state.currentState().configOverride);
|
|
207
189
|
const interruption = { description, fields: currentStep.workflow.gate?.fields };
|
|
208
|
-
await session.state.interruptCurrent(currentStep.
|
|
190
|
+
await session.state.interruptCurrent(currentStep.args, interruption);
|
|
209
191
|
await recordRunEvent(session, { type: "run.interrupted", workflowId: currentStep.workflow.id });
|
|
210
192
|
await commitRunBoundary(session, `run interrupted: ${currentStep.workflow.id}`);
|
|
211
|
-
return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, currentStep.
|
|
193
|
+
return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, currentStep.args, interruption);
|
|
212
194
|
}
|
|
213
195
|
await session.state.startStep(toRunStateStep(currentStep));
|
|
214
196
|
const stepResult = await this.executeWorkflowStep(session, currentStep, stepRuntime);
|
|
@@ -251,7 +233,7 @@ export class NornEngine {
|
|
|
251
233
|
try {
|
|
252
234
|
await assertWorkspaceBoundary(session, run.workspace);
|
|
253
235
|
await recordRunEvent(session, { type: "workflow.started", workflowId: step.workflow.id });
|
|
254
|
-
const result = await this.registry.execute(step.workflow, run, step.
|
|
236
|
+
const result = await this.registry.execute(step.workflow, run, step.args, session.state.currentState().configOverride);
|
|
255
237
|
await assertWorkspaceBoundary(session, run.workspace);
|
|
256
238
|
const durationMs = Date.now() - startedAtMs;
|
|
257
239
|
if (result.type === "complete") await recordRunEvent(session, { type: "workflow.completed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
|
|
@@ -274,14 +256,14 @@ export class NornEngine {
|
|
|
274
256
|
if (!workflow) throw new Error(`Unknown next workflow: ${next.workflowId}`);
|
|
275
257
|
return {
|
|
276
258
|
workflow,
|
|
277
|
-
|
|
259
|
+
args: next.args,
|
|
278
260
|
interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : undefined,
|
|
279
261
|
};
|
|
280
262
|
}
|
|
281
263
|
|
|
282
264
|
private async createRunSession<TWorkflow extends NornAnyWorkflowDeclaration>(
|
|
283
265
|
workflow: TWorkflow,
|
|
284
|
-
|
|
266
|
+
args: unknown,
|
|
285
267
|
options: NornRunStartOptions | undefined,
|
|
286
268
|
): Promise<RunSession> {
|
|
287
269
|
const id = options?.id ?? randomUUID();
|
|
@@ -314,7 +296,7 @@ export class NornEngine {
|
|
|
314
296
|
workspace,
|
|
315
297
|
configOverride: options?.configOverride,
|
|
316
298
|
current: {
|
|
317
|
-
workflowId: workflow.id,
|
|
299
|
+
workflowId: workflow.id, args, cwd, env: {},
|
|
318
300
|
interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : undefined,
|
|
319
301
|
},
|
|
320
302
|
startedAt,
|
|
@@ -364,7 +346,7 @@ export class NornEngine {
|
|
|
364
346
|
await mkdir(input.workspace, { recursive: true });
|
|
365
347
|
await mkdir(artifactsRoot, { recursive: true });
|
|
366
348
|
await mkdir(logsRoot, { recursive: true });
|
|
367
|
-
const
|
|
349
|
+
const resources = await NornRunResources.initialize(dirname(input.currentRoot));
|
|
368
350
|
return new NornRunContext({
|
|
369
351
|
id: input.id,
|
|
370
352
|
runRoot: input.currentRoot,
|
|
@@ -376,7 +358,6 @@ export class NornEngine {
|
|
|
376
358
|
agentDir: this.input.agentDir,
|
|
377
359
|
responseCollector: this.responseCollector,
|
|
378
360
|
resources,
|
|
379
|
-
state,
|
|
380
361
|
logger: input.logger,
|
|
381
362
|
artifacts: new NornArtifacts(artifactsRoot, resources.files),
|
|
382
363
|
logs: new NornRunLogs(logsRoot, resources.files),
|
|
@@ -416,11 +397,6 @@ export class NornEngine {
|
|
|
416
397
|
activeRun.finish();
|
|
417
398
|
}
|
|
418
399
|
|
|
419
|
-
private disposePlugin(pluginId: string): void {
|
|
420
|
-
const disposers = this.disposersByPlugin.get(pluginId) ?? [];
|
|
421
|
-
for (const dispose of [...disposers].reverse()) dispose();
|
|
422
|
-
this.disposersByPlugin.delete(pluginId);
|
|
423
|
-
}
|
|
424
400
|
}
|
|
425
401
|
|
|
426
402
|
function defaultRunRoot(cwd: string, id: string): string {
|
|
@@ -434,7 +410,7 @@ function workflowDefaultCwd(projectRoot: string, workspace: string, workflow: No
|
|
|
434
410
|
function toWorkflowStep(workflow: NornAnyWorkflowDeclaration, step: NonNullable<NornRunState["current"]>): WorkflowStep {
|
|
435
411
|
return {
|
|
436
412
|
workflow,
|
|
437
|
-
|
|
413
|
+
args: step.args,
|
|
438
414
|
interruption: step.interruption,
|
|
439
415
|
};
|
|
440
416
|
}
|
|
@@ -442,7 +418,7 @@ function toWorkflowStep(workflow: NornAnyWorkflowDeclaration, step: NonNullable<
|
|
|
442
418
|
function toRunStateStep(step: WorkflowStep): NonNullable<NornRunState["current"]> {
|
|
443
419
|
return {
|
|
444
420
|
workflowId: step.workflow.id,
|
|
445
|
-
|
|
421
|
+
args: step.args,
|
|
446
422
|
cwd: "",
|
|
447
423
|
env: {},
|
|
448
424
|
interruption: step.interruption,
|
|
@@ -464,7 +440,7 @@ function isRunStopped(session: RunSession, error: unknown): boolean {
|
|
|
464
440
|
return error instanceof NornRunStoppedError || session.activeRun.controller.signal.reason instanceof NornRunStoppedError;
|
|
465
441
|
}
|
|
466
442
|
|
|
467
|
-
function interruptedLaunchResult(run: RunIdentity, workflow: NornAnyWorkflowDeclaration,
|
|
443
|
+
function interruptedLaunchResult(run: RunIdentity, workflow: NornAnyWorkflowDeclaration, args: unknown, interruption: { readonly description?: string; readonly fields?: readonly string[] }): NornInterruptedRunResult {
|
|
468
444
|
if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
|
|
469
445
|
if (!interruption.description) throw new Error(`Interrupted workflow is missing description: ${workflow.id}`);
|
|
470
446
|
return {
|
|
@@ -474,7 +450,7 @@ function interruptedLaunchResult(run: RunIdentity, workflow: NornAnyWorkflowDecl
|
|
|
474
450
|
workspace: run.workspace,
|
|
475
451
|
cwd: run.cwd,
|
|
476
452
|
workflowId: workflow.id,
|
|
477
|
-
interruption: { workflowId: workflow.id,
|
|
453
|
+
interruption: { workflowId: workflow.id, args, description: interruption.description, fields: interruption.fields },
|
|
478
454
|
};
|
|
479
455
|
}
|
|
480
456
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NornWorkflowDiagnostic } from "@vimhead.dev/norn";
|
|
2
2
|
import { AssertError } from "typebox/value";
|
|
3
3
|
|
|
4
4
|
export class NornProjectLoadError extends Error {
|
|
5
5
|
readonly code = "NORN_PROJECT_INVALID";
|
|
6
6
|
readonly isComplete = false;
|
|
7
|
-
readonly diagnostics: readonly
|
|
7
|
+
readonly diagnostics: readonly NornWorkflowDiagnostic[];
|
|
8
8
|
|
|
9
|
-
constructor(input: { readonly diagnostics: readonly
|
|
9
|
+
constructor(input: { readonly diagnostics: readonly NornWorkflowDiagnostic[] }) {
|
|
10
10
|
const details = input.diagnostics.map(diagnostic =>
|
|
11
|
-
`${diagnostic.stage}: ${diagnostic.
|
|
11
|
+
`${diagnostic.stage}: ${diagnostic.modulePath} (declared in ${diagnostic.configPath}${diagnostic.workflowId ? `; workflow ${diagnostic.workflowId}` : ""}): ${diagnostic.message}`,
|
|
12
12
|
);
|
|
13
13
|
super(`Norn project is incomplete (${input.diagnostics.length} diagnostics); execution is blocked.\n${details.join("\n")}`);
|
|
14
14
|
this.name = "NornProjectLoadError";
|
|
@@ -10,22 +10,22 @@ export const RESUME_REQUEST_FILE_NAME = "resume-request.json";
|
|
|
10
10
|
export const RESUME_START_GRACE_MS = 60_000;
|
|
11
11
|
|
|
12
12
|
export type NornRunLaunchRequest = {
|
|
13
|
-
readonly version:
|
|
13
|
+
readonly version: 2;
|
|
14
14
|
readonly type: "run";
|
|
15
15
|
readonly id: string;
|
|
16
16
|
readonly name: string;
|
|
17
17
|
readonly workflowId: string;
|
|
18
|
-
readonly
|
|
18
|
+
readonly args: unknown;
|
|
19
19
|
readonly configOverride?: unknown;
|
|
20
20
|
readonly createdAt: string;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export type NornRunResumeRequest = {
|
|
24
|
-
readonly version:
|
|
24
|
+
readonly version: 2;
|
|
25
25
|
readonly type: "resume";
|
|
26
26
|
readonly id: string;
|
|
27
27
|
readonly requestId?: string;
|
|
28
|
-
readonly
|
|
28
|
+
readonly args?: unknown;
|
|
29
29
|
readonly createdAt: string;
|
|
30
30
|
};
|
|
31
31
|
|
|
@@ -75,7 +75,7 @@ export async function clearRunResumeRequest(input: { readonly runRoot: string; r
|
|
|
75
75
|
function parseRunLaunchRequest(value: unknown): NornRunLaunchRequest {
|
|
76
76
|
if (!value || typeof value !== "object") throw new Error("Invalid workflow launch request");
|
|
77
77
|
const request = value as Partial<NornRunLaunchRequest>;
|
|
78
|
-
if (request.version !==
|
|
78
|
+
if (request.version !== 2 || request.type !== "run") throw new Error("Unsupported workflow launch request");
|
|
79
79
|
if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow launch request id");
|
|
80
80
|
if (typeof request.name !== "string" || request.name.length === 0) throw new Error("Invalid workflow launch request name");
|
|
81
81
|
if (typeof request.workflowId !== "string" || request.workflowId.length === 0) throw new Error("Invalid workflow launch request workflow id");
|
|
@@ -86,7 +86,7 @@ function parseRunLaunchRequest(value: unknown): NornRunLaunchRequest {
|
|
|
86
86
|
function parseRunResumeRequest(value: unknown): NornRunResumeRequest {
|
|
87
87
|
if (!value || typeof value !== "object") throw new Error("Invalid workflow resume request");
|
|
88
88
|
const request = value as Partial<NornRunResumeRequest>;
|
|
89
|
-
if (request.version !==
|
|
89
|
+
if (request.version !== 2 || request.type !== "resume") throw new Error("Unsupported workflow resume request");
|
|
90
90
|
if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow resume request id");
|
|
91
91
|
if (request.requestId !== undefined && (typeof request.requestId !== "string" || request.requestId.length === 0)) throw new Error("Invalid workflow resume request token");
|
|
92
92
|
if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt))) throw new Error("Invalid workflow resume request timestamp");
|
|
@@ -13,19 +13,19 @@ import { Value } from "typebox/value";
|
|
|
13
13
|
export const RUN_STATE_FILE_NAME = "run-state.json";
|
|
14
14
|
const LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
|
|
15
15
|
|
|
16
|
-
export function
|
|
17
|
-
if (!isRecord(
|
|
16
|
+
export function mergeInterruptedWorkflowArgs(currentArgs: unknown, args: unknown, fields: readonly string[] | undefined): unknown {
|
|
17
|
+
if (!isRecord(currentArgs) || !isRecord(args)) return args;
|
|
18
18
|
if (fields) {
|
|
19
|
-
const unsupportedFields = Object.keys(
|
|
20
|
-
if (unsupportedFields.length > 0) throw new Error(`Interrupted workflow
|
|
19
|
+
const unsupportedFields = Object.keys(args).filter((field) => !fields.includes(field));
|
|
20
|
+
if (unsupportedFields.length > 0) throw new Error(`Interrupted workflow args cannot update non-gate fields: ${unsupportedFields.join(", ")}`);
|
|
21
21
|
}
|
|
22
|
-
return { ...
|
|
22
|
+
return { ...currentArgs, ...args };
|
|
23
23
|
}
|
|
24
|
-
const RUN_STATE_VERSION =
|
|
24
|
+
const RUN_STATE_VERSION = 2;
|
|
25
25
|
|
|
26
26
|
type NornRunWorkflowStep = {
|
|
27
27
|
readonly workflowId: string;
|
|
28
|
-
readonly
|
|
28
|
+
readonly args: unknown;
|
|
29
29
|
readonly cwd: string;
|
|
30
30
|
readonly env: Record<string, string>;
|
|
31
31
|
readonly interruption?: RuntimeInterruption;
|
|
@@ -58,7 +58,7 @@ type NornRunWorkflowOutcome = {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export type NornRunState = {
|
|
61
|
-
readonly version: typeof RUN_STATE_VERSION;
|
|
61
|
+
readonly version: 1 | typeof RUN_STATE_VERSION;
|
|
62
62
|
readonly id: string;
|
|
63
63
|
readonly name: string;
|
|
64
64
|
readonly entrypointWorkflowId: string;
|
|
@@ -119,7 +119,7 @@ export class NornRunStateStore {
|
|
|
119
119
|
static async load(runRoot: string): Promise<NornRunStateStore> {
|
|
120
120
|
const currentRoot = runCurrentRoot(runRoot);
|
|
121
121
|
const files = createRunFileCoordinator(runRoot);
|
|
122
|
-
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) })));
|
|
122
|
+
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"));
|
|
123
123
|
return new NornRunStateStore({ path: join(currentRoot, RUN_STATE_FILE_NAME), state, files });
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -163,11 +163,11 @@ export class NornRunStateStore {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
async interruptCurrent(
|
|
166
|
+
async interruptCurrent(args: unknown, interruption: { readonly description: string; readonly fields?: readonly string[] }): Promise<void> {
|
|
167
167
|
if (!this.state.current) throw new Error("Cannot interrupt run without current step");
|
|
168
168
|
await this.update({
|
|
169
169
|
status: "interrupted",
|
|
170
|
-
current: { ...this.state.current,
|
|
170
|
+
current: { ...this.state.current, args, interruption: { status: "pending", ...interruption } },
|
|
171
171
|
outcome: null,
|
|
172
172
|
failed: null,
|
|
173
173
|
});
|
|
@@ -185,11 +185,11 @@ export class NornRunStateStore {
|
|
|
185
185
|
await this.update({ status: "stopped", outcome: null, failed: null });
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async
|
|
188
|
+
async replaceCurrentArgs(args: unknown): Promise<void> {
|
|
189
189
|
if (!this.state.current) throw new Error("Cannot resume run without current step");
|
|
190
190
|
await this.update({
|
|
191
191
|
status: "running",
|
|
192
|
-
current: { ...this.state.current,
|
|
192
|
+
current: { ...this.state.current, args, interruption: { ...this.state.current.interruption, status: "satisfied" } },
|
|
193
193
|
outcome: null,
|
|
194
194
|
failed: null,
|
|
195
195
|
});
|
|
@@ -208,16 +208,16 @@ export class NornRunStateStore {
|
|
|
208
208
|
|
|
209
209
|
private async update(patch: Partial<NornRunState>): Promise<void> {
|
|
210
210
|
await this.files.withExclusiveLock(this.path, async (path) => {
|
|
211
|
-
const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname(this.path), LEGACY_RUN_STATE_FILE_NAME) }));
|
|
211
|
+
const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname(this.path), LEGACY_RUN_STATE_FILE_NAME) }), "execute");
|
|
212
212
|
const next = { ...current, ...patch, updatedAt: new Date().toISOString() };
|
|
213
|
-
if (next.current?.
|
|
213
|
+
if (next.current?.args !== undefined) Value.Assert(jsonValueSchema, next.current.args);
|
|
214
214
|
await writeJsonAtomically(path, next);
|
|
215
215
|
this.state = next;
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
private async write(): Promise<void> {
|
|
220
|
-
if (this.state.current?.
|
|
220
|
+
if (this.state.current?.args !== undefined) Value.Assert(jsonValueSchema, this.state.current.args);
|
|
221
221
|
await mkdir(dirname(this.path), { recursive: true });
|
|
222
222
|
await this.files.withExclusiveLock(this.path, (path) => writeJsonAtomically(path, this.state));
|
|
223
223
|
}
|
|
@@ -239,8 +239,14 @@ export async function listRuns(sessionCwd: string): Promise<NornRunInfo[]> {
|
|
|
239
239
|
|
|
240
240
|
export async function getRunInfo(runRoot: string): Promise<NornRunInfo> {
|
|
241
241
|
try {
|
|
242
|
-
|
|
243
|
-
const
|
|
242
|
+
// Read the request first so a completed handoff cannot revive an old interruption.
|
|
243
|
+
const pendingResume = await readOptionalRunResumeRequest(runRoot).then(
|
|
244
|
+
request => ({ status: "fulfilled" as const, request }),
|
|
245
|
+
(error: unknown) => ({ status: "rejected" as const, error }),
|
|
246
|
+
);
|
|
247
|
+
const state = parseNornRunState(await readRunStateJson(runCurrentRoot(runRoot)), "inspect");
|
|
248
|
+
if (state.version === RUN_STATE_VERSION && pendingResume.status === "rejected") throw pendingResume.error;
|
|
249
|
+
const resumeRequest = state.version === RUN_STATE_VERSION && pendingResume.status === "fulfilled" ? pendingResume.request : undefined;
|
|
244
250
|
return {
|
|
245
251
|
version: state.version,
|
|
246
252
|
id: state.id,
|
|
@@ -337,10 +343,14 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
337
343
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
338
344
|
}
|
|
339
345
|
|
|
340
|
-
function
|
|
346
|
+
export function assertRunVersion(version: unknown): void {
|
|
347
|
+
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.`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function parseNornRunState(value: unknown, mode: "execute" | "inspect"): NornRunState {
|
|
341
351
|
if (!value || typeof value !== "object") throw new Error("Invalid run state");
|
|
342
352
|
const state = value as Partial<NornRunState> & { rootWorkflowId?: unknown };
|
|
343
|
-
if (state.version !==
|
|
353
|
+
if (mode === "execute" || state.version !== 1) assertRunVersion(state.version);
|
|
344
354
|
if (typeof state.id !== "string" || state.id.length === 0) throw new Error("Invalid run state id");
|
|
345
355
|
const normalizedState = state as { name?: string; entrypointWorkflowId?: unknown; rootWorkflowId?: unknown };
|
|
346
356
|
if (typeof normalizedState.name !== "string" || normalizedState.name.length === 0) normalizedState.name = state.id;
|
|
@@ -369,7 +379,7 @@ function runInterruption(state: NornRunState): NornRunInterruption | undefined {
|
|
|
369
379
|
if (state.status !== "interrupted" || !state.current?.interruption) return undefined;
|
|
370
380
|
return {
|
|
371
381
|
workflowId: state.current.workflowId,
|
|
372
|
-
params: state.current.
|
|
382
|
+
args: state.version === 1 ? (state.current as { readonly params?: unknown }).params : state.current.args,
|
|
373
383
|
description: state.current.interruption.description ?? "",
|
|
374
384
|
fields: state.current.interruption.fields,
|
|
375
385
|
};
|
|
@@ -23,7 +23,6 @@ type DefaultNornRunInput = {
|
|
|
23
23
|
readonly agentDir?: string;
|
|
24
24
|
readonly responseCollector: NornAgentResponseCollector;
|
|
25
25
|
readonly resources: NornRunResources;
|
|
26
|
-
readonly state: NornRun["state"];
|
|
27
26
|
readonly logger: NornRunLogger;
|
|
28
27
|
readonly artifacts: NornArtifacts;
|
|
29
28
|
readonly logs: NornRunLogs;
|
|
@@ -31,7 +30,6 @@ type DefaultNornRunInput = {
|
|
|
31
30
|
|
|
32
31
|
export class NornRunContext implements NornProjectRun {
|
|
33
32
|
readonly resources: NornRunResources;
|
|
34
|
-
readonly state: NornRun["state"];
|
|
35
33
|
readonly artifacts: NornRun["artifacts"];
|
|
36
34
|
readonly logs: NornRun["logs"];
|
|
37
35
|
readonly commands: NornRun["commands"];
|
|
@@ -47,7 +45,6 @@ export class NornRunContext implements NornProjectRun {
|
|
|
47
45
|
this.projectRoot = input.projectRoot;
|
|
48
46
|
this.cwd = input.cwd;
|
|
49
47
|
this.resources = input.resources;
|
|
50
|
-
this.state = input.state;
|
|
51
48
|
this.artifacts = input.artifacts;
|
|
52
49
|
this.logs = {
|
|
53
50
|
read: (log) => this.input.logs.read(log),
|
|
@@ -107,8 +104,8 @@ export class NornRunContext implements NornProjectRun {
|
|
|
107
104
|
return this.resolveFromRoot(this.projectRoot, this.projectRoot, relativePath);
|
|
108
105
|
}
|
|
109
106
|
|
|
110
|
-
next(workflowId: string,
|
|
111
|
-
return createWorkflowTransition({ workflowId,
|
|
107
|
+
next(workflowId: string, args: unknown): ReturnType<NornRun["next"]> {
|
|
108
|
+
return createWorkflowTransition({ workflowId, args });
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
complete(metadata?: NornRunOutcomeMetadata): ReturnType<NornRun["complete"]> {
|