@vimhead.dev/norn-cli 0.1.0-tip.35359392805.1 → 0.1.0-tip.35436871363.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 +22 -27
- package/assets/docs/README.md +5 -5
- package/assets/docs/agents.md +48 -9
- package/assets/docs/cli.md +7 -7
- package/assets/docs/composition.md +25 -25
- package/assets/docs/persistence.md +34 -23
- package/assets/docs/projects.md +30 -18
- package/assets/docs/providers.md +3 -3
- package/assets/docs/recovery.md +13 -9
- package/assets/docs/schemas.md +1 -1
- package/assets/docs/workflows.md +105 -12
- package/assets/examples/agent-then-analysis/README.md +10 -10
- 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 +57 -68
- package/assets/examples/caller-selected-continuation/README.md +15 -14
- package/assets/examples/caller-selected-continuation/caller.ts +37 -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 +19 -24
- package/assets/examples/coordinating-multiple-agents/README.md +27 -17
- 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 +79 -60
- package/assets/examples/coordinating-multiple-agents/queue-tools.ts +43 -0
- package/assets/examples/coordinating-multiple-agents/work-queue.ts +60 -65
- package/assets/examples/getting-started/README.md +2 -2
- package/assets/examples/getting-started/norn.project.json +1 -1
- package/assets/examples/getting-started/plugin.ts +20 -26
- package/assets/examples/minimal-workflow/README.md +13 -12
- package/assets/examples/minimal-workflow/norn.project.json +1 -1
- package/assets/examples/minimal-workflow/plugin.ts +14 -25
- package/assets/examples/shared-state/README.md +12 -4
- 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 +49 -41
- package/assets/examples/shared-state/shared-state.ts +56 -0
- package/assets/examples/shared-state/state-tools.ts +68 -0
- package/assets/examples/worktree-development-loop/README.md +18 -17
- 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/repository.ts +5 -3
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +35 -33
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +6 -3
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +27 -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 +43 -31
- package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +6 -6
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +50 -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 +5 -5
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/cli.ts +44 -37
- 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/agents.ts +24 -32
- package/assets/packages/cli/src/internal/commands.ts +2 -14
- package/assets/packages/cli/src/internal/engine.ts +63 -101
- 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/logs.ts +1 -1
- package/assets/packages/cli/src/internal/run-log.ts +1 -1
- package/assets/packages/cli/src/internal/run-state.ts +38 -23
- package/assets/packages/cli/src/internal/run.ts +4 -62
- package/assets/packages/cli/src/internal/worker-directory.ts +17 -0
- package/assets/packages/cli/src/internal/workflow-registry.ts +126 -111
- package/assets/packages/cli/src/internal/working-directory.ts +6 -0
- package/assets/packages/cli/src/workflow-loader.ts +213 -0
- package/assets/packages/core/src/workflow-transition.ts +2 -2
- package/assets/packages/sdk/src/api.ts +141 -329
- package/assets/packages/sdk/src/index.ts +1 -4
- package/assets/packages/sdk/src/schema.ts +3 -3
- package/assets/tests/workflow-ref.test.ts +53 -55
- package/dist/cli.js +41 -35
- 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/agents.d.ts +0 -4
- package/dist/internal/agents.js +30 -46
- package/dist/internal/commands.d.ts +0 -4
- package/dist/internal/commands.js +2 -10
- package/dist/internal/engine.d.ts +4 -8
- package/dist/internal/engine.js +57 -84
- package/dist/internal/errors.d.ts +3 -3
- package/dist/internal/errors.js +1 -1
- package/dist/internal/file-coordinator.d.ts +17 -0
- package/dist/internal/file-coordinator.js +162 -0
- package/dist/internal/launch-request.d.ts +4 -4
- package/dist/internal/launch-request.js +2 -2
- package/dist/internal/logs.d.ts +1 -1
- package/dist/internal/run-log.d.ts +1 -1
- package/dist/internal/run-state.d.ts +11 -8
- package/dist/internal/run-state.js +34 -22
- package/dist/internal/run.d.ts +3 -23
- package/dist/internal/run.js +4 -50
- package/dist/internal/worker-directory.d.ts +1 -0
- package/dist/internal/worker-directory.js +26 -0
- package/dist/internal/workflow-registry.d.ts +35 -17
- package/dist/internal/workflow-registry.js +110 -73
- package/dist/internal/working-directory.d.ts +1 -0
- package/dist/internal/working-directory.js +9 -0
- package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
- package/dist/workflow-loader.js +234 -0
- package/package.json +2 -2
- package/assets/docs/resources.md +0 -61
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +0 -51
- 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/artifacts.ts +0 -26
- package/assets/packages/cli/src/internal/resource-bindings.ts +0 -35
- 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/assets/packages/cli/src/resources.ts +0 -69
- package/assets/packages/sdk/src/agent-resource-adapter.ts +0 -11
- package/assets/packages/sdk/src/resources.ts +0 -20
- package/assets/packages/sdk/src/state-adapter.ts +0 -75
- package/dist/internal/artifacts.d.ts +0 -10
- package/dist/internal/artifacts.js +0 -29
- package/dist/internal/resource-bindings.d.ts +0 -13
- package/dist/internal/resource-bindings.js +0 -34
- 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
- package/dist/resources.d.ts +0 -11
- package/dist/resources.js +0 -100
- /package/assets/packages/{sdk/src/files.ts → cli/src/internal/file-coordinator.ts} +0 -0
package/dist/internal/agents.js
CHANGED
|
@@ -14,17 +14,16 @@ import { inspectSchema } from "@vimhead.dev/norn/schema";
|
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { mkdir } from "node:fs/promises";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
|
-
import {
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { requireAbsoluteWorkingDirectory } from "./working-directory.js";
|
|
18
19
|
import { resolveNornAgentDirectory } from "./agent-directory.js";
|
|
19
20
|
import {
|
|
20
21
|
NornAgentResponseToolFactory
|
|
21
22
|
} from "./agent-response-tool.js";
|
|
22
23
|
import { errorMessage } from "./errors.js";
|
|
23
24
|
import { safeFileName } from "./file-names.js";
|
|
24
|
-
import { NornSessionResourceBindings } from "./resource-bindings.js";
|
|
25
25
|
import { agentUsageFromValue, emptyAgentUsage, totalAgentUsage } from "./usage.js";
|
|
26
26
|
var DEFAULT_AGENT_ATTEMPTS = 3;
|
|
27
|
-
var DEFAULT_AGENT_TOOL_ALLOWLIST = ["read", "bash", "edit", "write", AGENT_RESPONSE_TOOL_NAME];
|
|
28
27
|
var NornAgentRunner = class {
|
|
29
28
|
constructor(input) {
|
|
30
29
|
this.input = input;
|
|
@@ -33,7 +32,7 @@ var NornAgentRunner = class {
|
|
|
33
32
|
input;
|
|
34
33
|
responseToolFactory;
|
|
35
34
|
async createSession(agentInput) {
|
|
36
|
-
const cwd =
|
|
35
|
+
const cwd = requireAbsoluteWorkingDirectory(agentInput.cwd);
|
|
37
36
|
const sessionDir = resolve(this.input.runRoot, "sessions");
|
|
38
37
|
await mkdir(sessionDir, { recursive: true });
|
|
39
38
|
const eventBus = createEventBus();
|
|
@@ -53,31 +52,29 @@ var NornAgentRunner = class {
|
|
|
53
52
|
...loader.getExtensions().errors.map((error) => `Failed to load extension ${error.path}: ${error.error}`)
|
|
54
53
|
];
|
|
55
54
|
if (errors.length > 0) throw new Error(errors.join("\n"));
|
|
56
|
-
const
|
|
55
|
+
const customTools = agentInput.customTools ?? [];
|
|
56
|
+
assertCustomToolNames({
|
|
57
|
+
tools: customTools,
|
|
58
|
+
reservedNames: [
|
|
59
|
+
"read",
|
|
60
|
+
"bash",
|
|
61
|
+
"edit",
|
|
62
|
+
"write",
|
|
63
|
+
"grep",
|
|
64
|
+
"find",
|
|
65
|
+
"ls",
|
|
66
|
+
"powershell",
|
|
67
|
+
AGENT_RESPONSE_TOOL_NAME,
|
|
68
|
+
...loader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()])
|
|
69
|
+
]
|
|
70
|
+
});
|
|
57
71
|
let session;
|
|
58
72
|
try {
|
|
59
|
-
await resourceBindings.bind({
|
|
60
|
-
adapters: agentInput.resourceAdapters ?? [],
|
|
61
|
-
runId: this.input.id,
|
|
62
|
-
label: agentInput.label,
|
|
63
|
-
reservedTools: [
|
|
64
|
-
"read",
|
|
65
|
-
"bash",
|
|
66
|
-
"edit",
|
|
67
|
-
"write",
|
|
68
|
-
"grep",
|
|
69
|
-
"find",
|
|
70
|
-
"ls",
|
|
71
|
-
"powershell",
|
|
72
|
-
AGENT_RESPONSE_TOOL_NAME,
|
|
73
|
-
...loader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()])
|
|
74
|
-
]
|
|
75
|
-
});
|
|
76
73
|
({ session } = await createAgentSessionFromServices({
|
|
77
74
|
services,
|
|
78
75
|
sessionManager: SessionManager.create(cwd, sessionDir),
|
|
79
|
-
tools:
|
|
80
|
-
customTools: [this.responseToolFactory.create(), ...
|
|
76
|
+
tools: withAgentResponseTool(agentInput.tools),
|
|
77
|
+
customTools: [this.responseToolFactory.create(), ...customTools],
|
|
81
78
|
model: agentInput.model ?? this.input.model,
|
|
82
79
|
thinkingLevel: agentInput.thinkingLevel ?? this.input.thinkingLevel
|
|
83
80
|
}));
|
|
@@ -89,7 +86,6 @@ var NornAgentRunner = class {
|
|
|
89
86
|
label: agentInput.label,
|
|
90
87
|
cwd,
|
|
91
88
|
session,
|
|
92
|
-
resourceBindings,
|
|
93
89
|
events: eventBus
|
|
94
90
|
});
|
|
95
91
|
} catch (error) {
|
|
@@ -99,12 +95,7 @@ var NornAgentRunner = class {
|
|
|
99
95
|
} catch (cleanupError) {
|
|
100
96
|
errors2.push(cleanupError);
|
|
101
97
|
}
|
|
102
|
-
|
|
103
|
-
await resourceBindings.dispose();
|
|
104
|
-
} catch (cleanupError) {
|
|
105
|
-
errors2.push(cleanupError);
|
|
106
|
-
}
|
|
107
|
-
if (errors2.length > 1) throw new AggregateError(errors2, "Agent creation and resource cleanup failed");
|
|
98
|
+
if (errors2.length > 1) throw new AggregateError(errors2, "Agent creation and cleanup failed");
|
|
108
99
|
throw error;
|
|
109
100
|
}
|
|
110
101
|
}
|
|
@@ -116,14 +107,6 @@ var NornAgentRunner = class {
|
|
|
116
107
|
await agent.dispose();
|
|
117
108
|
}
|
|
118
109
|
}
|
|
119
|
-
resolveFromCwd(path) {
|
|
120
|
-
const resolvedPath = isAbsolute(path) ? path : resolve(this.input.cwd, path);
|
|
121
|
-
const pathFromBoundary = relative(this.input.boundaryRoot, resolvedPath);
|
|
122
|
-
if (pathFromBoundary === ".." || pathFromBoundary.startsWith(`..${sep}`) || isAbsolute(pathFromBoundary)) {
|
|
123
|
-
throw new Error(`Agent cwd escapes ${this.input.boundaryName} isolation: ${path}`);
|
|
124
|
-
}
|
|
125
|
-
return resolvedPath;
|
|
126
|
-
}
|
|
127
110
|
};
|
|
128
111
|
function systemPromptOverride(agentInput) {
|
|
129
112
|
const systemPrompt = agentInput.systemPrompt;
|
|
@@ -223,11 +206,6 @@ var CreatedNornAgentSession = class {
|
|
|
223
206
|
} catch (error) {
|
|
224
207
|
errors.push(error);
|
|
225
208
|
}
|
|
226
|
-
try {
|
|
227
|
-
await this.input.resourceBindings.dispose();
|
|
228
|
-
} catch (error) {
|
|
229
|
-
errors.push(error);
|
|
230
|
-
}
|
|
231
209
|
if (errors.length) throw new AggregateError(errors, "Agent disposal failed");
|
|
232
210
|
await this.input.logger.record({ type: "agent.disposed", label: this.label });
|
|
233
211
|
}
|
|
@@ -328,8 +306,14 @@ function withResponseToolFinalizationInstruction(responseSchema, label, response
|
|
|
328
306
|
].join("\n");
|
|
329
307
|
}
|
|
330
308
|
function withAgentResponseTool(tools) {
|
|
331
|
-
|
|
332
|
-
|
|
309
|
+
return tools === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([...tools, AGENT_RESPONSE_TOOL_NAME]));
|
|
310
|
+
}
|
|
311
|
+
function assertCustomToolNames(input) {
|
|
312
|
+
const names = new Set(input.reservedNames);
|
|
313
|
+
for (const tool of input.tools) {
|
|
314
|
+
if (names.has(tool.name)) throw new Error(`Custom tool name collision: ${tool.name}`);
|
|
315
|
+
names.add(tool.name);
|
|
316
|
+
}
|
|
333
317
|
}
|
|
334
318
|
export {
|
|
335
319
|
NornAgentRunner
|
|
@@ -2,9 +2,6 @@ import type { NornCommandRunInput, NornCommandRunResult } from "@vimhead.dev/nor
|
|
|
2
2
|
import type { NornRunLogs } from "./logs.ts";
|
|
3
3
|
import type { NornRunLogger } from "./run-log.ts";
|
|
4
4
|
type NornCommandRunnerInput = {
|
|
5
|
-
readonly boundaryRoot: string;
|
|
6
|
-
readonly boundaryName: string;
|
|
7
|
-
readonly cwd: string;
|
|
8
5
|
readonly signal?: AbortSignal;
|
|
9
6
|
readonly logs: NornRunLogs;
|
|
10
7
|
readonly logger: NornRunLogger;
|
|
@@ -13,6 +10,5 @@ export declare class NornCommandRunner {
|
|
|
13
10
|
private readonly input;
|
|
14
11
|
constructor(input: NornCommandRunnerInput);
|
|
15
12
|
run(commandInput: NornCommandRunInput): Promise<NornCommandRunResult>;
|
|
16
|
-
private resolveFromCwd;
|
|
17
13
|
}
|
|
18
14
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/internal/commands.ts
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { requireAbsoluteWorkingDirectory } from "./working-directory.js";
|
|
5
5
|
import { errorMessage } from "./errors.js";
|
|
6
6
|
import { safeFileName } from "./file-names.js";
|
|
7
7
|
var NornCommandRunner = class {
|
|
@@ -10,7 +10,7 @@ var NornCommandRunner = class {
|
|
|
10
10
|
}
|
|
11
11
|
input;
|
|
12
12
|
async run(commandInput) {
|
|
13
|
-
const cwd =
|
|
13
|
+
const cwd = requireAbsoluteWorkingDirectory(commandInput.cwd);
|
|
14
14
|
const startedAtMs = Date.now();
|
|
15
15
|
const invocationId = randomUUID();
|
|
16
16
|
const stdoutRef = commandLog({ label: commandInput.label, invocationId, stream: "stdout" });
|
|
@@ -56,14 +56,6 @@ var NornCommandRunner = class {
|
|
|
56
56
|
});
|
|
57
57
|
return commandResult;
|
|
58
58
|
}
|
|
59
|
-
resolveFromCwd(path) {
|
|
60
|
-
const resolvedPath = isAbsolute(path) ? path : resolve(this.input.cwd, path);
|
|
61
|
-
const pathFromBoundary = relative(this.input.boundaryRoot, resolvedPath);
|
|
62
|
-
if (pathFromBoundary === ".." || pathFromBoundary.startsWith(`..${sep}`) || isAbsolute(pathFromBoundary)) {
|
|
63
|
-
throw new Error(`Command cwd escapes ${this.input.boundaryName} isolation: ${path}`);
|
|
64
|
-
}
|
|
65
|
-
return resolvedPath;
|
|
66
|
-
}
|
|
67
59
|
};
|
|
68
60
|
async function spawnCommand(input) {
|
|
69
61
|
const command = typeof input.command === "string" ? "bash" : input.command[0];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NornAnyWorkflowDeclaration, type
|
|
1
|
+
import { type NornAnyWorkflowDeclaration, type NornDispose, type NornRunCheckpoint, type NornRunInfo, type NornRunResult, type NornRunStartOptions } from "@vimhead.dev/norn";
|
|
2
2
|
import { NornAgentResponseCollector } from "./agent-response-tool.ts";
|
|
3
3
|
import { type NornRunResumeRequest } from "./launch-request.ts";
|
|
4
4
|
import { type NornRegisteredWorkflow } from "./workflow-registry.ts";
|
|
@@ -13,19 +13,16 @@ export type NornEngineInput = {
|
|
|
13
13
|
export declare class NornEngine {
|
|
14
14
|
private readonly input;
|
|
15
15
|
private readonly registry;
|
|
16
|
-
private readonly registrarState;
|
|
17
|
-
private readonly disposersByPlugin;
|
|
18
16
|
private readonly responseCollector;
|
|
19
17
|
private readonly activeRuns;
|
|
20
18
|
constructor(input: NornEngineInput);
|
|
21
|
-
|
|
19
|
+
registerWorkflows(workflows: readonly NornAnyWorkflowDeclaration[]): NornDispose;
|
|
22
20
|
listWorkflows(): import("@vimhead.dev/norn").NornRegisteredWorkflowInfo[];
|
|
23
21
|
visibleWorkflowEntries(): NornRegisteredWorkflow[];
|
|
24
|
-
|
|
25
|
-
runWorkflow<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, params: unknown, options: NornRunStartOptions | undefined): Promise<NornRunResult>;
|
|
22
|
+
runWorkflow<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, args: unknown, options: NornRunStartOptions | undefined): Promise<NornRunResult>;
|
|
26
23
|
listRunCheckpoints(path: string): Promise<NornRunCheckpoint[]>;
|
|
27
24
|
rollbackRun(path: string, checkpointId: string): Promise<NornRunInfo>;
|
|
28
|
-
resumeWorkflow(path: string,
|
|
25
|
+
resumeWorkflow(path: string, args?: unknown): Promise<NornRunResult>;
|
|
29
26
|
resumeRequestedWorkflow(input: {
|
|
30
27
|
readonly runRoot: string;
|
|
31
28
|
readonly request: NornRunResumeRequest;
|
|
@@ -40,5 +37,4 @@ export declare class NornEngine {
|
|
|
40
37
|
private startActiveRun;
|
|
41
38
|
private finishActiveRun;
|
|
42
39
|
private failActiveRun;
|
|
43
|
-
private disposePlugin;
|
|
44
40
|
}
|
package/dist/internal/engine.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
// src/internal/engine.ts
|
|
2
|
-
import { createRunFileCoordinator } from "
|
|
2
|
+
import { createRunFileCoordinator } from "./file-coordinator.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { mkdir } from "node:fs/promises";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { Value } from "typebox/value";
|
|
7
7
|
import { NornAgentResponseCollector } from "./agent-response-tool.js";
|
|
8
|
-
import { NornArtifacts } from "./artifacts.js";
|
|
9
8
|
import { errorMessage, NornRunStoppedError } from "./errors.js";
|
|
10
9
|
import { clearRunResumeRequest, matchesRunResumeRequest, readOptionalRunResumeRequest, RESUME_START_GRACE_MS } from "./launch-request.js";
|
|
11
10
|
import { NornRunLogs } from "./logs.js";
|
|
12
11
|
import { NornRunLease } from "./run-lease.js";
|
|
13
12
|
import { NornRunLogger } from "./run-log.js";
|
|
14
|
-
import {
|
|
15
|
-
import { getRunInfo, mergeInterruptedWorkflowParams, NornRunStateStore, resolveRunRoot } from "./run-state.js";
|
|
13
|
+
import { assertRunVersion, getRunInfo, mergeInterruptedWorkflowArgs, NornRunStateStore, resolveRunRoot } from "./run-state.js";
|
|
16
14
|
import { NornRunStore, runCurrentRoot } from "./run-store.js";
|
|
17
15
|
import { NornRunContext } from "./run.js";
|
|
18
|
-
import {
|
|
19
|
-
import { decodePluginConfiguration, NornWorkflowRegistry } from "./workflow-registry.js";
|
|
16
|
+
import { NornWorkflowRegistry } from "./workflow-registry.js";
|
|
20
17
|
var RUNS_DIR_NAME = ".norn";
|
|
21
18
|
var MANIFEST_FILE_NAME = "manifest.json";
|
|
22
19
|
var NornEngine = class {
|
|
@@ -26,22 +23,18 @@ var NornEngine = class {
|
|
|
26
23
|
}
|
|
27
24
|
input;
|
|
28
25
|
registry = new NornWorkflowRegistry();
|
|
29
|
-
registrarState = new NornMemoryWorkflowState();
|
|
30
|
-
disposersByPlugin = /* @__PURE__ */ new Map();
|
|
31
26
|
responseCollector;
|
|
32
27
|
activeRuns = /* @__PURE__ */ new Map();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
this.disposersByPlugin.set(plugin.manifest.id, disposers);
|
|
28
|
+
registerWorkflows(workflows) {
|
|
29
|
+
const disposers = [];
|
|
30
|
+
try {
|
|
31
|
+
for (const workflow of workflows) disposers.push(this.registry.register({ workflow, config: this.input.config ?? {}, source: void 0 }));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
for (const dispose of disposers.reverse()) dispose();
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
43
36
|
return () => {
|
|
44
|
-
|
|
37
|
+
for (const dispose of disposers) dispose();
|
|
45
38
|
};
|
|
46
39
|
}
|
|
47
40
|
listWorkflows() {
|
|
@@ -50,11 +43,8 @@ var NornEngine = class {
|
|
|
50
43
|
visibleWorkflowEntries() {
|
|
51
44
|
return this.registry.launchableEntries();
|
|
52
45
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
async runWorkflow(workflow, params, options) {
|
|
57
|
-
const session = await this.createRunSession(workflow, params, options);
|
|
46
|
+
async runWorkflow(workflow, args, options) {
|
|
47
|
+
const session = await this.createRunSession(workflow, args, options);
|
|
58
48
|
const current = session.state.currentState().current;
|
|
59
49
|
if (!current) throw new Error("New run is missing its initial workflow step");
|
|
60
50
|
return this.runScheduler(session, toWorkflowStep(workflow, current));
|
|
@@ -65,6 +55,7 @@ var NornEngine = class {
|
|
|
65
55
|
}
|
|
66
56
|
async rollbackRun(path, checkpointId) {
|
|
67
57
|
const runRoot = await resolveRunRoot(this.input.cwd, path);
|
|
58
|
+
assertRunVersion((await getRunInfo(runRoot)).version);
|
|
68
59
|
const lease = await NornRunLease.acquire(runRoot);
|
|
69
60
|
try {
|
|
70
61
|
const pendingRequest = await readOptionalRunResumeRequest(runRoot);
|
|
@@ -80,19 +71,19 @@ var NornEngine = class {
|
|
|
80
71
|
await lease.release();
|
|
81
72
|
}
|
|
82
73
|
}
|
|
83
|
-
async resumeWorkflow(path,
|
|
84
|
-
return this.resumeWorkflowExecution({ path,
|
|
74
|
+
async resumeWorkflow(path, args) {
|
|
75
|
+
return this.resumeWorkflowExecution({ path, args, expectedRequest: void 0 });
|
|
85
76
|
}
|
|
86
77
|
async resumeRequestedWorkflow(input) {
|
|
87
|
-
return this.resumeWorkflowExecution({ path: input.runRoot,
|
|
78
|
+
return this.resumeWorkflowExecution({ path: input.runRoot, args: input.request.args, expectedRequest: input.request });
|
|
88
79
|
}
|
|
89
80
|
async resumeWorkflowExecution(input) {
|
|
90
|
-
const {
|
|
81
|
+
const { args, expectedRequest } = input;
|
|
91
82
|
const runRoot = await resolveRunRoot(this.input.cwd, input.path);
|
|
92
83
|
const initialStateStore = await NornRunStateStore.load(runRoot);
|
|
93
84
|
const initialState = initialStateStore.currentState();
|
|
94
|
-
if (initialState.status === "interrupted" &&
|
|
95
|
-
if (initialState.status === "pendingResume" &&
|
|
85
|
+
if (initialState.status === "interrupted" && args === void 0) throw new Error(`Interrupted workflow resume requires args: ${runRoot}`);
|
|
86
|
+
if (initialState.status === "pendingResume" && args !== void 0) throw new Error(`Pending-resume workflows do not accept args: ${runRoot}`);
|
|
96
87
|
if (initialState.status !== "interrupted" && initialState.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
|
|
97
88
|
const lease = await NornRunLease.acquire(runRoot);
|
|
98
89
|
let isLeaseOwnedByScheduler = false;
|
|
@@ -107,17 +98,17 @@ var NornEngine = class {
|
|
|
107
98
|
const workflow = this.registry.workflowById(current.workflowId);
|
|
108
99
|
if (!workflow) throw new Error(`Unknown workflow for resumed run: ${current.workflowId}`);
|
|
109
100
|
if (state.status === "interrupted") {
|
|
110
|
-
const
|
|
111
|
-
Value.Decode(workflow.
|
|
112
|
-
await session.state.
|
|
113
|
-
await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.
|
|
101
|
+
const rawArgs = mergeInterruptedWorkflowArgs(current.args, args, current.interruption?.fields);
|
|
102
|
+
Value.Decode(workflow.args, rawArgs);
|
|
103
|
+
await session.state.replaceCurrentArgs(rawArgs);
|
|
104
|
+
await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.cwd });
|
|
114
105
|
const resumedCurrent = session.state.currentState().current;
|
|
115
106
|
if (!resumedCurrent) throw new Error(`Run is not resumable: ${runRoot}`);
|
|
116
107
|
isLeaseOwnedByScheduler = true;
|
|
117
108
|
return this.runScheduler(session, toWorkflowStep(workflow, resumedCurrent));
|
|
118
109
|
}
|
|
119
110
|
if (state.status !== "pendingResume") throw new Error(`Run must be rolled back before resuming: ${runRoot}`);
|
|
120
|
-
await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.
|
|
111
|
+
await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.cwd });
|
|
121
112
|
isLeaseOwnedByScheduler = true;
|
|
122
113
|
return this.runScheduler(session, toWorkflowStep(workflow, current));
|
|
123
114
|
} finally {
|
|
@@ -135,28 +126,27 @@ var NornEngine = class {
|
|
|
135
126
|
try {
|
|
136
127
|
for (let step = 1; step <= 1e3; step++) {
|
|
137
128
|
throwIfRunAborted(session.activeRun);
|
|
138
|
-
const stepRuntime = session.run.forWorkflow(currentStep.workflow);
|
|
139
129
|
if (shouldPauseForGate(currentStep)) {
|
|
140
|
-
const description = await this.registry.describeGate(currentStep.workflow,
|
|
130
|
+
const description = await this.registry.describeGate({ workflow: currentStep.workflow, run: session.run, paths: session.paths, args: currentStep.args, configOverride: session.state.currentState().configOverride });
|
|
141
131
|
const interruption = { description, fields: currentStep.workflow.gate?.fields };
|
|
142
|
-
await session.state.interruptCurrent(currentStep.
|
|
132
|
+
await session.state.interruptCurrent(currentStep.args, interruption);
|
|
143
133
|
await recordRunEvent(session, { type: "run.interrupted", workflowId: currentStep.workflow.id });
|
|
144
134
|
await commitRunBoundary(session, `run interrupted: ${currentStep.workflow.id}`);
|
|
145
|
-
return interruptedLaunchResult({ id:
|
|
135
|
+
return interruptedLaunchResult({ id: session.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd }, currentStep.workflow, currentStep.args, interruption);
|
|
146
136
|
}
|
|
147
137
|
await session.state.startStep(toRunStateStep(currentStep));
|
|
148
|
-
const stepResult = await this.executeWorkflowStep(session, currentStep
|
|
138
|
+
const stepResult = await this.executeWorkflowStep(session, currentStep);
|
|
149
139
|
throwIfRunAborted(session.activeRun);
|
|
150
140
|
if (stepResult.type === "complete") {
|
|
151
141
|
await session.state.completeRun(stepResult.workflow.id, stepResult.metadata);
|
|
152
142
|
await recordRunEvent(session, { type: "run.completed", workflowId: stepResult.workflow.id, metadata: stepResult.metadata });
|
|
153
143
|
await commitRunBoundary(session, `run completed: ${stepResult.workflow.id}`);
|
|
154
|
-
return { status: "completed", id:
|
|
144
|
+
return { status: "completed", id: session.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: stepResult.workflow.id, metadata: stepResult.metadata };
|
|
155
145
|
}
|
|
156
146
|
if (stepResult.type === "fail") {
|
|
157
147
|
await session.state.failRun(stepResult.workflow.id, stepResult.metadata);
|
|
158
148
|
await recordRunEvent(session, { type: "run.failed", workflowId: stepResult.workflow.id, metadata: stepResult.metadata });
|
|
159
|
-
return { status: "failed", id:
|
|
149
|
+
return { status: "failed", id: session.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: stepResult.workflow.id, metadata: stepResult.metadata };
|
|
160
150
|
}
|
|
161
151
|
const nextStep = this.nextWorkflowStep(stepResult);
|
|
162
152
|
await session.state.completeWithNext(currentStep.workflow.id, toRunStateStep(nextStep));
|
|
@@ -169,7 +159,7 @@ var NornEngine = class {
|
|
|
169
159
|
if (isRunStopped(session, error)) {
|
|
170
160
|
await session.state.stopCurrent();
|
|
171
161
|
await recordRunEvent(session, { type: "run.stopped", workflowId: currentStep.workflow.id });
|
|
172
|
-
return { status: "stopped", id: session.run.id, name: session.state.currentState().name, workspace: session.
|
|
162
|
+
return { status: "stopped", id: session.run.id, name: session.state.currentState().name, workspace: session.paths.workspace, cwd: session.cwd, workflowId: currentStep.workflow.id };
|
|
173
163
|
}
|
|
174
164
|
await session.state.failCurrent(errorMessage(error));
|
|
175
165
|
await recordRunEvent(session, { type: "run.failed", workflowId: currentStep.workflow.id, error: errorMessage(error) });
|
|
@@ -179,13 +169,13 @@ var NornEngine = class {
|
|
|
179
169
|
this.finishActiveRun(session.runRoot, session.activeRun);
|
|
180
170
|
}
|
|
181
171
|
}
|
|
182
|
-
async executeWorkflowStep(session, step
|
|
172
|
+
async executeWorkflowStep(session, step) {
|
|
183
173
|
const startedAtMs = Date.now();
|
|
184
174
|
try {
|
|
185
|
-
await assertWorkspaceBoundary(session,
|
|
175
|
+
await assertWorkspaceBoundary(session, session.paths.workspace);
|
|
186
176
|
await recordRunEvent(session, { type: "workflow.started", workflowId: step.workflow.id });
|
|
187
|
-
const result = await this.registry.execute(step.workflow, run, step.
|
|
188
|
-
await assertWorkspaceBoundary(session,
|
|
177
|
+
const result = await this.registry.execute({ workflow: step.workflow, run: session.run, paths: session.paths, args: step.args, configOverride: session.state.currentState().configOverride });
|
|
178
|
+
await assertWorkspaceBoundary(session, session.paths.workspace);
|
|
189
179
|
const durationMs = Date.now() - startedAtMs;
|
|
190
180
|
if (result.type === "complete") await recordRunEvent(session, { type: "workflow.completed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
|
|
191
181
|
else if (result.type === "fail") await recordRunEvent(session, { type: "workflow.failed", workflowId: result.workflow.id, durationMs, metadata: result.metadata });
|
|
@@ -206,17 +196,18 @@ var NornEngine = class {
|
|
|
206
196
|
if (!workflow) throw new Error(`Unknown next workflow: ${next.workflowId}`);
|
|
207
197
|
return {
|
|
208
198
|
workflow,
|
|
209
|
-
|
|
199
|
+
args: next.args,
|
|
210
200
|
interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : void 0
|
|
211
201
|
};
|
|
212
202
|
}
|
|
213
|
-
async createRunSession(workflow,
|
|
203
|
+
async createRunSession(workflow, args, options) {
|
|
214
204
|
const id = options?.id ?? randomUUID();
|
|
215
205
|
const name = options?.name ?? id;
|
|
216
206
|
const runRoot = defaultRunRoot(this.input.cwd, id);
|
|
217
207
|
const currentRoot = runCurrentRoot(runRoot);
|
|
218
208
|
const workspace = join(currentRoot, "workspace");
|
|
219
|
-
const
|
|
209
|
+
const paths = { project: resolve(this.input.cwd), workspace };
|
|
210
|
+
const cwd = process.cwd();
|
|
220
211
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
221
212
|
await mkdir(runRoot, { recursive: true });
|
|
222
213
|
const lease = await NornRunLease.acquire(runRoot);
|
|
@@ -233,8 +224,9 @@ var NornEngine = class {
|
|
|
233
224
|
initialCwd: cwd,
|
|
234
225
|
startedAt
|
|
235
226
|
} });
|
|
236
|
-
const run = await this.buildRun({ id, currentRoot,
|
|
227
|
+
const run = await this.buildRun({ id, currentRoot, paths, signal: activeRun.controller.signal, logger });
|
|
237
228
|
const state = await NornRunStateStore.create(runRoot, {
|
|
229
|
+
projectRoot: paths.project,
|
|
238
230
|
id,
|
|
239
231
|
name,
|
|
240
232
|
entrypointWorkflowId: workflow.id,
|
|
@@ -242,14 +234,14 @@ var NornEngine = class {
|
|
|
242
234
|
configOverride: options?.configOverride,
|
|
243
235
|
current: {
|
|
244
236
|
workflowId: workflow.id,
|
|
245
|
-
|
|
237
|
+
args,
|
|
246
238
|
cwd,
|
|
247
239
|
env: {},
|
|
248
240
|
interruption: this.input.gateMode === "pause" && workflow.gate ? { status: "pending" } : void 0
|
|
249
241
|
},
|
|
250
242
|
startedAt
|
|
251
243
|
});
|
|
252
|
-
const session = { runRoot, run, state, lease, runStore, logger, activeRun };
|
|
244
|
+
const session = { runRoot, cwd, run, paths, state, lease, runStore, logger, activeRun };
|
|
253
245
|
await logger.record({ type: "run.started", workflowId: workflow.id, cwd, workspace });
|
|
254
246
|
await commitRunBoundary(session, `run started: ${workflow.id}`);
|
|
255
247
|
return session;
|
|
@@ -268,38 +260,27 @@ var NornEngine = class {
|
|
|
268
260
|
const runStore = await NornRunStore.open(runRoot);
|
|
269
261
|
const currentRoot = runCurrentRoot(runRoot);
|
|
270
262
|
const logger = await NornRunLogger.load(join(currentRoot, MANIFEST_FILE_NAME), createRunFileCoordinator(runRoot));
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
const run = await this.buildRun({ id: currentState.id, currentRoot, workspace: currentState.workspace, cwd, isolationMode, signal: activeRun.controller.signal, logger });
|
|
275
|
-
return { runRoot, run, state, lease, runStore, logger, activeRun };
|
|
263
|
+
const paths = { project: currentState.projectRoot ?? resolve(runRoot, "../../.."), workspace: currentState.workspace };
|
|
264
|
+
const run = await this.buildRun({ id: currentState.id, currentRoot, paths, signal: activeRun.controller.signal, logger });
|
|
265
|
+
return { runRoot, cwd: process.cwd(), run, paths, state, lease, runStore, logger, activeRun };
|
|
276
266
|
} catch (error) {
|
|
277
267
|
this.failActiveRun(runRoot, activeRun);
|
|
278
268
|
throw error;
|
|
279
269
|
}
|
|
280
270
|
}
|
|
281
271
|
async buildRun(input) {
|
|
282
|
-
const artifactsRoot = join(input.currentRoot, "artifacts");
|
|
283
272
|
const logsRoot = join(input.currentRoot, "logs");
|
|
284
|
-
await mkdir(input.workspace, { recursive: true });
|
|
285
|
-
await mkdir(artifactsRoot, { recursive: true });
|
|
273
|
+
await mkdir(input.paths.workspace, { recursive: true });
|
|
286
274
|
await mkdir(logsRoot, { recursive: true });
|
|
287
|
-
const
|
|
275
|
+
const files = createRunFileCoordinator(dirname(input.currentRoot));
|
|
288
276
|
return new NornRunContext({
|
|
289
277
|
id: input.id,
|
|
290
278
|
runRoot: input.currentRoot,
|
|
291
|
-
workspace: input.workspace,
|
|
292
|
-
projectRoot: this.input.cwd,
|
|
293
|
-
cwd: input.cwd,
|
|
294
|
-
isolationMode: input.isolationMode,
|
|
295
279
|
signal: input.signal,
|
|
296
280
|
agentDir: this.input.agentDir,
|
|
297
281
|
responseCollector: this.responseCollector,
|
|
298
|
-
resources,
|
|
299
|
-
state,
|
|
300
282
|
logger: input.logger,
|
|
301
|
-
|
|
302
|
-
logs: new NornRunLogs(logsRoot, resources.files)
|
|
283
|
+
logs: new NornRunLogs(logsRoot, files)
|
|
303
284
|
});
|
|
304
285
|
}
|
|
305
286
|
startActiveRun(runRoot) {
|
|
@@ -332,29 +313,21 @@ var NornEngine = class {
|
|
|
332
313
|
activeRun.dispose();
|
|
333
314
|
activeRun.finish();
|
|
334
315
|
}
|
|
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
316
|
};
|
|
341
317
|
function defaultRunRoot(cwd, id) {
|
|
342
|
-
return join(cwd, RUNS_DIR_NAME, "runs", id);
|
|
343
|
-
}
|
|
344
|
-
function workflowDefaultCwd(projectRoot, workspace, workflow) {
|
|
345
|
-
return workflow?.isolation.mode === "project" ? projectRoot : workspace;
|
|
318
|
+
return join(resolve(cwd), RUNS_DIR_NAME, "runs", id);
|
|
346
319
|
}
|
|
347
320
|
function toWorkflowStep(workflow, step) {
|
|
348
321
|
return {
|
|
349
322
|
workflow,
|
|
350
|
-
|
|
323
|
+
args: step.args,
|
|
351
324
|
interruption: step.interruption
|
|
352
325
|
};
|
|
353
326
|
}
|
|
354
327
|
function toRunStateStep(step) {
|
|
355
328
|
return {
|
|
356
329
|
workflowId: step.workflow.id,
|
|
357
|
-
|
|
330
|
+
args: step.args,
|
|
358
331
|
cwd: "",
|
|
359
332
|
env: {},
|
|
360
333
|
interruption: step.interruption
|
|
@@ -372,7 +345,7 @@ function throwIfRunAborted(activeRun) {
|
|
|
372
345
|
function isRunStopped(session, error) {
|
|
373
346
|
return error instanceof NornRunStoppedError || session.activeRun.controller.signal.reason instanceof NornRunStoppedError;
|
|
374
347
|
}
|
|
375
|
-
function interruptedLaunchResult(run, workflow,
|
|
348
|
+
function interruptedLaunchResult(run, workflow, args, interruption) {
|
|
376
349
|
if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
|
|
377
350
|
if (!interruption.description) throw new Error(`Interrupted workflow is missing description: ${workflow.id}`);
|
|
378
351
|
return {
|
|
@@ -382,7 +355,7 @@ function interruptedLaunchResult(run, workflow, params, interruption) {
|
|
|
382
355
|
workspace: run.workspace,
|
|
383
356
|
cwd: run.cwd,
|
|
384
357
|
workflowId: workflow.id,
|
|
385
|
-
interruption: { workflowId: workflow.id,
|
|
358
|
+
interruption: { workflowId: workflow.id, args, description: interruption.description, fields: interruption.fields }
|
|
386
359
|
};
|
|
387
360
|
}
|
|
388
361
|
async function recordRunEvent(session, event) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
6
|
+
readonly diagnostics: readonly NornWorkflowDiagnostic[];
|
|
7
7
|
constructor(input: {
|
|
8
|
-
readonly diagnostics: readonly
|
|
8
|
+
readonly diagnostics: readonly NornWorkflowDiagnostic[];
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
export declare class NornRunStoppedError extends Error {
|
package/dist/internal/errors.js
CHANGED
|
@@ -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.
|
|
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")}`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class NornFileCoordinator {
|
|
2
|
+
private readonly input;
|
|
3
|
+
private readonly lockRoot;
|
|
4
|
+
constructor(input: {
|
|
5
|
+
readonly lockRoot: string;
|
|
6
|
+
readonly waitTimeoutMs: number;
|
|
7
|
+
});
|
|
8
|
+
withExclusiveLock<T>(path: string, operation: (lockedPath: string) => Promise<T>): Promise<T>;
|
|
9
|
+
readText(path: string): Promise<string>;
|
|
10
|
+
writeText(path: string, content: string): Promise<void>;
|
|
11
|
+
private withCleanup;
|
|
12
|
+
private resolveTarget;
|
|
13
|
+
private acquire;
|
|
14
|
+
private reclaimDeadOwner;
|
|
15
|
+
private removeEmptyLock;
|
|
16
|
+
}
|
|
17
|
+
export declare function createRunFileCoordinator(runRoot: string): NornFileCoordinator;
|