@vimhead.dev/norn-cli 0.1.0-tip.0
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/README.md +13 -0
- package/assets/README.md +157 -0
- package/assets/docs/README.md +23 -0
- package/assets/docs/agents.md +64 -0
- package/assets/docs/cli.md +183 -0
- package/assets/docs/composition.md +76 -0
- package/assets/docs/persistence.md +56 -0
- package/assets/docs/projects.md +75 -0
- package/assets/docs/recovery.md +56 -0
- package/assets/docs/resources.md +61 -0
- package/assets/docs/workflows.md +57 -0
- package/assets/examples/agent-then-analysis/README.md +103 -0
- package/assets/examples/agent-then-analysis/input.json +5 -0
- package/assets/examples/agent-then-analysis/norn.project.json +4 -0
- package/assets/examples/agent-then-analysis/plugin.ts +89 -0
- package/assets/examples/coordinating-multiple-agents/README.md +56 -0
- package/assets/examples/coordinating-multiple-agents/input.json +10 -0
- package/assets/examples/coordinating-multiple-agents/norn.project.json +4 -0
- package/assets/examples/coordinating-multiple-agents/plugin.ts +102 -0
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +52 -0
- package/assets/examples/coordinating-multiple-agents/work-queue.ts +153 -0
- package/assets/examples/minimal-workflow/README.md +71 -0
- package/assets/examples/minimal-workflow/norn.project.json +4 -0
- package/assets/examples/minimal-workflow/plugin.ts +29 -0
- package/assets/examples/shared-state/README.md +19 -0
- package/assets/examples/shared-state/input.json +1 -0
- package/assets/examples/shared-state/norn.project.json +4 -0
- package/assets/examples/shared-state/plugin.ts +47 -0
- package/assets/examples/worktree-development-loop/README.md +66 -0
- package/assets/examples/worktree-development-loop/index.ts +1 -0
- package/assets/examples/worktree-development-loop/manifest.ts +26 -0
- package/assets/examples/worktree-development-loop/norn.project.json +9 -0
- package/assets/examples/worktree-development-loop/plugin.ts +27 -0
- package/assets/examples/worktree-development-loop/shared/commands.ts +6 -0
- package/assets/examples/worktree-development-loop/state.ts +23 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +8 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +22 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +14 -0
- package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +8 -0
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +54 -0
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +3 -0
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +12 -0
- package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +8 -0
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +28 -0
- package/assets/examples/worktree-development-loop/workflows/planning/index.ts +3 -0
- package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +12 -0
- package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +8 -0
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +53 -0
- package/assets/examples/worktree-development-loop/workflows/review/index.ts +10 -0
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +23 -0
- package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +12 -0
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -0
- package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +3 -0
- package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +12 -0
- package/assets/package.json +1 -0
- package/assets/packages/cli/src/build-info.ts +36 -0
- package/assets/packages/cli/src/bun/cli.ts +16 -0
- package/assets/packages/cli/src/cli.ts +1135 -0
- package/assets/packages/cli/src/client.ts +167 -0
- package/assets/packages/cli/src/documentation-intro.ts +30 -0
- package/assets/packages/cli/src/documentation.ts +149 -0
- package/assets/packages/cli/src/generated-build-info.ts +12 -0
- package/assets/packages/cli/src/internal/agent-directory.ts +5 -0
- package/assets/packages/cli/src/internal/agent-response-tool.ts +96 -0
- package/assets/packages/cli/src/internal/agents.ts +365 -0
- package/assets/packages/cli/src/internal/artifacts.ts +26 -0
- package/assets/packages/cli/src/internal/commands.ts +180 -0
- package/assets/packages/cli/src/internal/documentation-bundle.ts +49 -0
- package/assets/packages/cli/src/internal/engine.ts +501 -0
- package/assets/packages/cli/src/internal/errors.ts +39 -0
- package/assets/packages/cli/src/internal/file-names.ts +3 -0
- package/assets/packages/cli/src/internal/launch-request.ts +94 -0
- package/assets/packages/cli/src/internal/logs.ts +41 -0
- package/assets/packages/cli/src/internal/metrics.ts +356 -0
- package/assets/packages/cli/src/internal/pi-assets.ts +95 -0
- package/assets/packages/cli/src/internal/resource-bindings.ts +35 -0
- package/assets/packages/cli/src/internal/run-lease.ts +158 -0
- package/assets/packages/cli/src/internal/run-log.ts +59 -0
- package/assets/packages/cli/src/internal/run-names.ts +36 -0
- package/assets/packages/cli/src/internal/run-resources.ts +23 -0
- package/assets/packages/cli/src/internal/run-state.ts +380 -0
- package/assets/packages/cli/src/internal/run-store.ts +323 -0
- package/assets/packages/cli/src/internal/run.ts +133 -0
- package/assets/packages/cli/src/internal/state-store.ts +75 -0
- package/assets/packages/cli/src/internal/usage.ts +70 -0
- package/assets/packages/cli/src/internal/workflow-registry.ts +176 -0
- package/assets/packages/cli/src/plugin-loader.ts +412 -0
- package/assets/packages/cli/src/resources.ts +67 -0
- package/assets/packages/core/src/agent-protocol.ts +1 -0
- package/assets/packages/core/src/atomic-files.ts +24 -0
- package/assets/packages/core/src/errors.ts +3 -0
- package/assets/packages/sdk/src/agent-resource-adapter.ts +11 -0
- package/assets/packages/sdk/src/api.ts +821 -0
- package/assets/packages/sdk/src/files.ts +136 -0
- package/assets/packages/sdk/src/index.ts +6 -0
- package/assets/packages/sdk/src/resources.ts +20 -0
- package/assets/packages/sdk/src/schema.ts +48 -0
- package/assets/packages/sdk/src/seer/config.ts +62 -0
- package/assets/packages/sdk/src/seer/index.ts +7 -0
- package/assets/packages/sdk/src/state-adapter.ts +75 -0
- package/assets/setup/providers.md +128 -0
- package/assets/setup/releases.md +76 -0
- package/assets/tests/workflow-ref.test.ts +113 -0
- package/bin/norn.mjs +10 -0
- package/dist/build-info.d.ts +30 -0
- package/dist/build-info.js +6 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1032 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.js +118 -0
- package/dist/documentation-intro.d.ts +5 -0
- package/dist/documentation-intro.js +29 -0
- package/dist/documentation.d.ts +33 -0
- package/dist/documentation.js +132 -0
- package/dist/generated-build-info.d.ts +10 -0
- package/dist/generated-build-info.js +14 -0
- package/dist/internal/agent-directory.d.ts +4 -0
- package/dist/internal/agent-directory.js +8 -0
- package/dist/internal/agent-response-tool.d.ts +21 -0
- package/dist/internal/agent-response-tool.js +79 -0
- package/dist/internal/agents.d.ts +29 -0
- package/dist/internal/agents.js +336 -0
- package/dist/internal/artifacts.d.ts +10 -0
- package/dist/internal/artifacts.js +29 -0
- package/dist/internal/commands.d.ts +18 -0
- package/dist/internal/commands.js +147 -0
- package/dist/internal/documentation-bundle.d.ts +16 -0
- package/dist/internal/documentation-bundle.js +42 -0
- package/dist/internal/engine.d.ts +44 -0
- package/dist/internal/engine.js +399 -0
- package/dist/internal/errors.d.ts +14 -0
- package/dist/internal/errors.js +38 -0
- package/dist/internal/file-names.d.ts +1 -0
- package/dist/internal/file-names.js +7 -0
- package/dist/internal/launch-request.d.ts +33 -0
- package/dist/internal/launch-request.js +110 -0
- package/dist/internal/logs.d.ts +16 -0
- package/dist/internal/logs.js +38 -0
- package/dist/internal/metrics.d.ts +19 -0
- package/dist/internal/metrics.js +282 -0
- package/dist/internal/pi-assets.d.ts +13 -0
- package/dist/internal/pi-assets.js +94 -0
- package/dist/internal/resource-bindings.d.ts +13 -0
- package/dist/internal/resource-bindings.js +34 -0
- package/dist/internal/run-lease.d.ts +32 -0
- package/dist/internal/run-lease.js +166 -0
- package/dist/internal/run-log.d.ts +30 -0
- package/dist/internal/run-log.js +71 -0
- package/dist/internal/run-names.d.ts +1 -0
- package/dist/internal/run-names.js +144 -0
- package/dist/internal/run-resources.d.ts +6 -0
- package/dist/internal/run-resources.js +26 -0
- package/dist/internal/run-state.d.ts +95 -0
- package/dist/internal/run-state.js +323 -0
- package/dist/internal/run-store.d.ts +35 -0
- package/dist/internal/run-store.js +314 -0
- package/dist/internal/run.d.ts +51 -0
- package/dist/internal/run.js +101 -0
- package/dist/internal/state-store.d.ts +22 -0
- package/dist/internal/state-store.js +97 -0
- package/dist/internal/usage.d.ts +5 -0
- package/dist/internal/usage.js +70 -0
- package/dist/internal/workflow-registry.d.ts +35 -0
- package/dist/internal/workflow-registry.js +129 -0
- package/dist/plugin-loader.d.ts +55 -0
- package/dist/plugin-loader.js +353 -0
- package/dist/resources.d.ts +11 -0
- package/dist/resources.js +98 -0
- package/package.json +52 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { NornAnyWorkflowDeclaration, NornProjectRun, NornRunOutcomeMetadata, NornWorkflowIsolationMode, NornWorkflowTarget, NornWorkflowTargetParamsInput, NornRun } from "@vimhead.dev/norn";
|
|
3
|
+
import type { NornAgentResponseCollector } from "./agent-response-tool.ts";
|
|
4
|
+
import type { NornArtifacts } from "./artifacts.ts";
|
|
5
|
+
import type { NornRunLogs } from "./logs.ts";
|
|
6
|
+
import type { NornRunLogger } from "./run-log.ts";
|
|
7
|
+
import type { NornRunResources } from "../resources.ts";
|
|
8
|
+
type DefaultNornRunInput = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly runRoot: string;
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly workspace: string;
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
readonly isolationMode: NornWorkflowIsolationMode;
|
|
15
|
+
readonly signal?: AbortSignal;
|
|
16
|
+
readonly model?: CreateAgentSessionOptions["model"];
|
|
17
|
+
readonly thinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
18
|
+
readonly agentDir?: string;
|
|
19
|
+
readonly responseCollector: NornAgentResponseCollector;
|
|
20
|
+
readonly resources: NornRunResources;
|
|
21
|
+
readonly state: NornRun["state"];
|
|
22
|
+
readonly logger: NornRunLogger;
|
|
23
|
+
readonly artifacts: NornArtifacts;
|
|
24
|
+
readonly logs: NornRunLogs;
|
|
25
|
+
};
|
|
26
|
+
export declare class NornRunContext implements NornProjectRun {
|
|
27
|
+
private readonly input;
|
|
28
|
+
readonly resources: NornRunResources;
|
|
29
|
+
readonly state: NornRun["state"];
|
|
30
|
+
readonly artifacts: NornRun["artifacts"];
|
|
31
|
+
readonly logs: NornRun["logs"];
|
|
32
|
+
readonly commands: NornRun["commands"];
|
|
33
|
+
readonly agents: NornRun["agents"];
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly workspace: string;
|
|
36
|
+
readonly projectRoot: string;
|
|
37
|
+
readonly cwd: string;
|
|
38
|
+
constructor(input: DefaultNornRunInput);
|
|
39
|
+
forWorkflow(workflow: NornAnyWorkflowDeclaration): NornRunContext;
|
|
40
|
+
private get boundaryRoot();
|
|
41
|
+
private createAgentRunner;
|
|
42
|
+
path(relativePath: string): string;
|
|
43
|
+
projectPath(relativePath: string): string;
|
|
44
|
+
next<TWorkflow extends NornWorkflowTarget<any>>(workflow: TWorkflow, params: NornWorkflowTargetParamsInput<TWorkflow>): ReturnType<NornRun["next"]>;
|
|
45
|
+
complete(metadata?: NornRunOutcomeMetadata): ReturnType<NornRun["complete"]>;
|
|
46
|
+
fail(metadata: NornRunOutcomeMetadata & {
|
|
47
|
+
readonly summary: string;
|
|
48
|
+
}): ReturnType<NornRun["fail"]>;
|
|
49
|
+
private resolveFromRoot;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/internal/run.ts
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { NornAgentRunner } from "./agents.js";
|
|
4
|
+
import { NornCommandRunner } from "./commands.js";
|
|
5
|
+
var NornRunContext = class _NornRunContext {
|
|
6
|
+
constructor(input) {
|
|
7
|
+
this.input = input;
|
|
8
|
+
this.id = input.id;
|
|
9
|
+
this.workspace = input.workspace;
|
|
10
|
+
this.projectRoot = input.projectRoot;
|
|
11
|
+
this.cwd = input.cwd;
|
|
12
|
+
this.resources = input.resources;
|
|
13
|
+
this.state = input.state;
|
|
14
|
+
this.artifacts = input.artifacts;
|
|
15
|
+
this.logs = {
|
|
16
|
+
read: (log) => this.input.logs.read(log)
|
|
17
|
+
};
|
|
18
|
+
this.commands = {
|
|
19
|
+
run: (commandInput) => new NornCommandRunner({
|
|
20
|
+
boundaryRoot: this.boundaryRoot,
|
|
21
|
+
boundaryName: this.input.isolationMode,
|
|
22
|
+
cwd: this.cwd,
|
|
23
|
+
signal: this.input.signal,
|
|
24
|
+
logs: this.input.logs,
|
|
25
|
+
logger: this.input.logger
|
|
26
|
+
}).run(commandInput)
|
|
27
|
+
};
|
|
28
|
+
this.agents = {
|
|
29
|
+
createSession: (agentInput) => this.createAgentRunner().createSession(agentInput),
|
|
30
|
+
prompt: (agentInput) => this.createAgentRunner().prompt(agentInput)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
input;
|
|
34
|
+
resources;
|
|
35
|
+
state;
|
|
36
|
+
artifacts;
|
|
37
|
+
logs;
|
|
38
|
+
commands;
|
|
39
|
+
agents;
|
|
40
|
+
id;
|
|
41
|
+
workspace;
|
|
42
|
+
projectRoot;
|
|
43
|
+
cwd;
|
|
44
|
+
forWorkflow(workflow) {
|
|
45
|
+
const isolationMode = workflow.isolation.mode;
|
|
46
|
+
return new _NornRunContext({
|
|
47
|
+
...this.input,
|
|
48
|
+
isolationMode,
|
|
49
|
+
cwd: isolationMode === "project" ? this.projectRoot : this.workspace
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
get boundaryRoot() {
|
|
53
|
+
return this.input.isolationMode === "project" ? this.projectRoot : this.workspace;
|
|
54
|
+
}
|
|
55
|
+
createAgentRunner() {
|
|
56
|
+
return new NornAgentRunner({
|
|
57
|
+
id: this.id,
|
|
58
|
+
runRoot: this.input.runRoot,
|
|
59
|
+
boundaryRoot: this.boundaryRoot,
|
|
60
|
+
boundaryName: this.input.isolationMode,
|
|
61
|
+
cwd: this.cwd,
|
|
62
|
+
signal: this.input.signal,
|
|
63
|
+
model: this.input.model,
|
|
64
|
+
thinkingLevel: this.input.thinkingLevel,
|
|
65
|
+
agentDir: this.input.agentDir,
|
|
66
|
+
logs: this.input.logs,
|
|
67
|
+
logger: this.input.logger,
|
|
68
|
+
responseCollector: this.input.responseCollector
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
path(relativePath) {
|
|
72
|
+
return this.resolveFromRoot(this.cwd, this.boundaryRoot, relativePath);
|
|
73
|
+
}
|
|
74
|
+
projectPath(relativePath) {
|
|
75
|
+
return this.resolveFromRoot(this.projectRoot, this.projectRoot, relativePath);
|
|
76
|
+
}
|
|
77
|
+
next(workflow, params) {
|
|
78
|
+
return {
|
|
79
|
+
type: "next",
|
|
80
|
+
workflowId: typeof workflow === "string" ? workflow : workflow.id,
|
|
81
|
+
params
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
complete(metadata) {
|
|
85
|
+
return { type: "complete", metadata };
|
|
86
|
+
}
|
|
87
|
+
fail(metadata) {
|
|
88
|
+
return { type: "fail", metadata };
|
|
89
|
+
}
|
|
90
|
+
resolveFromRoot(root, boundaryRoot, path) {
|
|
91
|
+
const resolvedPath = isAbsolute(path) ? path : resolve(root, path);
|
|
92
|
+
const pathFromBoundary = relative(boundaryRoot, resolvedPath);
|
|
93
|
+
if (pathFromBoundary === ".." || pathFromBoundary.startsWith(`..${sep}`) || isAbsolute(pathFromBoundary)) {
|
|
94
|
+
throw new Error(`Workflow path escapes ${this.input.isolationMode} isolation: ${path}`);
|
|
95
|
+
}
|
|
96
|
+
return resolvedPath;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
export {
|
|
100
|
+
NornRunContext
|
|
101
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NornWorkflowStateDefinition, NornWorkflowState } from "@vimhead.dev/norn";
|
|
2
|
+
import type { NornFileCoordinator } from "@vimhead.dev/norn/files";
|
|
3
|
+
export declare class NornMemoryWorkflowState implements NornWorkflowState {
|
|
4
|
+
private readonly data;
|
|
5
|
+
get<T>(state: NornWorkflowStateDefinition<T>): Promise<T>;
|
|
6
|
+
getOptional<T>(state: NornWorkflowStateDefinition<T>): Promise<T | undefined>;
|
|
7
|
+
set<T>(state: NornWorkflowStateDefinition<T>, value: T): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare class NornJsonWorkflowState implements NornWorkflowState {
|
|
10
|
+
private readonly input;
|
|
11
|
+
readonly stateFile: string;
|
|
12
|
+
constructor(input: {
|
|
13
|
+
readonly stateFile: string;
|
|
14
|
+
readonly files: NornFileCoordinator;
|
|
15
|
+
readonly coordinateMutation: <T>(path: string, operation: () => Promise<T>) => Promise<T>;
|
|
16
|
+
});
|
|
17
|
+
initialize(mode: "create" | "open"): Promise<void>;
|
|
18
|
+
get<T>(state: NornWorkflowStateDefinition<T>): Promise<T>;
|
|
19
|
+
getOptional<T>(state: NornWorkflowStateDefinition<T>): Promise<T | undefined>;
|
|
20
|
+
set<T>(state: NornWorkflowStateDefinition<T>, value: T): Promise<void>;
|
|
21
|
+
private readStateFile;
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/internal/state-store.ts
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
// ../core/src/errors.ts
|
|
5
|
+
function isNodeError(error) {
|
|
6
|
+
return error instanceof Error && "code" in error;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// ../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
|
+
async function writeJsonAtomically(path, value) {
|
|
14
|
+
await writeTextAtomically(path, `${JSON.stringify(value, null, 2)}
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
async function writeTextAtomically(path, content) {
|
|
18
|
+
await mkdir(dirname(path), { recursive: true });
|
|
19
|
+
let existingMode;
|
|
20
|
+
try {
|
|
21
|
+
existingMode = (await stat(path)).mode & 511;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
24
|
+
}
|
|
25
|
+
const tmpPath = `${path}.${randomUUID()}.tmp`;
|
|
26
|
+
try {
|
|
27
|
+
await writeFile(tmpPath, content, { encoding: "utf8", mode: existingMode });
|
|
28
|
+
if (existingMode !== void 0) await chmod(tmpPath, existingMode);
|
|
29
|
+
await rename(tmpPath, path);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
await rm(tmpPath, { force: true }).catch(() => void 0);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/internal/state-store.ts
|
|
37
|
+
var NornMemoryWorkflowState = class {
|
|
38
|
+
data = /* @__PURE__ */ new Map();
|
|
39
|
+
async get(state) {
|
|
40
|
+
const value = await this.getOptional(state);
|
|
41
|
+
if (value === void 0) throw new Error(`Missing workflow state: ${state.id}`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
async getOptional(state) {
|
|
45
|
+
if (!this.data.has(state.id)) return void 0;
|
|
46
|
+
return state.schema.parse(this.data.get(state.id));
|
|
47
|
+
}
|
|
48
|
+
async set(state, value) {
|
|
49
|
+
this.data.set(state.id, state.schema.parse(value));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var NornJsonWorkflowState = class {
|
|
53
|
+
constructor(input) {
|
|
54
|
+
this.input = input;
|
|
55
|
+
this.stateFile = input.stateFile;
|
|
56
|
+
}
|
|
57
|
+
input;
|
|
58
|
+
stateFile;
|
|
59
|
+
async initialize(mode) {
|
|
60
|
+
await this.input.files.withExclusiveLock(this.stateFile, async (path) => {
|
|
61
|
+
try {
|
|
62
|
+
await this.readStateFile(path);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (mode !== "create" || !isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
65
|
+
await writeJsonAtomically(path, {});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async get(state) {
|
|
70
|
+
const value = await this.getOptional(state);
|
|
71
|
+
if (value === void 0) throw new Error(`Missing workflow state: ${state.id}`);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
async getOptional(state) {
|
|
75
|
+
const data = await this.input.files.withExclusiveLock(this.stateFile, (path) => this.readStateFile(path));
|
|
76
|
+
if (!Object.prototype.hasOwnProperty.call(data, state.id)) return void 0;
|
|
77
|
+
return state.schema.parse(data[state.id]);
|
|
78
|
+
}
|
|
79
|
+
async set(state, value) {
|
|
80
|
+
const parsedValue = state.schema.parse(value);
|
|
81
|
+
await this.input.coordinateMutation(this.stateFile, () => this.input.files.withExclusiveLock(this.stateFile, async (path) => {
|
|
82
|
+
const data = await this.readStateFile(path);
|
|
83
|
+
Object.defineProperty(data, state.id, { value: parsedValue, enumerable: true, configurable: true, writable: true });
|
|
84
|
+
await writeJsonAtomically(path, data);
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
async readStateFile(path) {
|
|
88
|
+
const content = await readFile(path, "utf8");
|
|
89
|
+
const parsed = JSON.parse(content);
|
|
90
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
91
|
+
throw new Error(`Invalid workflow state document: ${path}`);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
export {
|
|
95
|
+
NornJsonWorkflowState,
|
|
96
|
+
NornMemoryWorkflowState
|
|
97
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NornAgentUsage } from "@vimhead.dev/norn";
|
|
2
|
+
export declare function agentUsageFromValue(value: unknown): NornAgentUsage | undefined;
|
|
3
|
+
export declare function totalAgentUsage(usages: readonly NornAgentUsage[]): NornAgentUsage;
|
|
4
|
+
export declare function addAgentUsage(left: NornAgentUsage, right: NornAgentUsage): NornAgentUsage;
|
|
5
|
+
export declare function emptyAgentUsage(): NornAgentUsage;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/internal/usage.ts
|
|
2
|
+
function agentUsageFromValue(value) {
|
|
3
|
+
if (!value || typeof value !== "object") return void 0;
|
|
4
|
+
const usage = value;
|
|
5
|
+
const input = numberField(usage.input);
|
|
6
|
+
const output = numberField(usage.output);
|
|
7
|
+
const cacheRead = numberField(usage.cacheRead);
|
|
8
|
+
const cacheWrite = numberField(usage.cacheWrite);
|
|
9
|
+
const reasoning = optionalNumberField(usage.reasoning);
|
|
10
|
+
const totalTokens = numberField(usage.totalTokens) || input + output + cacheRead + cacheWrite;
|
|
11
|
+
const costValue = usage.cost && typeof usage.cost === "object" ? usage.cost : {};
|
|
12
|
+
return {
|
|
13
|
+
input,
|
|
14
|
+
output,
|
|
15
|
+
cacheRead,
|
|
16
|
+
cacheWrite,
|
|
17
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
18
|
+
totalTokens,
|
|
19
|
+
cost: {
|
|
20
|
+
input: numberField(costValue.input),
|
|
21
|
+
output: numberField(costValue.output),
|
|
22
|
+
cacheRead: numberField(costValue.cacheRead),
|
|
23
|
+
cacheWrite: numberField(costValue.cacheWrite),
|
|
24
|
+
total: numberField(costValue.total)
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function totalAgentUsage(usages) {
|
|
29
|
+
return usages.reduce(addAgentUsage, emptyAgentUsage());
|
|
30
|
+
}
|
|
31
|
+
function addAgentUsage(left, right) {
|
|
32
|
+
const reasoning = left.reasoning === void 0 && right.reasoning === void 0 ? void 0 : (left.reasoning ?? 0) + (right.reasoning ?? 0);
|
|
33
|
+
return {
|
|
34
|
+
input: left.input + right.input,
|
|
35
|
+
output: left.output + right.output,
|
|
36
|
+
cacheRead: left.cacheRead + right.cacheRead,
|
|
37
|
+
cacheWrite: left.cacheWrite + right.cacheWrite,
|
|
38
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
39
|
+
totalTokens: left.totalTokens + right.totalTokens,
|
|
40
|
+
cost: {
|
|
41
|
+
input: left.cost.input + right.cost.input,
|
|
42
|
+
output: left.cost.output + right.cost.output,
|
|
43
|
+
cacheRead: left.cost.cacheRead + right.cost.cacheRead,
|
|
44
|
+
cacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,
|
|
45
|
+
total: left.cost.total + right.cost.total
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function emptyAgentUsage() {
|
|
50
|
+
return {
|
|
51
|
+
input: 0,
|
|
52
|
+
output: 0,
|
|
53
|
+
cacheRead: 0,
|
|
54
|
+
cacheWrite: 0,
|
|
55
|
+
totalTokens: 0,
|
|
56
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function numberField(value) {
|
|
60
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
61
|
+
}
|
|
62
|
+
function optionalNumberField(value) {
|
|
63
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
addAgentUsage,
|
|
67
|
+
agentUsageFromValue,
|
|
68
|
+
emptyAgentUsage,
|
|
69
|
+
totalAgentUsage
|
|
70
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type NornAnyWorkflowDeclaration, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornDispose, type NornRunFail, type NornWorkflowImplementation, type NornRunNext, type NornRunFor, type NornWorkflowPluginInfo } from "@vimhead.dev/norn";
|
|
3
|
+
export type NornRegisteredWorkflow = {
|
|
4
|
+
workflow: NornAnyWorkflowDeclaration;
|
|
5
|
+
implementation: NornWorkflowImplementation<NornAnyWorkflowDeclaration, unknown>;
|
|
6
|
+
configSchema?: z.ZodType;
|
|
7
|
+
config: unknown;
|
|
8
|
+
plugin?: NornWorkflowPluginInfo;
|
|
9
|
+
};
|
|
10
|
+
export type NornWorkflowStepResult = NornRunNext | {
|
|
11
|
+
readonly type: "complete";
|
|
12
|
+
readonly workflow: NornAnyWorkflowDeclaration;
|
|
13
|
+
readonly metadata?: NornRunComplete["metadata"];
|
|
14
|
+
} | {
|
|
15
|
+
readonly type: "fail";
|
|
16
|
+
readonly workflow: NornAnyWorkflowDeclaration;
|
|
17
|
+
readonly metadata: NornRunFail["metadata"];
|
|
18
|
+
};
|
|
19
|
+
export declare class NornWorkflowRegistry {
|
|
20
|
+
private readonly entries;
|
|
21
|
+
register<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, implementation: NornWorkflowImplementation<TWorkflow, unknown>, metadata?: {
|
|
22
|
+
readonly plugin?: NornWorkflowPluginInfo;
|
|
23
|
+
readonly configSchema?: z.ZodType;
|
|
24
|
+
readonly config?: unknown;
|
|
25
|
+
}): NornDispose;
|
|
26
|
+
list(options?: {
|
|
27
|
+
readonly entrypointsOnly?: boolean;
|
|
28
|
+
}): NornRegisteredWorkflowInfo[];
|
|
29
|
+
inspect(workflowId: string): NornInspectedWorkflowInfo | undefined;
|
|
30
|
+
launchableEntries(): NornRegisteredWorkflow[];
|
|
31
|
+
workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined;
|
|
32
|
+
describeGate<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, params: unknown, configOverride?: unknown): Promise<string>;
|
|
33
|
+
execute<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, params: unknown, configOverride?: unknown): Promise<NornWorkflowStepResult>;
|
|
34
|
+
private sortedEntries;
|
|
35
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/internal/workflow-registry.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isWorkflowComplete, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
|
|
4
|
+
import { assertWorkflowMetadata, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
|
|
5
|
+
var NornWorkflowRegistry = class {
|
|
6
|
+
entries = /* @__PURE__ */ new Map();
|
|
7
|
+
register(workflow, implementation, metadata = {}) {
|
|
8
|
+
if (this.entries.has(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
|
|
9
|
+
const entry = {
|
|
10
|
+
workflow,
|
|
11
|
+
implementation,
|
|
12
|
+
configSchema: metadata.configSchema,
|
|
13
|
+
config: metadata.configSchema ? metadata.configSchema.parse(defaultConfigInput(metadata.configSchema, metadata.config)) : void 0,
|
|
14
|
+
plugin: metadata.plugin
|
|
15
|
+
};
|
|
16
|
+
assertWorkflowMetadata(workflow);
|
|
17
|
+
assertGateWorkflow(workflow);
|
|
18
|
+
this.entries.set(workflow.id, entry);
|
|
19
|
+
return () => {
|
|
20
|
+
if (this.entries.get(workflow.id) === entry) this.entries.delete(workflow.id);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
list(options = {}) {
|
|
24
|
+
const entries = options.entrypointsOnly ? this.launchableEntries() : this.sortedEntries();
|
|
25
|
+
return entries.map((entry) => workflowInfo(entry));
|
|
26
|
+
}
|
|
27
|
+
inspect(workflowId) {
|
|
28
|
+
const entry = this.entries.get(workflowId);
|
|
29
|
+
return entry ? inspectedWorkflowInfo(entry) : void 0;
|
|
30
|
+
}
|
|
31
|
+
launchableEntries() {
|
|
32
|
+
return this.sortedEntries().filter(({ workflow }) => workflow.isEntrypoint);
|
|
33
|
+
}
|
|
34
|
+
workflowById(workflowId) {
|
|
35
|
+
return this.entries.get(workflowId)?.workflow;
|
|
36
|
+
}
|
|
37
|
+
async describeGate(workflow, run, params, configOverride) {
|
|
38
|
+
const entry = this.entries.get(workflow.id);
|
|
39
|
+
if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
|
|
40
|
+
if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
|
|
41
|
+
const parsedParams = workflow.params.parse(params);
|
|
42
|
+
const parsedConfig = parseExecutionConfig(entry, configOverride);
|
|
43
|
+
const description = await entry.implementation.gate?.describe(run, parsedParams, parsedConfig);
|
|
44
|
+
return validateGateDescription(description ?? workflow.id, workflow.id);
|
|
45
|
+
}
|
|
46
|
+
async execute(workflow, run, params, configOverride) {
|
|
47
|
+
const entry = this.entries.get(workflow.id);
|
|
48
|
+
if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
|
|
49
|
+
const parsedParams = workflow.params.parse(params);
|
|
50
|
+
const parsedConfig = parseExecutionConfig(entry, configOverride);
|
|
51
|
+
const result = await entry.implementation.execute(run, parsedParams, parsedConfig);
|
|
52
|
+
if (isWorkflowNext(result)) return result;
|
|
53
|
+
if (isWorkflowComplete(result)) return { type: "complete", workflow, metadata: result.metadata };
|
|
54
|
+
if (isWorkflowFail(result)) return { type: "fail", workflow, metadata: result.metadata };
|
|
55
|
+
throw new Error(`Workflow returned invalid control result: ${workflow.id}`);
|
|
56
|
+
}
|
|
57
|
+
sortedEntries() {
|
|
58
|
+
return Array.from(this.entries.values()).sort((left, right) => left.workflow.id.localeCompare(right.workflow.id));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
function defaultConfigInput(configSchema, config) {
|
|
62
|
+
if (config !== void 0) return config;
|
|
63
|
+
return schemaType(unwrapSchema(configSchema)) === "object" ? {} : void 0;
|
|
64
|
+
}
|
|
65
|
+
function parseExecutionConfig(entry, configOverride) {
|
|
66
|
+
const pluginConfigOverride = pluginConfigOverrideInput(entry, configOverride);
|
|
67
|
+
if (!entry.configSchema) {
|
|
68
|
+
if (pluginConfigOverride !== void 0) throw new Error(`Run config override provided for plugin without config schema: ${entry.plugin?.id ?? entry.workflow.id}`);
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
if (pluginConfigOverride === void 0) return entry.config;
|
|
72
|
+
const rawConfig = isPlainObject(entry.config) && isPlainObject(pluginConfigOverride) ? deepMerge(entry.config, pluginConfigOverride) : pluginConfigOverride;
|
|
73
|
+
return entry.configSchema.parse(rawConfig);
|
|
74
|
+
}
|
|
75
|
+
function pluginConfigOverrideInput(entry, configOverride) {
|
|
76
|
+
if (configOverride === void 0) return void 0;
|
|
77
|
+
if (!isPlainObject(configOverride)) throw new Error("Run config override must be an object keyed by plugin id");
|
|
78
|
+
const pluginId = entry.plugin?.id;
|
|
79
|
+
return pluginId ? configOverride[pluginId] : void 0;
|
|
80
|
+
}
|
|
81
|
+
function deepMerge(base, override) {
|
|
82
|
+
const result = { ...base };
|
|
83
|
+
for (const [key, value] of Object.entries(override)) {
|
|
84
|
+
const baseValue = result[key];
|
|
85
|
+
result[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function assertGateWorkflow(workflow) {
|
|
90
|
+
if (!workflow.gate) return;
|
|
91
|
+
if (workflow.gate.enabled !== true) throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
|
|
92
|
+
if (!workflow.gate.fields) return;
|
|
93
|
+
const paramsSchema = unwrapSchema(workflow.params);
|
|
94
|
+
if (schemaType(paramsSchema) !== "object") throw new Error(`Workflow gate fields require object params: ${workflow.id}`);
|
|
95
|
+
const paramsShape = schemaShape(paramsSchema);
|
|
96
|
+
for (const field of workflow.gate.fields) {
|
|
97
|
+
if (!Object.prototype.hasOwnProperty.call(paramsShape, field)) throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function validateGateDescription(description, workflowId) {
|
|
101
|
+
const trimmed = description.trim();
|
|
102
|
+
if (trimmed.length === 0) throw new Error(`Workflow gate description must not be empty: ${workflowId}`);
|
|
103
|
+
return trimmed;
|
|
104
|
+
}
|
|
105
|
+
function inspectedWorkflowInfo(entry) {
|
|
106
|
+
return {
|
|
107
|
+
...workflowInfo(entry),
|
|
108
|
+
paramsSchema: jsonSchema(entry.workflow.params),
|
|
109
|
+
gate: gateInfo(entry.workflow)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function workflowInfo(entry) {
|
|
113
|
+
return {
|
|
114
|
+
id: entry.workflow.id,
|
|
115
|
+
instructions: entry.workflow.instructions,
|
|
116
|
+
isEntrypoint: entry.workflow.isEntrypoint,
|
|
117
|
+
isolation: entry.workflow.isolation,
|
|
118
|
+
plugin: entry.plugin
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function gateInfo(workflow) {
|
|
122
|
+
return workflow.gate ? { enabled: true, fields: workflow.gate.fields } : null;
|
|
123
|
+
}
|
|
124
|
+
function jsonSchema(schema) {
|
|
125
|
+
return z.toJSONSchema(schema, { io: "input" });
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
NornWorkflowRegistry
|
|
129
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type NornProjectInspection, type NornProjectPluginInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowPlugin } from "@vimhead.dev/norn";
|
|
3
|
+
import { NornMemoryWorkflowState } from "./internal/state-store.ts";
|
|
4
|
+
import { NornWorkflowRegistry, type NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
|
|
5
|
+
import { type NornResolvedSeerModeConfig } from "@vimhead.dev/norn/seer";
|
|
6
|
+
export declare const NORN_PROJECT_FILE_NAME = "norn.project.json";
|
|
7
|
+
declare const nornConfigSchema: z.ZodObject<{
|
|
8
|
+
plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
|
+
includes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
10
|
+
config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
declare const nornProjectConfigSchema: z.ZodObject<{
|
|
13
|
+
plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14
|
+
includes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
15
|
+
config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
16
|
+
version: z.ZodDefault<z.ZodLiteral<1>>;
|
|
17
|
+
seerMode: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
writableRoots: z.ZodArray<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
type NornConfig = z.output<typeof nornConfigSchema> & {
|
|
22
|
+
readonly seerMode?: never;
|
|
23
|
+
};
|
|
24
|
+
type NornProjectConfig = z.output<typeof nornProjectConfigSchema>;
|
|
25
|
+
type NornConfigFile = {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly root: string;
|
|
28
|
+
readonly config: NornConfig;
|
|
29
|
+
};
|
|
30
|
+
export type NornProject = {
|
|
31
|
+
readonly cwd: string;
|
|
32
|
+
readonly projectPath: string;
|
|
33
|
+
readonly projectRoot: string;
|
|
34
|
+
readonly configPath: string;
|
|
35
|
+
readonly configRoot: string;
|
|
36
|
+
readonly config: NornProjectConfig;
|
|
37
|
+
readonly configFiles: readonly NornConfigFile[];
|
|
38
|
+
readonly projectConfig: Record<string, unknown>;
|
|
39
|
+
readonly seerMode?: NornResolvedSeerModeConfig;
|
|
40
|
+
};
|
|
41
|
+
export type NornLoadedProject = NornProject & {
|
|
42
|
+
readonly plugins: readonly NornWorkflowPlugin[];
|
|
43
|
+
readonly pluginInfos: readonly NornProjectPluginInfo[];
|
|
44
|
+
readonly registry: NornWorkflowRegistry;
|
|
45
|
+
readonly workflows: readonly NornRegisteredWorkflow[];
|
|
46
|
+
readonly state: NornMemoryWorkflowState;
|
|
47
|
+
};
|
|
48
|
+
export declare function loadNornProject(cwd: string): Promise<NornLoadedProject>;
|
|
49
|
+
export declare function discoverNornProject(cwd: string): Promise<NornProjectInspection & NornWorkflowCatalogInfo>;
|
|
50
|
+
export declare function inspectNornWorkflow(input: {
|
|
51
|
+
readonly cwd: string;
|
|
52
|
+
readonly workflowId: string;
|
|
53
|
+
}): Promise<NornWorkflowInspection>;
|
|
54
|
+
export declare function findNornProject(cwd: string): Promise<NornProject>;
|
|
55
|
+
export {};
|