@vimhead.dev/norn-cli 0.1.0-tip.35240723931.1 → 0.1.0-tip.35343816255.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 +2 -24
- package/assets/docs/README.md +3 -1
- package/assets/docs/agents.md +3 -3
- package/assets/docs/cli.md +7 -7
- package/assets/docs/composition.md +64 -34
- package/assets/docs/persistence.md +4 -2
- package/assets/docs/projects.md +3 -12
- package/assets/{setup → docs}/providers.md +4 -3
- package/assets/docs/resources.md +7 -7
- package/assets/docs/schemas.md +130 -0
- package/assets/docs/workflows.md +4 -3
- package/assets/examples/agent-then-analysis/README.md +1 -1
- package/assets/examples/agent-then-analysis/plugin.ts +16 -15
- package/assets/examples/caller-selected-continuation/README.md +75 -0
- package/assets/examples/caller-selected-continuation/caller.ts +47 -0
- package/assets/examples/caller-selected-continuation/input.json +9 -0
- package/assets/examples/caller-selected-continuation/norn.project.json +4 -0
- package/assets/examples/caller-selected-continuation/producer.ts +32 -0
- package/assets/examples/coordinating-multiple-agents/README.md +1 -1
- package/assets/examples/coordinating-multiple-agents/plugin.ts +10 -11
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +3 -4
- package/assets/examples/coordinating-multiple-agents/work-queue.ts +18 -18
- package/assets/examples/minimal-workflow/plugin.ts +2 -2
- package/assets/examples/shared-state/README.md +1 -1
- package/assets/examples/shared-state/plugin.ts +6 -6
- package/assets/examples/worktree-development-loop/README.md +105 -44
- package/assets/examples/worktree-development-loop/norn.project.json +1 -1
- package/assets/examples/worktree-development-loop/state.ts +6 -6
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +1 -1
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +9 -9
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +1 -1
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -7
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
- package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +7 -7
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +1 -1
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +11 -11
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +1 -1
- 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 +15 -32
- package/assets/packages/cli/src/client.ts +0 -7
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/agent-response-tool.ts +9 -10
- package/assets/packages/cli/src/internal/agents.ts +17 -16
- package/assets/packages/cli/src/internal/engine.ts +20 -18
- package/assets/packages/cli/src/internal/errors.ts +4 -10
- package/assets/packages/cli/src/internal/run-state.ts +4 -0
- package/assets/packages/cli/src/internal/run.ts +4 -7
- package/assets/packages/cli/src/internal/state-store.ts +22 -14
- package/assets/packages/cli/src/internal/workflow-registry.ts +27 -21
- package/assets/packages/cli/src/plugin-loader.ts +33 -45
- package/assets/packages/cli/src/resources.ts +14 -12
- package/assets/packages/core/src/workflow-transition.ts +4 -0
- package/assets/packages/sdk/src/api.ts +87 -131
- package/assets/packages/sdk/src/files.ts +11 -10
- package/assets/packages/sdk/src/index.ts +0 -1
- package/assets/packages/sdk/src/resources.ts +2 -2
- package/assets/packages/sdk/src/schema.ts +53 -29
- package/assets/packages/sdk/src/state-adapter.ts +4 -4
- package/assets/tests/workflow-ref.test.ts +121 -77
- package/dist/cli.js +20 -36
- package/dist/client.d.ts +0 -4
- package/dist/client.js +0 -3
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/agent-response-tool.d.ts +2 -2
- package/dist/internal/agent-response-tool.js +7 -7
- package/dist/internal/agents.d.ts +2 -2
- package/dist/internal/agents.js +10 -10
- package/dist/internal/engine.d.ts +1 -1
- package/dist/internal/engine.js +14 -12
- package/dist/internal/errors.d.ts +2 -1
- package/dist/internal/errors.js +5 -8
- package/dist/internal/run-state.js +4 -0
- package/dist/internal/run.d.ts +2 -2
- package/dist/internal/run.js +10 -6
- package/dist/internal/state-store.d.ts +8 -7
- package/dist/internal/state-store.js +15 -9
- package/dist/internal/workflow-registry.d.ts +14 -6
- package/dist/internal/workflow-registry.js +20 -15
- package/dist/plugin-loader.d.ts +15 -21
- package/dist/plugin-loader.js +35 -47
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +15 -13
- package/package.json +3 -4
- package/assets/packages/sdk/src/seer/config.ts +0 -62
- package/assets/packages/sdk/src/seer/index.ts +0 -7
- package/assets/setup/releases.md +0 -76
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NornPluginDiagnostic } from "@vimhead.dev/norn";
|
|
2
|
+
import { AssertError } from "typebox/value";
|
|
2
3
|
export declare class NornProjectLoadError extends Error {
|
|
3
4
|
readonly code = "NORN_PROJECT_INVALID";
|
|
4
5
|
readonly isComplete = false;
|
|
@@ -10,5 +11,5 @@ export declare class NornProjectLoadError extends Error {
|
|
|
10
11
|
export declare class NornRunStoppedError extends Error {
|
|
11
12
|
constructor();
|
|
12
13
|
}
|
|
13
|
-
export declare function
|
|
14
|
+
export declare function schemaErrorMessage(error: AssertError): string;
|
|
14
15
|
export declare function errorMessage(error: unknown): string;
|
package/dist/internal/errors.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/internal/errors.ts
|
|
2
|
+
import { AssertError } from "typebox/value";
|
|
2
3
|
var NornProjectLoadError = class extends Error {
|
|
3
4
|
code = "NORN_PROJECT_INVALID";
|
|
4
5
|
isComplete = false;
|
|
@@ -19,20 +20,16 @@ var NornRunStoppedError = class extends Error {
|
|
|
19
20
|
this.name = "NornRunStoppedError";
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
if (!Array.isArray(issues) || issues.length === 0) return errorMessage(error);
|
|
25
|
-
return issues.slice(0, 3).map((issue) => {
|
|
26
|
-
const path = Array.isArray(issue.path) && issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
27
|
-
return `${path}${issue.message ?? "Invalid workflow response"}`;
|
|
28
|
-
}).join("; ");
|
|
23
|
+
function schemaErrorMessage(error) {
|
|
24
|
+
return error.cause.errors.slice(0, 3).map((issue) => `${issue.instancePath || "/"}: ${issue.message}`).join("; ");
|
|
29
25
|
}
|
|
30
26
|
function errorMessage(error) {
|
|
27
|
+
if (error instanceof AssertError) return schemaErrorMessage(error);
|
|
31
28
|
return error instanceof Error ? error.message : String(error);
|
|
32
29
|
}
|
|
33
30
|
export {
|
|
34
31
|
NornProjectLoadError,
|
|
35
32
|
NornRunStoppedError,
|
|
36
33
|
errorMessage,
|
|
37
|
-
|
|
34
|
+
schemaErrorMessage
|
|
38
35
|
};
|
|
@@ -41,6 +41,8 @@ async function writeTextAtomically(path, content) {
|
|
|
41
41
|
// src/internal/run-state.ts
|
|
42
42
|
import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
|
|
43
43
|
import { runCurrentRoot } from "./run-store.js";
|
|
44
|
+
import { jsonValueSchema } from "@vimhead.dev/norn/schema";
|
|
45
|
+
import { Value } from "typebox/value";
|
|
44
46
|
var RUN_STATE_FILE_NAME = "run-state.json";
|
|
45
47
|
var LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
|
|
46
48
|
function mergeInterruptedWorkflowParams(currentParams, params, fields) {
|
|
@@ -165,11 +167,13 @@ var NornRunStateStore = class _NornRunStateStore {
|
|
|
165
167
|
await this.files.withExclusiveLock(this.path, async (path) => {
|
|
166
168
|
const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME) }));
|
|
167
169
|
const next = { ...current, ...patch, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
170
|
+
if (next.current?.params !== void 0) Value.Assert(jsonValueSchema, next.current.params);
|
|
168
171
|
await writeJsonAtomically(path, next);
|
|
169
172
|
this.state = next;
|
|
170
173
|
});
|
|
171
174
|
}
|
|
172
175
|
async write() {
|
|
176
|
+
if (this.state.current?.params !== void 0) Value.Assert(jsonValueSchema, this.state.current.params);
|
|
173
177
|
await mkdir2(dirname2(this.path), { recursive: true });
|
|
174
178
|
await this.files.withExclusiveLock(this.path, (path) => writeJsonAtomically(path, this.state));
|
|
175
179
|
}
|
package/dist/internal/run.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { NornAnyWorkflowDeclaration, NornProjectRun, NornRunOutcomeMetadata, NornWorkflowIsolationMode,
|
|
2
|
+
import type { NornAnyWorkflowDeclaration, NornProjectRun, NornRunOutcomeMetadata, NornWorkflowIsolationMode, NornRun } from "@vimhead.dev/norn";
|
|
3
3
|
import type { NornAgentResponseCollector } from "./agent-response-tool.ts";
|
|
4
4
|
import type { NornArtifacts } from "./artifacts.ts";
|
|
5
5
|
import type { NornRunLogs } from "./logs.ts";
|
|
@@ -41,7 +41,7 @@ export declare class NornRunContext implements NornProjectRun {
|
|
|
41
41
|
private createAgentRunner;
|
|
42
42
|
path(relativePath: string): string;
|
|
43
43
|
projectPath(relativePath: string): string;
|
|
44
|
-
next
|
|
44
|
+
next(workflowId: string, params: unknown): ReturnType<NornRun["next"]>;
|
|
45
45
|
complete(metadata?: NornRunOutcomeMetadata): ReturnType<NornRun["complete"]>;
|
|
46
46
|
fail(metadata: NornRunOutcomeMetadata & {
|
|
47
47
|
readonly summary: string;
|
package/dist/internal/run.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// src/internal/run.ts
|
|
2
2
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
// ../core/src/workflow-transition.ts
|
|
5
|
+
function createWorkflowTransition(input) {
|
|
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 };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/internal/run.ts
|
|
3
11
|
import { NornAgentRunner } from "./agents.js";
|
|
4
12
|
import { NornCommandRunner } from "./commands.js";
|
|
5
13
|
var NornRunContext = class _NornRunContext {
|
|
@@ -74,12 +82,8 @@ var NornRunContext = class _NornRunContext {
|
|
|
74
82
|
projectPath(relativePath) {
|
|
75
83
|
return this.resolveFromRoot(this.projectRoot, this.projectRoot, relativePath);
|
|
76
84
|
}
|
|
77
|
-
next(
|
|
78
|
-
return {
|
|
79
|
-
type: "next",
|
|
80
|
-
workflowId: typeof workflow === "string" ? workflow : workflow.id,
|
|
81
|
-
params
|
|
82
|
-
};
|
|
85
|
+
next(workflowId, params) {
|
|
86
|
+
return createWorkflowTransition({ workflowId, params });
|
|
83
87
|
}
|
|
84
88
|
complete(metadata) {
|
|
85
89
|
return { type: "complete", metadata };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NornWorkflowState, NornWorkflowStateDefinition } from "@vimhead.dev/norn";
|
|
2
2
|
import type { NornFileCoordinator } from "@vimhead.dev/norn/files";
|
|
3
|
+
import type { Static, TSchema } from "typebox";
|
|
3
4
|
export declare class NornMemoryWorkflowState implements NornWorkflowState {
|
|
4
5
|
private readonly data;
|
|
5
|
-
get<
|
|
6
|
-
getOptional<
|
|
7
|
-
set<
|
|
6
|
+
get<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema>>;
|
|
7
|
+
getOptional<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema> | undefined>;
|
|
8
|
+
set<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>, value: NoInfer<Static<Schema>>): Promise<void>;
|
|
8
9
|
}
|
|
9
10
|
export declare class NornJsonWorkflowState implements NornWorkflowState {
|
|
10
11
|
private readonly input;
|
|
@@ -15,8 +16,8 @@ export declare class NornJsonWorkflowState implements NornWorkflowState {
|
|
|
15
16
|
readonly coordinateMutation: <T>(path: string, operation: () => Promise<T>) => Promise<T>;
|
|
16
17
|
});
|
|
17
18
|
initialize(mode: "create" | "open"): Promise<void>;
|
|
18
|
-
get<
|
|
19
|
-
getOptional<
|
|
20
|
-
set<
|
|
19
|
+
get<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema>>;
|
|
20
|
+
getOptional<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema> | undefined>;
|
|
21
|
+
set<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>, value: NoInfer<Static<Schema>>): Promise<void>;
|
|
21
22
|
private readStateFile;
|
|
22
23
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// ../core/src/atomic-files.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
3
5
|
|
|
4
6
|
// ../core/src/errors.ts
|
|
5
7
|
function isNodeError(error) {
|
|
@@ -7,9 +9,6 @@ function isNodeError(error) {
|
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
// ../core/src/atomic-files.ts
|
|
10
|
-
import { randomUUID } from "node:crypto";
|
|
11
|
-
import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
12
|
-
import { dirname } from "node:path";
|
|
13
12
|
async function writeJsonAtomically(path, value) {
|
|
14
13
|
await writeTextAtomically(path, `${JSON.stringify(value, null, 2)}
|
|
15
14
|
`);
|
|
@@ -34,6 +33,13 @@ async function writeTextAtomically(path, content) {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// src/internal/state-store.ts
|
|
36
|
+
import { jsonValueSchema } from "@vimhead.dev/norn/schema";
|
|
37
|
+
import { readFile } from "node:fs/promises";
|
|
38
|
+
import { Value } from "typebox/value";
|
|
39
|
+
function copyStateValue(schema, value) {
|
|
40
|
+
Value.Assert(jsonValueSchema, value);
|
|
41
|
+
return structuredClone(Value.Parse(schema, value));
|
|
42
|
+
}
|
|
37
43
|
var NornMemoryWorkflowState = class {
|
|
38
44
|
data = /* @__PURE__ */ new Map();
|
|
39
45
|
async get(state) {
|
|
@@ -43,10 +49,10 @@ var NornMemoryWorkflowState = class {
|
|
|
43
49
|
}
|
|
44
50
|
async getOptional(state) {
|
|
45
51
|
if (!this.data.has(state.id)) return void 0;
|
|
46
|
-
return state.schema
|
|
52
|
+
return copyStateValue(state.schema, this.data.get(state.id));
|
|
47
53
|
}
|
|
48
54
|
async set(state, value) {
|
|
49
|
-
this.data.set(state.id, state.schema
|
|
55
|
+
this.data.set(state.id, copyStateValue(state.schema, value));
|
|
50
56
|
}
|
|
51
57
|
};
|
|
52
58
|
var NornJsonWorkflowState = class {
|
|
@@ -74,10 +80,10 @@ var NornJsonWorkflowState = class {
|
|
|
74
80
|
async getOptional(state) {
|
|
75
81
|
const data = await this.input.files.withExclusiveLock(this.stateFile, (path) => this.readStateFile(path));
|
|
76
82
|
if (!Object.prototype.hasOwnProperty.call(data, state.id)) return void 0;
|
|
77
|
-
return state.schema
|
|
83
|
+
return copyStateValue(state.schema, data[state.id]);
|
|
78
84
|
}
|
|
79
85
|
async set(state, value) {
|
|
80
|
-
const parsedValue = state.schema
|
|
86
|
+
const parsedValue = copyStateValue(state.schema, value);
|
|
81
87
|
await this.input.coordinateMutation(this.stateFile, () => this.input.files.withExclusiveLock(this.stateFile, async (path) => {
|
|
82
88
|
const data = await this.readStateFile(path);
|
|
83
89
|
Object.defineProperty(data, state.id, { value: parsedValue, enumerable: true, configurable: true, writable: true });
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
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
3
|
export type NornRegisteredWorkflow = {
|
|
4
4
|
workflow: NornAnyWorkflowDeclaration;
|
|
5
5
|
implementation: NornWorkflowImplementation<NornAnyWorkflowDeclaration, unknown>;
|
|
6
|
-
|
|
7
|
-
config: unknown;
|
|
6
|
+
configuration?: NornRegisteredConfiguration;
|
|
8
7
|
plugin?: NornWorkflowPluginInfo;
|
|
9
8
|
};
|
|
9
|
+
export type NornRegisteredConfiguration = {
|
|
10
|
+
readonly schema: TSchema;
|
|
11
|
+
readonly input: unknown;
|
|
12
|
+
readonly value: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function decodePluginConfiguration(input: {
|
|
15
|
+
readonly pluginId: string;
|
|
16
|
+
readonly schema: TSchema | undefined;
|
|
17
|
+
readonly value: unknown;
|
|
18
|
+
}): NornRegisteredConfiguration | undefined;
|
|
10
19
|
export type NornWorkflowStepResult = NornRunNext | {
|
|
11
20
|
readonly type: "complete";
|
|
12
21
|
readonly workflow: NornAnyWorkflowDeclaration;
|
|
@@ -20,8 +29,7 @@ export declare class NornWorkflowRegistry {
|
|
|
20
29
|
private readonly entries;
|
|
21
30
|
register<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, implementation: NornWorkflowImplementation<TWorkflow, unknown>, metadata?: {
|
|
22
31
|
readonly plugin?: NornWorkflowPluginInfo;
|
|
23
|
-
readonly
|
|
24
|
-
readonly config?: unknown;
|
|
32
|
+
readonly configuration?: NornRegisteredConfiguration;
|
|
25
33
|
}): NornDispose;
|
|
26
34
|
list(options?: {
|
|
27
35
|
readonly entrypointsOnly?: boolean;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
// src/internal/workflow-registry.ts
|
|
2
|
-
import { z } from "zod";
|
|
3
2
|
import { isWorkflowComplete, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
|
|
4
|
-
import { assertWorkflowMetadata, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
|
|
3
|
+
import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
|
|
4
|
+
import { Value } from "typebox/value";
|
|
5
|
+
function decodePluginConfiguration(input) {
|
|
6
|
+
if (!input.schema) {
|
|
7
|
+
if (input.value !== void 0) throw new Error(`Norn config provided for plugin without config schema: ${input.pluginId}`);
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
const value = defaultConfigInput(input.schema, input.value);
|
|
11
|
+
return { schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
|
|
12
|
+
}
|
|
5
13
|
var NornWorkflowRegistry = class {
|
|
6
14
|
entries = /* @__PURE__ */ new Map();
|
|
7
15
|
register(workflow, implementation, metadata = {}) {
|
|
@@ -9,8 +17,7 @@ var NornWorkflowRegistry = class {
|
|
|
9
17
|
const entry = {
|
|
10
18
|
workflow,
|
|
11
19
|
implementation,
|
|
12
|
-
|
|
13
|
-
config: metadata.configSchema ? metadata.configSchema.parse(defaultConfigInput(metadata.configSchema, metadata.config)) : void 0,
|
|
20
|
+
configuration: metadata.configuration,
|
|
14
21
|
plugin: metadata.plugin
|
|
15
22
|
};
|
|
16
23
|
assertWorkflowMetadata(workflow);
|
|
@@ -38,7 +45,7 @@ var NornWorkflowRegistry = class {
|
|
|
38
45
|
const entry = this.entries.get(workflow.id);
|
|
39
46
|
if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
|
|
40
47
|
if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
|
|
41
|
-
const parsedParams = workflow.params
|
|
48
|
+
const parsedParams = Value.Decode(workflow.params, params);
|
|
42
49
|
const parsedConfig = parseExecutionConfig(entry, configOverride);
|
|
43
50
|
const description = await entry.implementation.gate?.describe(run, parsedParams, parsedConfig);
|
|
44
51
|
return validateGateDescription(description ?? workflow.id, workflow.id);
|
|
@@ -46,7 +53,7 @@ var NornWorkflowRegistry = class {
|
|
|
46
53
|
async execute(workflow, run, params, configOverride) {
|
|
47
54
|
const entry = this.entries.get(workflow.id);
|
|
48
55
|
if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
|
|
49
|
-
const parsedParams = workflow.params
|
|
56
|
+
const parsedParams = Value.Decode(workflow.params, params);
|
|
50
57
|
const parsedConfig = parseExecutionConfig(entry, configOverride);
|
|
51
58
|
const result = await entry.implementation.execute(run, parsedParams, parsedConfig);
|
|
52
59
|
if (isWorkflowNext(result)) return result;
|
|
@@ -64,13 +71,13 @@ function defaultConfigInput(configSchema, config) {
|
|
|
64
71
|
}
|
|
65
72
|
function parseExecutionConfig(entry, configOverride) {
|
|
66
73
|
const pluginConfigOverride = pluginConfigOverrideInput(entry, configOverride);
|
|
67
|
-
if (!entry.
|
|
74
|
+
if (!entry.configuration) {
|
|
68
75
|
if (pluginConfigOverride !== void 0) throw new Error(`Run config override provided for plugin without config schema: ${entry.plugin?.id ?? entry.workflow.id}`);
|
|
69
76
|
return void 0;
|
|
70
77
|
}
|
|
71
|
-
if (pluginConfigOverride === void 0) return entry.
|
|
72
|
-
const rawConfig = isPlainObject(entry.
|
|
73
|
-
return entry.
|
|
78
|
+
if (pluginConfigOverride === void 0) return entry.configuration.value;
|
|
79
|
+
const rawConfig = isPlainObject(entry.configuration.input) && isPlainObject(pluginConfigOverride) ? deepMerge(entry.configuration.input, pluginConfigOverride) : pluginConfigOverride;
|
|
80
|
+
return Value.Decode(entry.configuration.schema, rawConfig);
|
|
74
81
|
}
|
|
75
82
|
function pluginConfigOverrideInput(entry, configOverride) {
|
|
76
83
|
if (configOverride === void 0) return void 0;
|
|
@@ -105,7 +112,7 @@ function validateGateDescription(description, workflowId) {
|
|
|
105
112
|
function inspectedWorkflowInfo(entry) {
|
|
106
113
|
return {
|
|
107
114
|
...workflowInfo(entry),
|
|
108
|
-
paramsSchema:
|
|
115
|
+
paramsSchema: inspectSchema(entry.workflow.params),
|
|
109
116
|
gate: gateInfo(entry.workflow)
|
|
110
117
|
};
|
|
111
118
|
}
|
|
@@ -121,9 +128,7 @@ function workflowInfo(entry) {
|
|
|
121
128
|
function gateInfo(workflow) {
|
|
122
129
|
return workflow.gate ? { enabled: true, fields: workflow.gate.fields } : null;
|
|
123
130
|
}
|
|
124
|
-
function jsonSchema(schema) {
|
|
125
|
-
return z.toJSONSchema(schema, { io: "input" });
|
|
126
|
-
}
|
|
127
131
|
export {
|
|
128
|
-
NornWorkflowRegistry
|
|
132
|
+
NornWorkflowRegistry,
|
|
133
|
+
decodePluginConfiguration
|
|
129
134
|
};
|
package/dist/plugin-loader.d.ts
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { type NornProjectInspection, type NornProjectPluginInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowPlugin } from "@vimhead.dev/norn";
|
|
2
|
+
import * as typeboxModule from "typebox";
|
|
3
|
+
import { type StaticDecode } from "typebox";
|
|
3
4
|
import { NornMemoryWorkflowState } from "./internal/state-store.ts";
|
|
4
5
|
import { NornWorkflowRegistry, type NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
|
|
5
|
-
import { type NornResolvedSeerModeConfig } from "@vimhead.dev/norn/seer";
|
|
6
6
|
export declare const NORN_PROJECT_FILE_NAME = "norn.project.json";
|
|
7
|
-
declare const nornConfigSchema:
|
|
8
|
-
plugins:
|
|
9
|
-
includes:
|
|
10
|
-
config:
|
|
11
|
-
}
|
|
12
|
-
declare const nornProjectConfigSchema:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}, z.core.$strip>;
|
|
21
|
-
type NornConfig = z.output<typeof nornConfigSchema> & {
|
|
22
|
-
readonly seerMode?: never;
|
|
23
|
-
};
|
|
24
|
-
type NornProjectConfig = z.output<typeof nornProjectConfigSchema>;
|
|
7
|
+
declare const nornConfigSchema: typeboxModule.TObject<{
|
|
8
|
+
plugins: typeboxModule.TArray<typeboxModule.TString>;
|
|
9
|
+
includes: typeboxModule.TArray<typeboxModule.TString>;
|
|
10
|
+
config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
|
|
11
|
+
}>;
|
|
12
|
+
declare const nornProjectConfigSchema: typeboxModule.TObject<{
|
|
13
|
+
version: typeboxModule.TLiteral<1>;
|
|
14
|
+
plugins: typeboxModule.TArray<typeboxModule.TString>;
|
|
15
|
+
includes: typeboxModule.TArray<typeboxModule.TString>;
|
|
16
|
+
config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
|
|
17
|
+
}>;
|
|
18
|
+
type NornConfig = StaticDecode<typeof nornConfigSchema>;
|
|
19
|
+
type NornProjectConfig = StaticDecode<typeof nornProjectConfigSchema>;
|
|
25
20
|
type NornConfigFile = {
|
|
26
21
|
readonly path: string;
|
|
27
22
|
readonly root: string;
|
|
@@ -36,7 +31,6 @@ export type NornProject = {
|
|
|
36
31
|
readonly config: NornProjectConfig;
|
|
37
32
|
readonly configFiles: readonly NornConfigFile[];
|
|
38
33
|
readonly projectConfig: Record<string, unknown>;
|
|
39
|
-
readonly seerMode?: NornResolvedSeerModeConfig;
|
|
40
34
|
};
|
|
41
35
|
export type NornLoadedProject = NornProject & {
|
|
42
36
|
readonly plugins: readonly NornWorkflowPlugin[];
|
package/dist/plugin-loader.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
// src/plugin-loader.ts
|
|
2
|
-
import { access, readFile, readdir } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { createJiti } from "jiti/static";
|
|
6
|
-
import * as typeboxModule from "typebox";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import * as zodModule from "zod";
|
|
9
2
|
import * as nornModule from "@vimhead.dev/norn";
|
|
10
|
-
import * as nornFilesModule from "@vimhead.dev/norn/files";
|
|
11
|
-
import * as nornSchemaModule from "@vimhead.dev/norn/schema";
|
|
12
|
-
import * as nornSeerModule from "@vimhead.dev/norn/seer";
|
|
13
3
|
import { isWorkflowPlugin } from "@vimhead.dev/norn";
|
|
14
|
-
import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
|
|
15
4
|
|
|
16
5
|
// ../core/src/errors.ts
|
|
17
6
|
function isNodeError(error) {
|
|
@@ -19,22 +8,31 @@ function isNodeError(error) {
|
|
|
19
8
|
}
|
|
20
9
|
|
|
21
10
|
// src/plugin-loader.ts
|
|
11
|
+
import * as nornFilesModule from "@vimhead.dev/norn/files";
|
|
12
|
+
import * as nornSchemaModule from "@vimhead.dev/norn/schema";
|
|
13
|
+
import { inspectSchema } from "@vimhead.dev/norn/schema";
|
|
14
|
+
import { createJiti } from "jiti/static";
|
|
15
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
16
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
17
|
+
import { pathToFileURL } from "node:url";
|
|
18
|
+
import * as typeboxModule from "typebox";
|
|
19
|
+
import { Type } from "typebox";
|
|
20
|
+
import * as typeboxCompileModule from "typebox/compile";
|
|
21
|
+
import * as typeboxSchemaModule from "typebox/schema";
|
|
22
|
+
import * as typeboxValueModule from "typebox/value";
|
|
23
|
+
import { AssertError, Value } from "typebox/value";
|
|
24
|
+
import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
|
|
22
25
|
import { NornMemoryWorkflowState } from "./internal/state-store.js";
|
|
23
|
-
import { NornWorkflowRegistry } from "./internal/workflow-registry.js";
|
|
24
|
-
import { schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
|
|
25
|
-
import { resolveSeerModeConfig } from "@vimhead.dev/norn/seer";
|
|
26
|
+
import { decodePluginConfiguration, NornWorkflowRegistry } from "./internal/workflow-registry.js";
|
|
26
27
|
var NORN_PROJECT_FILE_NAME = "norn.project.json";
|
|
27
|
-
var
|
|
28
|
-
|
|
28
|
+
var nornConfigSchema = Type.Object({
|
|
29
|
+
plugins: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
|
|
30
|
+
includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
|
|
31
|
+
config: Type.Record(Type.String(), Type.Unknown(), { default: {} })
|
|
29
32
|
});
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
config: z.record(z.string(), z.unknown()).default({})
|
|
34
|
-
});
|
|
35
|
-
var nornProjectConfigSchema = nornConfigSchema.extend({
|
|
36
|
-
version: z.literal(1).default(1),
|
|
37
|
-
seerMode: seerModeConfigSchema.optional()
|
|
33
|
+
var nornProjectConfigSchema = Type.Object({
|
|
34
|
+
...nornConfigSchema.properties,
|
|
35
|
+
version: Type.Literal(1, { default: 1 })
|
|
38
36
|
});
|
|
39
37
|
async function loadNornProject(cwd) {
|
|
40
38
|
const { project, diagnostics } = await collectNornProject(cwd);
|
|
@@ -73,8 +71,7 @@ function buildProjectInfo(project) {
|
|
|
73
71
|
configPath: project.configPath,
|
|
74
72
|
configRoot: project.configRoot,
|
|
75
73
|
configFiles: project.configFiles.map((file) => file.path),
|
|
76
|
-
plugins: project.pluginInfos
|
|
77
|
-
seerMode: project.seerMode ?? null
|
|
74
|
+
plugins: project.pluginInfos
|
|
78
75
|
};
|
|
79
76
|
}
|
|
80
77
|
async function findNornProject(cwd) {
|
|
@@ -89,8 +86,7 @@ async function findNornProject(cwd) {
|
|
|
89
86
|
configRoot: projectRootConfig.root,
|
|
90
87
|
config: projectRootConfig.config,
|
|
91
88
|
configFiles,
|
|
92
|
-
projectConfig: mergeProjectConfig(configFiles)
|
|
93
|
-
seerMode: resolveSeerModeConfig({ configPath: projectRootConfig.path, configRoot: projectRootConfig.root, seerMode: projectRootConfig.config.seerMode })
|
|
89
|
+
projectConfig: mergeProjectConfig(configFiles)
|
|
94
90
|
};
|
|
95
91
|
}
|
|
96
92
|
async function findNearestNornProject(cwd) {
|
|
@@ -127,11 +123,11 @@ async function loadNornConfigTree(configFile, visitedPaths) {
|
|
|
127
123
|
return [configFile, ...descendants.flat()];
|
|
128
124
|
}
|
|
129
125
|
async function readNornProjectConfigFile(path) {
|
|
130
|
-
const config = nornProjectConfigSchema.
|
|
126
|
+
const config = Value.Parse(nornProjectConfigSchema, Value.Default(nornProjectConfigSchema, JSON.parse(await readFile(path, "utf8"))));
|
|
131
127
|
return { path, root: dirname(path), config };
|
|
132
128
|
}
|
|
133
129
|
async function readNornConfigFile(path) {
|
|
134
|
-
const config = nornConfigSchema.
|
|
130
|
+
const config = Value.Parse(nornConfigSchema, Value.Default(nornConfigSchema, JSON.parse(await readFile(path, "utf8"))));
|
|
135
131
|
return { path, root: dirname(path), config };
|
|
136
132
|
}
|
|
137
133
|
function createProjectConfigEntry(projectFile) {
|
|
@@ -227,15 +223,14 @@ function registerProjectPlugin(input) {
|
|
|
227
223
|
let stage = "config";
|
|
228
224
|
try {
|
|
229
225
|
const configInput = input.project.projectConfig[plugin.manifest.id];
|
|
230
|
-
|
|
231
|
-
const config = plugin.manifest.config ? plugin.manifest.config.parse(defaultConfigInput(plugin.manifest.config, configInput)) : void 0;
|
|
226
|
+
const configuration = decodePluginConfiguration({ pluginId: plugin.manifest.id, schema: plugin.manifest.config, value: configInput });
|
|
232
227
|
stage = "schema";
|
|
233
228
|
const info = {
|
|
234
229
|
id: plugin.manifest.id,
|
|
235
230
|
path: source.pluginPath,
|
|
236
231
|
configPath: source.configPath,
|
|
237
|
-
configSchema: plugin.manifest.config ?
|
|
238
|
-
config
|
|
232
|
+
configSchema: plugin.manifest.config ? inspectSchema(plugin.manifest.config) : null,
|
|
233
|
+
config: configuration?.value
|
|
239
234
|
};
|
|
240
235
|
stage = "implementation";
|
|
241
236
|
const implementation = typeof plugin.implementation === "function" ? plugin.implementation({ cwd: input.project.cwd, state: input.state }) : plugin.implementation;
|
|
@@ -247,9 +242,9 @@ function registerProjectPlugin(input) {
|
|
|
247
242
|
stage = "duplicate";
|
|
248
243
|
if (input.registry.workflowById(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
|
|
249
244
|
stage = "declaration";
|
|
250
|
-
unregister.push(input.registry.register(workflow, workflowImplementation, { plugin: workflowPluginInfo(info),
|
|
245
|
+
unregister.push(input.registry.register(workflow, workflowImplementation, { plugin: workflowPluginInfo(info), configuration }));
|
|
251
246
|
} catch (error) {
|
|
252
|
-
diagnostics.push(createPluginDiagnostic({ source, workflowId: workflow.id, stage
|
|
247
|
+
diagnostics.push(createPluginDiagnostic({ source, workflowId: workflow.id, stage, error }));
|
|
253
248
|
}
|
|
254
249
|
}
|
|
255
250
|
if (diagnostics.length === 0) return { loaded: { plugin, info }, diagnostics };
|
|
@@ -265,11 +260,7 @@ function createPluginDiagnostic(input) {
|
|
|
265
260
|
workflowId: input.workflowId,
|
|
266
261
|
stage: input.stage,
|
|
267
262
|
message: errorMessage(input.error),
|
|
268
|
-
issues: input.error instanceof
|
|
269
|
-
path: issue.path.map((part) => typeof part === "symbol" ? String(part) : part),
|
|
270
|
-
code: issue.code,
|
|
271
|
-
message: issue.message
|
|
272
|
-
})) : []
|
|
263
|
+
issues: input.error instanceof AssertError ? input.error.cause.errors : []
|
|
273
264
|
};
|
|
274
265
|
}
|
|
275
266
|
function workflowPluginInfo(info) {
|
|
@@ -280,9 +271,10 @@ function nornWorkflowVirtualModules() {
|
|
|
280
271
|
"@vimhead.dev/norn": nornModule,
|
|
281
272
|
"@vimhead.dev/norn/files": nornFilesModule,
|
|
282
273
|
"@vimhead.dev/norn/schema": nornSchemaModule,
|
|
283
|
-
"@vimhead.dev/norn/seer": nornSeerModule,
|
|
284
274
|
typebox: typeboxModule,
|
|
285
|
-
|
|
275
|
+
"typebox/value": typeboxValueModule,
|
|
276
|
+
"typebox/compile": typeboxCompileModule,
|
|
277
|
+
"typebox/schema": typeboxSchemaModule
|
|
286
278
|
};
|
|
287
279
|
}
|
|
288
280
|
function mergeProjectConfig(configFiles) {
|
|
@@ -315,10 +307,6 @@ function mergeProjectConfigObjects(base, override) {
|
|
|
315
307
|
function isPlainObject(value) {
|
|
316
308
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
317
309
|
}
|
|
318
|
-
function defaultConfigInput(configSchema, config) {
|
|
319
|
-
if (config !== void 0) return config;
|
|
320
|
-
return schemaType(unwrapSchema(configSchema)) === "object" ? {} : void 0;
|
|
321
|
-
}
|
|
322
310
|
async function expandIncludePath(configRoot, includePath) {
|
|
323
311
|
const resolvedIncludePath = resolveConfigPath(configRoot, includePath);
|
|
324
312
|
if (!hasGlobSegment(includePath)) return [resolvedIncludePath];
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type NornFileCoordinator } from "@vimhead.dev/norn/files";
|
|
2
1
|
import type { NornResourceDefinition, NornResources } from "@vimhead.dev/norn";
|
|
2
|
+
import { type NornFileCoordinator } from "@vimhead.dev/norn/files";
|
|
3
3
|
export declare class NornRunResources implements NornResources {
|
|
4
4
|
private readonly runRoot;
|
|
5
5
|
readonly files: NornFileCoordinator;
|
package/dist/resources.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
|
|
1
|
+
// ../core/src/atomic-files.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
7
5
|
|
|
8
6
|
// ../core/src/errors.ts
|
|
9
7
|
function isNodeError(error) {
|
|
@@ -11,9 +9,6 @@ function isNodeError(error) {
|
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
// ../core/src/atomic-files.ts
|
|
14
|
-
import { randomUUID } from "node:crypto";
|
|
15
|
-
import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
16
|
-
import { dirname } from "node:path";
|
|
17
12
|
async function writeJsonAtomically(path, value) {
|
|
18
13
|
await writeTextAtomically(path, `${JSON.stringify(value, null, 2)}
|
|
19
14
|
`);
|
|
@@ -38,8 +33,15 @@ async function writeTextAtomically(path, content) {
|
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
// src/resources.ts
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
|
|
37
|
+
import { jsonValueSchema } from "@vimhead.dev/norn/schema";
|
|
38
|
+
import { mkdir as mkdir2, readFile } from "node:fs/promises";
|
|
39
|
+
import { join, resolve } from "node:path";
|
|
40
|
+
import { isDeepStrictEqual } from "node:util";
|
|
41
|
+
import { Type } from "typebox";
|
|
42
|
+
import { Value } from "typebox/value";
|
|
43
|
+
var definitionSchema = Type.Object({ name: Type.String(), kind: Type.String({ minLength: 1 }), configuration: jsonValueSchema }, { additionalProperties: false });
|
|
44
|
+
var recordSchema = Type.Object({ ...definitionSchema.properties, isInitialized: Type.Boolean() }, { additionalProperties: false });
|
|
43
45
|
var NornRunResources = class _NornRunResources {
|
|
44
46
|
constructor(runRoot) {
|
|
45
47
|
this.runRoot = runRoot;
|
|
@@ -55,7 +57,7 @@ var NornRunResources = class _NornRunResources {
|
|
|
55
57
|
}
|
|
56
58
|
async ensure(definition) {
|
|
57
59
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(definition.name)) throw new Error(`Invalid resource name: ${definition.name}`);
|
|
58
|
-
const identity =
|
|
60
|
+
const identity = structuredClone(Value.Parse(definitionSchema, { name: definition.name, kind: definition.kind, configuration: definition.configuration }));
|
|
59
61
|
const existing = this.entries.get(definition.name);
|
|
60
62
|
if (existing) {
|
|
61
63
|
if (!isDeepStrictEqual(existing.definition, identity)) throw new Error(`Incompatible resource definition: ${definition.name}`);
|
|
@@ -77,7 +79,7 @@ var NornRunResources = class _NornRunResources {
|
|
|
77
79
|
return this.files.withExclusiveLock(join(directory, "definition.json"), async (path) => {
|
|
78
80
|
let stored;
|
|
79
81
|
try {
|
|
80
|
-
stored =
|
|
82
|
+
stored = Value.Parse(recordSchema, JSON.parse(await readFile(path, "utf8")));
|
|
81
83
|
} catch (error) {
|
|
82
84
|
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
83
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vimhead.dev/norn-cli",
|
|
3
|
-
"version": "0.1.0-tip.
|
|
3
|
+
"version": "0.1.0-tip.35343816255.1",
|
|
4
4
|
"description": "Harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,9 +42,8 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
44
44
|
"jiti": "^2.7.0",
|
|
45
|
-
"typebox": "^1.3.
|
|
46
|
-
"
|
|
47
|
-
"@vimhead.dev/norn": "0.1.0-tip.35240723931.1"
|
|
45
|
+
"typebox": "^1.3.33",
|
|
46
|
+
"@vimhead.dev/norn": "0.1.0-tip.35343816255.1"
|
|
48
47
|
},
|
|
49
48
|
"scripts": {
|
|
50
49
|
"build": "node ../../scripts/build-package.ts"
|