@vimhead.dev/norn-cli 0.1.0-tip.35568631545.1 → 0.1.0-tip.35586725577.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 +3 -2
- package/assets/docs/workflows.md +20 -6
- package/assets/examples/agent-then-analysis/plugin.ts +2 -3
- package/assets/examples/caller-selected-continuation/caller.ts +2 -2
- package/assets/examples/caller-selected-continuation/producer.ts +1 -2
- package/assets/examples/coordinating-multiple-agents/plugin.ts +3 -4
- package/assets/examples/getting-started/plugin.ts +1 -2
- package/assets/examples/minimal-workflow/plugin.ts +1 -2
- package/assets/examples/shared-state/plugin.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +1 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +1 -2
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -2
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +1 -2
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +1 -2
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/workflow-registry.ts +4 -2
- package/assets/packages/sdk/src/api.ts +7 -10
- package/assets/packages/sdk/src/schema.ts +5 -5
- package/assets/tests/workflow-ref.test.ts +1 -1
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/workflow-registry.js +3 -3
- package/package.json +2 -2
package/assets/README.md
CHANGED
|
@@ -20,8 +20,9 @@ through the CLI from any harness. Agents run on the bundled
|
|
|
20
20
|
|
|
21
21
|
const summarize = workflow({
|
|
22
22
|
name: "summarize",
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
entrypoint: {
|
|
24
|
+
instructions: "Summarize staged and unstaged tracked changes relative to HEAD in an absolute repositoryPath. Requires Git and an existing commit; untracked files are excluded. Saves workspace-relative summaryPath and retains the diff log. A nonempty diff is sent to the configured model; an empty diff needs no model call.",
|
|
25
|
+
},
|
|
25
26
|
args: Type.Object({ repositoryPath: Type.String({ minLength: 1 }) }),
|
|
26
27
|
async execute({ args, paths, commands, logs, agents, run }) {
|
|
27
28
|
const diff = await commands.run({
|
package/assets/docs/workflows.md
CHANGED
|
@@ -4,6 +4,21 @@ The Norn SDK is the TypeScript interface for building reusable workflows. A work
|
|
|
4
4
|
|
|
5
5
|
Start with the complete [minimal workflow](../examples/minimal-workflow/plugin.ts) and its [write/run/change exercise](../examples/minimal-workflow/README.md).
|
|
6
6
|
|
|
7
|
+
## Develop agent workflows through execution
|
|
8
|
+
|
|
9
|
+
For agent-driven workflows, trial and error in native Norn runs is the default development method, starting with the first runnable step—not a final smoke test after implementation:
|
|
10
|
+
|
|
11
|
+
**Author → execute real agents → inspect saved evidence → repair → rollback and restore a valid checkpoint → resume → repeat.**
|
|
12
|
+
|
|
13
|
+
| Decision | GOOD | BAD |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| IF a workflow depends on agent behavior, THEN execute it early and frequently through Norn with bounded, representative inputs, using the loop above as the primary development process. ELSE deterministic workflows and helpers can be developed and validated with automated tests. | Exercise a real agent step before building the remaining orchestration; retain unit tests for scoring mathematics. | Build the whole agent workflow around mocked responses and postpone actual execution until the end. |
|
|
16
|
+
| IF inspecting an agent run, THEN compare retained outputs and evidence against the task requirements. ELSE do not claim the workflow works from status or test results alone. | Check a generated report against its source evidence and requested deliverable. | Treat `completed`, schema-valid JSON, or a passing deterministic suite as proof of task correctness. |
|
|
17
|
+
| IF repairing or iterating on an agent workflow, THEN use rollback, checkpoint restoration, and resume frequently to exercise the changed step while preserving valid earlier work. ELSE retain the inspected run as evidence for the unchanged behavior. | Restore the boundary after a valid assessment, repair delivery, and verify the assessment survives the resumed execution. | Restart every agent from scratch, simulate recovery only in tests, or assume restored files are correct without inspecting them. |
|
|
18
|
+
| IF credentials, inputs, access, or authorization block native execution, THEN report the blocker and mark agent behavior unvalidated. ELSE report the actual runs, inspected artifacts, and recovery exercised. | Distinguish passing deterministic checks from a blocked live agent run. | Substitute mocked success for execution or imply an unperformed recovery cycle passed. |
|
|
19
|
+
|
|
20
|
+
Use [source repair and rollback](recovery.md#source-repair-and-rollback) for checkpoint selection and external-effect precautions. The [agent → saved file → analysis repair exercise](../examples/agent-then-analysis/README.md#repair-only-the-analysis-step) demonstrates this loop with a real agent result retained across failure and recovery.
|
|
21
|
+
|
|
7
22
|
## Define a workflow
|
|
8
23
|
|
|
9
24
|
`workflow` declares a complete, typed callable workflow:
|
|
@@ -14,8 +29,7 @@ import { Type } from "typebox";
|
|
|
14
29
|
|
|
15
30
|
export const greet = workflow({
|
|
16
31
|
name: "greet",
|
|
17
|
-
|
|
18
|
-
instructions: "Return a greeting for the supplied name.",
|
|
32
|
+
entrypoint: { instructions: "Return a greeting for the supplied name." },
|
|
19
33
|
args: Type.Object({ name: Type.String() }),
|
|
20
34
|
execute({ args, run }) {
|
|
21
35
|
return run.complete({ summary: `Hello, ${args.name}!` });
|
|
@@ -25,11 +39,11 @@ export const greet = workflow({
|
|
|
25
39
|
export default [greet];
|
|
26
40
|
```
|
|
27
41
|
|
|
28
|
-
Supply `name`, `args`, `
|
|
42
|
+
Supply `name`, `args`, `entrypoint`, and `execute` explicitly. Use `entrypoint: { instructions: "..." }` with nonempty caller-facing instructions, or `entrypoint: false` for an internal step. This controls default catalogue visibility, not authorization: the CLI can start a known internal workflow ID directly. Discovery exposes the derived `isEntrypoint` boolean and `instructions` string; internal steps have no caller instructions.
|
|
29
43
|
|
|
30
44
|
Workflow and scope names must be nonempty and cannot contain dots. A standalone workflow's ID is its name; a scoped workflow's ID is `<scope name>.<workflow name>`. Declarations expose the resolved `id`; CLI commands, references, and `run.next` use that exact ID, without implicit scope lookup.
|
|
31
45
|
|
|
32
|
-
`instructions` describe selection, inputs, effects, and outputs. They are neither a Norn agent system prompt nor a gate decision. Declare args and config with [TypeBox schemas](schemas.md). Workflow inputs must be JSON data; `execute` receives the values after schema defaults and conversions. Public schemas must support `workflows inspect`.
|
|
46
|
+
`entrypoint.instructions` describe selection, inputs, effects, and outputs. They are neither a Norn agent system prompt nor a gate decision. Declare args and config with [TypeBox schemas](schemas.md). Workflow inputs must be JSON data; `execute` receives the values after schema defaults and conversions. Public schemas must support `workflows inspect`.
|
|
33
47
|
|
|
34
48
|
Destructure the properties needed by the step from `execute(context)`. Gate descriptions receive the same inferred context:
|
|
35
49
|
|
|
@@ -79,7 +93,7 @@ export const reports = workflowScope({
|
|
|
79
93
|
|
|
80
94
|
export const save = reports.workflow({
|
|
81
95
|
name: "save",
|
|
82
|
-
|
|
96
|
+
entrypoint: false,
|
|
83
97
|
args: Type.Object({ text: Type.String() }),
|
|
84
98
|
config: Type.Object({ filename: Type.String() }),
|
|
85
99
|
async execute({ args, config, scope, paths, run }) {
|
|
@@ -116,7 +130,7 @@ import { Type } from "typebox";
|
|
|
116
130
|
|
|
117
131
|
const repeat = workflow({
|
|
118
132
|
name: "repeat",
|
|
119
|
-
|
|
133
|
+
entrypoint: false,
|
|
120
134
|
args: Type.Object({ remaining: Type.Integer() }),
|
|
121
135
|
execute({ args, run }): WorkflowResult {
|
|
122
136
|
return args.remaining > 0
|
|
@@ -24,8 +24,7 @@ const analysisSchema = Type.Object({
|
|
|
24
24
|
const scope = workflowScope({ name: "sourceSummary" });
|
|
25
25
|
export const draft = scope.workflow({
|
|
26
26
|
name: "draft",
|
|
27
|
-
|
|
28
|
-
instructions: "Summarize a supplied source, save the draft, and independently assess its support and omissions. Returns workspace-relative draftPath and analysisPath plus an assessment; needs-revision is a completed assessment, not an approved summary.",
|
|
27
|
+
entrypoint: { instructions: "Summarize a supplied source, save the draft, and independently assess its support and omissions. Returns workspace-relative draftPath and analysisPath plus an assessment; needs-revision is a completed assessment, not an approved summary." },
|
|
29
28
|
args: Type.Object({ source: Type.String({ minLength: 1 }) }),
|
|
30
29
|
async execute({ args, paths, agents }) {
|
|
31
30
|
const draft = await agents.prompt({
|
|
@@ -47,7 +46,7 @@ export const draft = scope.workflow({
|
|
|
47
46
|
});
|
|
48
47
|
export const analyze = scope.workflow({
|
|
49
48
|
name: "analyze",
|
|
50
|
-
|
|
49
|
+
entrypoint: false,
|
|
51
50
|
args: Type.Object({ draftPath: Type.String() }),
|
|
52
51
|
async execute({ args, paths, agents, run }) {
|
|
53
52
|
const savedDraft = Value.Parse(savedDraftSchema, JSON.parse(await readFile(join(paths.workspace, args.draftPath), "utf8")));
|
|
@@ -12,7 +12,7 @@ const deliveryArgsSchema = Type.Object({
|
|
|
12
12
|
const scope = workflowScope({ name: "greetingConsumer" });
|
|
13
13
|
export const saveJson = scope.workflow({
|
|
14
14
|
name: "saveJson",
|
|
15
|
-
|
|
15
|
+
entrypoint: false,
|
|
16
16
|
args: deliveryArgsSchema,
|
|
17
17
|
async execute({ args, paths, run }) {
|
|
18
18
|
const greeting = await readFile(join(paths.workspace, args.resultPath), "utf8");
|
|
@@ -30,7 +30,7 @@ export const saveJson = scope.workflow({
|
|
|
30
30
|
});
|
|
31
31
|
export const saveText = scope.workflow({
|
|
32
32
|
name: "saveText",
|
|
33
|
-
|
|
33
|
+
entrypoint: false,
|
|
34
34
|
args: deliveryArgsSchema,
|
|
35
35
|
async execute({ args, paths, run }) {
|
|
36
36
|
const greeting = await readFile(join(paths.workspace, args.resultPath), "utf8");
|
|
@@ -11,8 +11,7 @@ export const greetingContributionSchema = Type.Object({
|
|
|
11
11
|
const scope = workflowScope({ name: "greetingProducer" });
|
|
12
12
|
export const write = scope.workflow({
|
|
13
13
|
name: "write",
|
|
14
|
-
|
|
15
|
-
instructions: "Write a greeting file for name, then invoke the caller-selected next workflow with workspace-relative resultPath and summary. The continuation owns completion; no model or external service is used.",
|
|
14
|
+
entrypoint: { instructions: "Write a greeting file for name, then invoke the caller-selected next workflow with workspace-relative resultPath and summary. The continuation owns completion; no model or external service is used." },
|
|
16
15
|
args: Type.Object({
|
|
17
16
|
name: Type.String({ minLength: 1 }),
|
|
18
17
|
next: workflowRefSchema({ args: greetingContributionSchema }),
|
|
@@ -13,8 +13,7 @@ const workerReportSchema = Type.Object({ status: Type.Enum(["acknowledged", "idl
|
|
|
13
13
|
const scope = workflowScope({ name: "coordinatingAgents" });
|
|
14
14
|
export const start = scope.workflow({
|
|
15
15
|
name: "start",
|
|
16
|
-
|
|
17
|
-
instructions: "Summarize 2–12 supplied notes using two concurrent Norn agents and a shared leased work queue. Checkpoint completed rounds, verify every persisted result and exact source quotation, and return workspace-relative summariesPath for summaries.json. Requires configured Norn agent authentication; modifies only this run's logs and workspace.",
|
|
16
|
+
entrypoint: { instructions: "Summarize 2–12 supplied notes using two concurrent Norn agents and a shared leased work queue. Checkpoint completed rounds, verify every persisted result and exact source quotation, and return workspace-relative summariesPath for summaries.json. Requires configured Norn agent authentication; modifies only this run's logs and workspace." },
|
|
18
17
|
args: inputSchema,
|
|
19
18
|
async execute({ args, paths }) {
|
|
20
19
|
const queue = await openQueue({ workspace: paths.workspace, create: true });
|
|
@@ -28,7 +27,7 @@ export const start = scope.workflow({
|
|
|
28
27
|
});
|
|
29
28
|
export const work = scope.workflow({
|
|
30
29
|
name: "work",
|
|
31
|
-
|
|
30
|
+
entrypoint: false,
|
|
32
31
|
args: Type.Object({ ...inputSchema.properties, round: Type.Integer({ minimum: 0, maximum: 12 }) }, { additionalProperties: false }),
|
|
33
32
|
async execute({ args, paths, agents, run }): Promise<WorkflowResult> {
|
|
34
33
|
const queue = await openQueue({ workspace: paths.workspace, create: false });
|
|
@@ -54,7 +53,7 @@ export const work = scope.workflow({
|
|
|
54
53
|
});
|
|
55
54
|
export const verify = scope.workflow({
|
|
56
55
|
name: "verify",
|
|
57
|
-
|
|
56
|
+
entrypoint: false,
|
|
58
57
|
args: inputSchema,
|
|
59
58
|
async execute({ args, paths, run }) {
|
|
60
59
|
const queue = await openQueue({ workspace: paths.workspace, create: false });
|
|
@@ -5,8 +5,7 @@ import { Type } from "typebox";
|
|
|
5
5
|
|
|
6
6
|
export const summarize = workflow({
|
|
7
7
|
name: "summarize",
|
|
8
|
-
|
|
9
|
-
instructions: "Summarize staged and unstaged tracked changes relative to HEAD in an absolute repositoryPath. Requires Git and an existing commit; untracked files are excluded. Saves workspace-relative summaryPath and retains the diff log. A nonempty diff is sent to the configured model; an empty diff needs no model call.",
|
|
8
|
+
entrypoint: { instructions: "Summarize staged and unstaged tracked changes relative to HEAD in an absolute repositoryPath. Requires Git and an existing commit; untracked files are excluded. Saves workspace-relative summaryPath and retains the diff log. A nonempty diff is sent to the configured model; an empty diff needs no model call." },
|
|
10
9
|
args: Type.Object({ repositoryPath: Type.String({ minLength: 1 }) }),
|
|
11
10
|
async execute({ args, paths, commands, logs, agents, run }) {
|
|
12
11
|
const diff = await commands.run({
|
|
@@ -5,8 +5,7 @@ import { Type } from "typebox";
|
|
|
5
5
|
|
|
6
6
|
export const write = workflow({
|
|
7
7
|
name: "greet",
|
|
8
|
-
|
|
9
|
-
instructions: "Write a greeting file for the supplied name. Returns the greeting text and its workspace-relative greetingPath; no agent or external service is used.",
|
|
8
|
+
entrypoint: { instructions: "Write a greeting file for the supplied name. Returns the greeting text and its workspace-relative greetingPath; no agent or external service is used." },
|
|
10
9
|
args: Type.Object({ name: Type.Decode(Type.String({ pattern: "\\S" }), value => value.trim()) }),
|
|
11
10
|
async execute({ args, paths, run }) {
|
|
12
11
|
const greeting = `Hello, ${args.name}!`;
|
|
@@ -11,8 +11,7 @@ const copyField = { id: "copiedText", schema: Type.String() };
|
|
|
11
11
|
|
|
12
12
|
export const copy = copyScope.workflow({
|
|
13
13
|
name: "copy",
|
|
14
|
-
|
|
15
|
-
instructions: "A Norn agent reads explicitly shared source and writes a copy, then a separate workflow verifies exact equality from a workspace SQLite database.",
|
|
14
|
+
entrypoint: { instructions: "A Norn agent reads explicitly shared source and writes a copy, then a separate workflow verifies exact equality from a workspace SQLite database." },
|
|
16
15
|
args: Type.Object({ source: Type.String({ minLength: 1, maxLength: 500 }) }),
|
|
17
16
|
async execute({ args, paths, agents }) {
|
|
18
17
|
const state = await SharedState.open({ path: join(paths.workspace, "state.sqlite"), create: true });
|
|
@@ -37,7 +36,7 @@ export const copy = copyScope.workflow({
|
|
|
37
36
|
},
|
|
38
37
|
});
|
|
39
38
|
export const verify = copyScope.workflow({
|
|
40
|
-
name: "verify",
|
|
39
|
+
name: "verify", entrypoint: false, args: Type.Object({}),
|
|
41
40
|
async execute({ paths, run }) {
|
|
42
41
|
const state = await SharedState.open({ path: join(paths.workspace, "state.sqlite"), create: false });
|
|
43
42
|
try {
|
|
@@ -6,8 +6,7 @@ import { materializeWorkspaceRepository } from "./repository.ts";
|
|
|
6
6
|
|
|
7
7
|
export const developmentLoopWorkflow = developmentLoopScope.workflow({
|
|
8
8
|
name: "developmentLoop",
|
|
9
|
-
|
|
10
|
-
instructions: "Plan once, then loop implementation and review in a workspace repository copy. Call this when a repository task should run through planning, implementation, and review.",
|
|
9
|
+
entrypoint: { instructions: "Plan once, then loop implementation and review in a workspace repository copy. Call this when a repository task should run through planning, implementation, and review." },
|
|
11
10
|
args: developmentLoopArgsSchema,
|
|
12
11
|
async execute({ args, scope, paths, commands }): Promise<WorkflowResult> {
|
|
13
12
|
const repositoryPath = await materializeWorkspaceRepository({ commands, paths, repositoryRoot: scope.config.repositoryRoot, baseRef: args.baseRef });
|
|
@@ -9,8 +9,7 @@ import { implementationAgentResponseSchema } from "./schema.ts";
|
|
|
9
9
|
|
|
10
10
|
export const implementationWorkflow = developmentLoopScope.workflow({
|
|
11
11
|
name: "implementation",
|
|
12
|
-
|
|
13
|
-
instructions: "Apply one implementation pass in the current repository.",
|
|
12
|
+
entrypoint: false,
|
|
14
13
|
args: implementationArgsSchema,
|
|
15
14
|
async execute({ args, paths, agents, commands, logs }): Promise<WorkflowResult> {
|
|
16
15
|
const repositoryPath = resolve(paths.workspace, args.repositoryPath);
|
|
@@ -8,8 +8,7 @@ import { planningAgentResponseSchema } from "./schema.ts";
|
|
|
8
8
|
|
|
9
9
|
export const planningWorkflow = developmentLoopScope.workflow({
|
|
10
10
|
name: "planning",
|
|
11
|
-
|
|
12
|
-
instructions: "Create an implementation plan for a repository task.",
|
|
11
|
+
entrypoint: false,
|
|
13
12
|
args: planningArgsSchema,
|
|
14
13
|
async execute({ args, paths, agents }): Promise<WorkflowResult> {
|
|
15
14
|
const repositoryPath = resolve(paths.workspace, args.repositoryPath);
|
|
@@ -9,8 +9,7 @@ import { reviewAgentResponseSchema } from "./schema.ts";
|
|
|
9
9
|
|
|
10
10
|
export const reviewWorkflow = developmentLoopScope.workflow({
|
|
11
11
|
name: "review",
|
|
12
|
-
|
|
13
|
-
instructions: "Review the current repository boundary changes.",
|
|
12
|
+
entrypoint: false,
|
|
14
13
|
args: reviewArgsSchema,
|
|
15
14
|
async execute({ args, paths, agents, commands, logs }): Promise<WorkflowResult> {
|
|
16
15
|
const repositoryPath = resolve(paths.workspace, args.repositoryPath);
|
|
@@ -8,8 +8,7 @@ import type { StoredReview } from "../review/schema.ts";
|
|
|
8
8
|
|
|
9
9
|
export const reviewRouterWorkflow = developmentLoopScope.workflow({
|
|
10
10
|
name: "reviewRouter",
|
|
11
|
-
|
|
12
|
-
instructions: "Route implementation and review iterations based on the latest review decision.",
|
|
11
|
+
entrypoint: false,
|
|
13
12
|
gate: {
|
|
14
13
|
enabled: true,
|
|
15
14
|
describe: ({ args }) => `Review iteration ${args.iteration}. Confirm or edit the automated decision before continuing. Plan: ${args.planPath}.`,
|
package/assets/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.1.0-tip.
|
|
1
|
+
{"version":"0.1.0-tip.35586725577.1"}
|
|
@@ -2,8 +2,8 @@ import type { NornBuildInfo } from "./build-info.ts";
|
|
|
2
2
|
|
|
3
3
|
export const NORN_GENERATED_BUILD_INFO = {
|
|
4
4
|
"kind": "npm-registry",
|
|
5
|
-
"version": "0.1.0-tip.
|
|
6
|
-
"commit": "
|
|
5
|
+
"version": "0.1.0-tip.35586725577.1",
|
|
6
|
+
"commit": "9f57baea28085673cd562990813d8b2a2efa02c4",
|
|
7
7
|
"packageSpec": "@vimhead.dev/norn-cli@tip",
|
|
8
8
|
"upgrade": {
|
|
9
9
|
"supported": false,
|
|
@@ -105,7 +105,7 @@ export class NornWorkflowRegistry {
|
|
|
105
105
|
].map(({ configuration, scopeId }) => ({ key: configuration.key, scopeId, configSchema: configuration.schema ? inspectSchema(configuration.schema) : null, config: configuration.value }));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
launchableEntries(): NornRegisteredWorkflow[] { return this.sortedEntries().filter(({ workflow }) => workflow.
|
|
108
|
+
launchableEntries(): NornRegisteredWorkflow[] { return this.sortedEntries().filter(({ workflow }) => workflow.entrypoint !== false); }
|
|
109
109
|
workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined { return this.entries.get(workflowId)?.workflow; }
|
|
110
110
|
|
|
111
111
|
async describeGate(input: NornWorkflowExecutionInput): Promise<string> {
|
|
@@ -189,7 +189,9 @@ function assertGateWorkflow(workflow: NornAnyWorkflowDeclaration): void {
|
|
|
189
189
|
|
|
190
190
|
function workflowInfo(entry: NornRegisteredWorkflow): NornRegisteredWorkflowInfo {
|
|
191
191
|
return {
|
|
192
|
-
id: entry.workflow.id,
|
|
192
|
+
id: entry.workflow.id,
|
|
193
|
+
instructions: entry.workflow.entrypoint === false ? undefined : entry.workflow.entrypoint.instructions,
|
|
194
|
+
isEntrypoint: entry.workflow.entrypoint !== false,
|
|
193
195
|
source: entry.source,
|
|
194
196
|
configKey: entry.workflow.id,
|
|
195
197
|
scope: entry.scopeId ? { id: entry.scopeId } : undefined,
|
|
@@ -32,8 +32,7 @@ export type NornWorkflowDeclaration<
|
|
|
32
32
|
(args: StaticEncode<ArgsSchema>): NornRunNext;
|
|
33
33
|
readonly kind: typeof WORKFLOW_DECLARATION_KIND;
|
|
34
34
|
readonly id: Id;
|
|
35
|
-
readonly
|
|
36
|
-
readonly instructions?: string;
|
|
35
|
+
readonly entrypoint: NornWorkflowEntrypoint;
|
|
37
36
|
readonly args: ArgsSchema;
|
|
38
37
|
readonly gate?: NornWorkflowGate<ArgsSchema> & {
|
|
39
38
|
describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
|
|
@@ -65,9 +64,7 @@ export type NornWorkflowGate<ArgsSchema extends TSchema> = unknown extends Stati
|
|
|
65
64
|
readonly fields?: never;
|
|
66
65
|
};
|
|
67
66
|
|
|
68
|
-
type
|
|
69
|
-
| { readonly isEntrypoint: true; readonly instructions: string }
|
|
70
|
-
| { readonly isEntrypoint: false; readonly instructions?: string };
|
|
67
|
+
export type NornWorkflowEntrypoint = false | { readonly instructions: string };
|
|
71
68
|
|
|
72
69
|
type WorkflowConfig<Schema extends TSchema | undefined> = Schema extends TSchema ? StaticDecode<Schema> : undefined;
|
|
73
70
|
|
|
@@ -94,13 +91,14 @@ export type NornWorkflowDefinition<
|
|
|
94
91
|
Scope extends NornWorkflowScopeInfo | undefined = undefined,
|
|
95
92
|
> = {
|
|
96
93
|
readonly name: Name;
|
|
94
|
+
readonly entrypoint: NornWorkflowEntrypoint;
|
|
97
95
|
readonly args: ArgsSchema;
|
|
98
96
|
readonly config?: ConfigSchema;
|
|
99
97
|
readonly gate?: NornWorkflowGate<ArgsSchema> & {
|
|
100
98
|
describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
|
|
101
99
|
};
|
|
102
100
|
execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
|
|
103
|
-
}
|
|
101
|
+
};
|
|
104
102
|
|
|
105
103
|
export type NornWorkflowScopeInfo = {
|
|
106
104
|
readonly id: string;
|
|
@@ -134,8 +132,8 @@ function createWorkflow<Id extends string, ArgsSchema extends TSchema, ConfigSch
|
|
|
134
132
|
id,
|
|
135
133
|
scope,
|
|
136
134
|
}) as NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, Scope>;
|
|
137
|
-
if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${id}`);
|
|
138
135
|
assertWorkflowMetadata(result);
|
|
136
|
+
if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${id}`);
|
|
139
137
|
return result;
|
|
140
138
|
}
|
|
141
139
|
|
|
@@ -564,13 +562,12 @@ function assertDeclarationName(name: string): void {
|
|
|
564
562
|
|
|
565
563
|
export function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowDeclaration {
|
|
566
564
|
if (typeof value !== "function") return false;
|
|
567
|
-
const candidate = value as { kind?: unknown; id?: unknown;
|
|
565
|
+
const candidate = value as { kind?: unknown; id?: unknown; entrypoint?: unknown; args?: unknown; execute?: unknown };
|
|
568
566
|
return (
|
|
569
567
|
candidate.kind === WORKFLOW_DECLARATION_KIND &&
|
|
570
568
|
typeof candidate.id === "string" &&
|
|
571
569
|
candidate.id.length > 0 &&
|
|
572
|
-
(candidate.
|
|
573
|
-
typeof candidate.isEntrypoint === "boolean" &&
|
|
570
|
+
(candidate.entrypoint === false || (isPlainObject(candidate.entrypoint) && typeof candidate.entrypoint.instructions === "string")) &&
|
|
574
571
|
Boolean(candidate.args) &&
|
|
575
572
|
typeof candidate.execute === "function"
|
|
576
573
|
);
|
|
@@ -43,11 +43,11 @@ function assertWorkflowReferenceAnnotations(schema: unknown): void {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function assertWorkflowMetadata(workflow: NornAnyWorkflowDeclaration): void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
if (
|
|
50
|
-
throw new Error(`
|
|
46
|
+
const { entrypoint } = workflow;
|
|
47
|
+
if (entrypoint === false) return;
|
|
48
|
+
if (!isPlainObject(entrypoint)) throw new Error(`Workflow entrypoint must be false or an object with instructions: ${workflow.id}`);
|
|
49
|
+
if (typeof entrypoint.instructions !== "string" || entrypoint.instructions.trim().length === 0) {
|
|
50
|
+
throw new Error(`Workflow entrypoint.instructions must be a nonempty string: ${workflow.id}`);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -7,7 +7,7 @@ import { expect, expectTypeOf, test } from "vitest";
|
|
|
7
7
|
|
|
8
8
|
const refs = workflowScope({ name: "refs" });
|
|
9
9
|
const target = refs.workflow({
|
|
10
|
-
name: "finish",
|
|
10
|
+
name: "finish", entrypoint: false, args: Type.Object({ report: Type.String() }),
|
|
11
11
|
execute() { throw new Error("A callable must select a transition, not execute it"); },
|
|
12
12
|
});
|
|
13
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const NORN_GENERATED_BUILD_INFO: {
|
|
2
2
|
readonly kind: "npm-registry";
|
|
3
|
-
readonly version: "0.1.0-tip.
|
|
4
|
-
readonly commit: "
|
|
3
|
+
readonly version: "0.1.0-tip.35586725577.1";
|
|
4
|
+
readonly commit: "9f57baea28085673cd562990813d8b2a2efa02c4";
|
|
5
5
|
readonly packageSpec: "@vimhead.dev/norn-cli@tip";
|
|
6
6
|
readonly upgrade: {
|
|
7
7
|
readonly supported: false;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/generated-build-info.ts
|
|
2
2
|
var NORN_GENERATED_BUILD_INFO = {
|
|
3
3
|
"kind": "npm-registry",
|
|
4
|
-
"version": "0.1.0-tip.
|
|
5
|
-
"commit": "
|
|
4
|
+
"version": "0.1.0-tip.35586725577.1",
|
|
5
|
+
"commit": "9f57baea28085673cd562990813d8b2a2efa02c4",
|
|
6
6
|
"packageSpec": "@vimhead.dev/norn-cli@tip",
|
|
7
7
|
"upgrade": {
|
|
8
8
|
"supported": false,
|
|
@@ -76,7 +76,7 @@ var NornWorkflowRegistry = class {
|
|
|
76
76
|
].map(({ configuration, scopeId }) => ({ key: configuration.key, scopeId, configSchema: configuration.schema ? inspectSchema(configuration.schema) : null, config: configuration.value }));
|
|
77
77
|
}
|
|
78
78
|
launchableEntries() {
|
|
79
|
-
return this.sortedEntries().filter(({ workflow }) => workflow.
|
|
79
|
+
return this.sortedEntries().filter(({ workflow }) => workflow.entrypoint !== false);
|
|
80
80
|
}
|
|
81
81
|
workflowById(workflowId) {
|
|
82
82
|
return this.entries.get(workflowId)?.workflow;
|
|
@@ -156,8 +156,8 @@ function assertGateWorkflow(workflow) {
|
|
|
156
156
|
function workflowInfo(entry) {
|
|
157
157
|
return {
|
|
158
158
|
id: entry.workflow.id,
|
|
159
|
-
instructions: entry.workflow.instructions,
|
|
160
|
-
isEntrypoint: entry.workflow.
|
|
159
|
+
instructions: entry.workflow.entrypoint === false ? void 0 : entry.workflow.entrypoint.instructions,
|
|
160
|
+
isEntrypoint: entry.workflow.entrypoint !== false,
|
|
161
161
|
source: entry.source,
|
|
162
162
|
configKey: entry.workflow.id,
|
|
163
163
|
scope: entry.scopeId ? { id: entry.scopeId } : void 0
|
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.35586725577.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",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
44
44
|
"jiti": "^2.7.0",
|
|
45
45
|
"typebox": "^1.3.34",
|
|
46
|
-
"@vimhead.dev/norn": "0.1.0-tip.
|
|
46
|
+
"@vimhead.dev/norn": "0.1.0-tip.35586725577.1"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "node ../../scripts/build-package.ts"
|