@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.
Files changed (88) hide show
  1. package/assets/README.md +2 -24
  2. package/assets/docs/README.md +3 -1
  3. package/assets/docs/agents.md +3 -3
  4. package/assets/docs/cli.md +7 -7
  5. package/assets/docs/composition.md +64 -34
  6. package/assets/docs/persistence.md +4 -2
  7. package/assets/docs/projects.md +3 -12
  8. package/assets/{setup → docs}/providers.md +4 -3
  9. package/assets/docs/resources.md +7 -7
  10. package/assets/docs/schemas.md +130 -0
  11. package/assets/docs/workflows.md +4 -3
  12. package/assets/examples/agent-then-analysis/README.md +1 -1
  13. package/assets/examples/agent-then-analysis/plugin.ts +16 -15
  14. package/assets/examples/caller-selected-continuation/README.md +75 -0
  15. package/assets/examples/caller-selected-continuation/caller.ts +47 -0
  16. package/assets/examples/caller-selected-continuation/input.json +9 -0
  17. package/assets/examples/caller-selected-continuation/norn.project.json +4 -0
  18. package/assets/examples/caller-selected-continuation/producer.ts +32 -0
  19. package/assets/examples/coordinating-multiple-agents/README.md +1 -1
  20. package/assets/examples/coordinating-multiple-agents/plugin.ts +10 -11
  21. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +3 -4
  22. package/assets/examples/coordinating-multiple-agents/work-queue.ts +18 -18
  23. package/assets/examples/minimal-workflow/plugin.ts +2 -2
  24. package/assets/examples/shared-state/README.md +1 -1
  25. package/assets/examples/shared-state/plugin.ts +6 -6
  26. package/assets/examples/worktree-development-loop/README.md +105 -44
  27. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  28. package/assets/examples/worktree-development-loop/state.ts +6 -6
  29. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +1 -1
  30. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +9 -9
  31. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +1 -1
  32. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -7
  33. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  34. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +7 -7
  35. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +1 -1
  36. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +11 -11
  37. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +1 -1
  38. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +5 -5
  39. package/assets/package.json +1 -1
  40. package/assets/packages/cli/src/cli.ts +15 -32
  41. package/assets/packages/cli/src/client.ts +0 -7
  42. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  43. package/assets/packages/cli/src/internal/agent-response-tool.ts +9 -10
  44. package/assets/packages/cli/src/internal/agents.ts +17 -16
  45. package/assets/packages/cli/src/internal/engine.ts +20 -18
  46. package/assets/packages/cli/src/internal/errors.ts +4 -10
  47. package/assets/packages/cli/src/internal/run-state.ts +4 -0
  48. package/assets/packages/cli/src/internal/run.ts +4 -7
  49. package/assets/packages/cli/src/internal/state-store.ts +22 -14
  50. package/assets/packages/cli/src/internal/workflow-registry.ts +27 -21
  51. package/assets/packages/cli/src/plugin-loader.ts +33 -45
  52. package/assets/packages/cli/src/resources.ts +14 -12
  53. package/assets/packages/core/src/workflow-transition.ts +4 -0
  54. package/assets/packages/sdk/src/api.ts +87 -131
  55. package/assets/packages/sdk/src/files.ts +11 -10
  56. package/assets/packages/sdk/src/index.ts +0 -1
  57. package/assets/packages/sdk/src/resources.ts +2 -2
  58. package/assets/packages/sdk/src/schema.ts +53 -29
  59. package/assets/packages/sdk/src/state-adapter.ts +4 -4
  60. package/assets/tests/workflow-ref.test.ts +121 -77
  61. package/dist/cli.js +20 -36
  62. package/dist/client.d.ts +0 -4
  63. package/dist/client.js +0 -3
  64. package/dist/generated-build-info.d.ts +2 -2
  65. package/dist/generated-build-info.js +2 -2
  66. package/dist/internal/agent-response-tool.d.ts +2 -2
  67. package/dist/internal/agent-response-tool.js +7 -7
  68. package/dist/internal/agents.d.ts +2 -2
  69. package/dist/internal/agents.js +10 -10
  70. package/dist/internal/engine.d.ts +1 -1
  71. package/dist/internal/engine.js +14 -12
  72. package/dist/internal/errors.d.ts +2 -1
  73. package/dist/internal/errors.js +5 -8
  74. package/dist/internal/run-state.js +4 -0
  75. package/dist/internal/run.d.ts +2 -2
  76. package/dist/internal/run.js +10 -6
  77. package/dist/internal/state-store.d.ts +8 -7
  78. package/dist/internal/state-store.js +15 -9
  79. package/dist/internal/workflow-registry.d.ts +14 -6
  80. package/dist/internal/workflow-registry.js +20 -15
  81. package/dist/plugin-loader.d.ts +15 -21
  82. package/dist/plugin-loader.js +35 -47
  83. package/dist/resources.d.ts +1 -1
  84. package/dist/resources.js +15 -13
  85. package/package.json +3 -4
  86. package/assets/packages/sdk/src/seer/config.ts +0 -62
  87. package/assets/packages/sdk/src/seer/index.ts +0 -7
  88. package/assets/setup/releases.md +0 -76
@@ -1,113 +1,157 @@
1
+ import { artifactRefSchema, definePluginManifest, isWorkflowDeclaration, isWorkflowPluginManifest, workflowRefSchema, type NornWorkflowRefSchemaOptions } from "@vimhead.dev/norn";
2
+ import { inspectSchema } from "@vimhead.dev/norn/schema";
1
3
  import assert from "node:assert/strict";
2
- import { expect, test } from "vitest";
3
- import { z } from "zod";
4
+ import { Type } from "typebox";
5
+ import { Value } from "typebox/value";
6
+ import { expect, expectTypeOf, test } from "vitest";
4
7
 
5
- import { artifactRefSchema, definePluginManifest, workflowRefSchema } from "@vimhead.dev/norn";
8
+ const manifest = definePluginManifest({
9
+ id: "refs", workflows: { finish: { isEntrypoint: false, params: Type.Object({ report: Type.String() }) } },
10
+ });
11
+ const target = manifest.workflows.finish;
6
12
 
7
- const target = definePluginManifest({
8
- id: "refs", workflows: { finish: { isEntrypoint: false, params: z.object({ report: z.string() }) } },
9
- }).workflows.finish;
13
+ test("callable declarations retain metadata and construct transitions without execution", () => {
14
+ expectTypeOf(target.id).toEqualTypeOf<"refs.finish">();
15
+ assert.equal(isWorkflowDeclaration(target), true);
16
+ assert.equal(isWorkflowPluginManifest(manifest), true);
17
+ assert.equal(isWorkflowDeclaration(() => undefined), false);
18
+ assert.equal(Value.Check(target.params, { report: "done" }), true);
19
+ assert.deepEqual(target.isolation, { mode: "runWorkspace" });
20
+ assert.deepEqual(target({ report: "done" }), { type: "next", workflowId: target.id, params: { report: "done" } });
21
+ });
10
22
 
11
- for (const reference of ["refs.finish", { id: "refs.finish" }, target]) {
12
- test(`workflow references normalize ${typeof reference === "string" ? "strings" : "kind" in reference ? "declarations" : "id objects"}`, () => {
13
- const schema = workflowRefSchema({ params: z.object({ report: z.string() }) });
14
- assert.deepEqual(schema.parse(reference), { workflow: "refs.finish", forwardParams: {} });
15
- assert.deepEqual(schema.parse({ workflow: reference, forwardParams: { task: "retain" } }), {
16
- workflow: "refs.finish", forwardParams: { task: "retain" },
17
- });
23
+ for (const reference of ["refs.finish", target.id]) {
24
+ test("workflow references decode into contribution-taking transition builders", () => {
25
+ const contributions = Type.Object({ report: Type.String() });
26
+ const options: NornWorkflowRefSchemaOptions<typeof contributions> = { params: contributions };
27
+ const schema = workflowRefSchema(options);
28
+ assert.deepEqual(Value.Decode(workflowRefSchema({}), reference)({}), { type: "next", workflowId: "refs.finish", params: {} });
29
+ interface Report { report: string }
30
+ const report: Report = { report: "done" };
31
+ assert.deepEqual(Value.Decode(schema, reference)(report), target(report));
32
+ const input = { workflow: reference, forwardParams: { task: "retain", report: "stale" } };
33
+ const next = Value.Decode(schema, input);
34
+ assert.deepEqual(next({ report: "done" }), { type: "next", workflowId: reference, params: { task: "retain", report: "done" } });
35
+ assert.deepEqual(next({ report: "again" }).params, { task: "retain", report: "again" });
36
+ assert.deepEqual(input.forwardParams, { task: "retain", report: "stale" });
37
+ assert.throws(() => Reflect.apply(next, undefined, [{}]));
18
38
  });
19
39
  }
20
40
 
21
- test("required and nullable reference inputs remain required in JSON inspection", () => {
22
- const schema = z.object({ next: workflowRefSchema().nullable() });
23
- const inspected = z.toJSONSchema(schema, { io: "input" });
41
+ test("nullable reference inputs retain their required property and input union", () => {
42
+ const schema = Type.Object({ next: Type.Union([workflowRefSchema(), Type.Null()]) });
43
+ const inspected = inspectSchema(schema);
24
44
  assert.deepEqual(inspected.required, ["next"]);
25
- assert.equal(schema.safeParse({}).success, false);
26
- assert.equal(schema.safeParse({ next: null }).success, true);
27
- assert.equal(schema.safeParse({ next: "refs.finish" }).success, true);
28
- expect(inspected).toHaveProperty("properties.next.anyOf.0.anyOf.0.anyOf.0.type", "string");
45
+ assert.equal(Value.Check(schema, {}), false);
46
+ assert.equal(Value.Check(schema, { next: null }), true);
47
+ assert.equal(Value.Check(schema, { next: "refs.finish" }), true);
48
+ expect(inspected).toHaveProperty("properties.next.anyOf.0.anyOf.0.type", "string");
29
49
  expect(inspected).toHaveProperty("properties.next.anyOf.0.anyOf.1.required", ["workflow", "forwardParams"]);
30
50
  });
31
51
 
32
- test("optional reference inputs are optional in both validation and inspection", () => {
33
- const schema = z.object({ next: workflowRefSchema().optional() });
34
- assert.deepEqual(schema.parse({}), {});
35
- assert.equal(z.toJSONSchema(schema, { io: "input" }).required?.includes("next") ?? false, false);
52
+ test("optional reference properties remain optional in validation and inspection", () => {
53
+ const schema = Type.Object({ next: Type.Optional(workflowRefSchema()) });
54
+ assert.deepEqual(Value.Decode(schema, {}), {});
55
+ assert.equal(inspectSchema(schema).required, undefined);
36
56
  });
37
57
 
38
- test("inspection advertises contributions alongside the unchanged reference and forward-params input schema", () => {
39
- const contributions = z.object({
58
+ test("inspection advertises encoded contributions and invocation does not decode them", () => {
59
+ let decodes = 0;
60
+ const contributions = Type.Object({
40
61
  records: artifactRefSchema,
41
- count: z.string().transform(Number),
42
- label: z.string().default("collected"),
62
+ count: Type.Decode(Type.String(), value => { decodes++; return Number(value); }),
63
+ label: Type.Optional(Type.String({ default: "collected" })),
43
64
  });
44
- const reference = workflowRefSchema({ params: contributions }).describe("Continue with the collected records");
45
- const schema = z.object({ next: reference });
46
- const inspected = z.toJSONSchema(schema, { io: "input" });
47
- expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedParamsSchema", z.toJSONSchema(contributions, { io: "input" }));
65
+ const reference = Type.With(workflowRefSchema({ params: contributions }), { description: "Continue with the collected records" });
66
+ const schema = Type.Object({ next: reference });
67
+ const inspected = inspectSchema(schema);
68
+ expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedParamsSchema", inspectSchema(contributions));
48
69
  expect(inspected).toHaveProperty("properties.next.description", "Continue with the collected records");
49
- expect(inspected).toHaveProperty("properties.next.anyOf", z.toJSONSchema(workflowRefSchema(), { io: "input" }).anyOf);
50
- expect(inspected).toHaveProperty("properties.next.anyOf.1.properties.forwardParams.type", "object");
51
- expect(inspected).toHaveProperty("properties.next.anyOf.1.properties.forwardParams.additionalProperties", {});
52
- expect(inspected).toHaveProperty("properties.next.anyOf.1.required", ["workflow", "forwardParams"]);
70
+ expect(inspected).toHaveProperty("properties.next.anyOf", inspectSchema(workflowRefSchema()).anyOf);
71
+ expect(inspected).toHaveProperty("properties.next.anyOf.1.properties.forwardParams.patternProperties", { "^.*$": {} });
53
72
  expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedParamsSchema.required", ["records", "count"]);
54
73
  expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedParamsSchema.properties.count.type", "string");
55
74
  const forwardParams = { taskId: "task-42", nested: { labels: ["one", "two"], enabled: false, absent: null } };
56
- const input = { next: { workflow: "refs.finish", forwardParams } };
57
- assert.deepEqual(schema.parse(input), input);
75
+ const next = Value.Decode(schema, { next: { workflow: "refs.finish", forwardParams } }).next;
76
+ const contribution = { records: { path: "records.json" }, count: "2" };
77
+ assert.deepEqual(next(contribution).params, { ...forwardParams, ...contribution });
78
+ assert.equal(decodes, 0);
79
+ assert.throws(() => Reflect.apply(next, undefined, [{ records: { path: "records.json" }, count: 2 }]));
58
80
  });
59
81
 
60
- test("references without declared contributions advertise an empty contribution schema", () => {
61
- for (const reference of [workflowRefSchema(), workflowRefSchema({ params: undefined })]) {
62
- expect(z.toJSONSchema(reference, { io: "input" })).toHaveProperty("x-norn-workflow-ref.contributedParamsSchema", z.toJSONSchema(z.object({}), { io: "input" }));
63
- assert.deepEqual(reference.parse("refs.finish"), { workflow: "refs.finish", forwardParams: {} });
64
- }
82
+ test("references without contributions advertise an empty object schema", () => {
83
+ const reference = workflowRefSchema();
84
+ expect(inspectSchema(reference)).toHaveProperty("x-norn-workflow-ref.contributedParamsSchema", inspectSchema(Type.Object({})));
85
+ assert.deepEqual(Value.Decode(reference, "refs.finish")({}), { type: "next", workflowId: "refs.finish", params: {} });
65
86
  });
66
87
 
67
- test("optional, nullable and array references retain their contribution annotations", () => {
68
- const reference = workflowRefSchema({ params: z.object({ report: z.string() }) });
69
- const schema = z.object({ optional: reference.optional(), nullable: reference.nullable(), many: z.array(reference) });
70
- const inspected = z.toJSONSchema(schema, { io: "input" });
71
- const expected = z.toJSONSchema(z.object({ report: z.string() }), { io: "input" });
88
+ test("optional, nullable and array references retain annotations and callable codecs", () => {
89
+ const reference = workflowRefSchema({ params: Type.Object({ report: Type.String() }) });
90
+ const schema = Type.Object({ optional: Type.Optional(reference), nullable: Type.Union([reference, Type.Null()]), many: Type.Array(reference) });
91
+ const inspected = inspectSchema(schema);
72
92
  for (const path of ["properties.optional", "properties.nullable.anyOf.0", "properties.many.items"]) {
73
- expect(inspected).toHaveProperty(`${path}.x-norn-workflow-ref.contributedParamsSchema`, expected);
93
+ expect(inspected).toHaveProperty(`${path}.x-norn-workflow-ref.contributedParamsSchema`, inspectSchema(Type.Object({ report: Type.String() })));
74
94
  }
75
95
  assert.deepEqual(inspected.required, ["nullable", "many"]);
76
- assert.deepEqual(schema.parse({ nullable: null, many: ["refs.finish"] }), { nullable: null, many: [{ workflow: "refs.finish", forwardParams: {} }] });
96
+ const decoded = Value.Decode(schema, { nullable: null, many: ["refs.finish"], optional: "refs.finish" });
97
+ assert.equal(decoded.nullable, null);
98
+ assert.deepEqual(decoded.many[0]({ report: "array" }), target({ report: "array" }));
99
+ assert.deepEqual(decoded.optional!({ report: "optional" }), target({ report: "optional" }));
77
100
  });
78
101
 
79
- test("nested continuation schemas retain their own contribution and forwarding contracts", () => {
80
- const finalContributions = z.object({ summary: z.string() });
81
- const contributions = z.object({
82
- records: artifactRefSchema,
83
- next: workflowRefSchema({ params: finalContributions }),
84
- });
85
- const inspected = z.toJSONSchema(workflowRefSchema({ params: contributions }), { io: "input" });
86
- const path = "x-norn-workflow-ref.contributedParamsSchema.properties.next";
87
- expect(inspected).toHaveProperty(`${path}.x-norn-workflow-ref.contributedParamsSchema`, z.toJSONSchema(finalContributions, { io: "input" }));
88
- expect(inspected).toHaveProperty(`${path}.anyOf.1.properties.forwardParams.type`, "object");
89
- expect(inspected).toHaveProperty(`${path}.anyOf.1.properties.forwardParams.additionalProperties`, {});
102
+ test("nested references retain encoded payloads in contributed parameters", () => {
103
+ const finalContributions = Type.Object({ summary: Type.String() });
104
+ const contributions = Type.Object({ records: artifactRefSchema, next: workflowRefSchema({ params: finalContributions }) });
105
+ const reference = workflowRefSchema({ params: contributions });
106
+ expect(inspectSchema(reference)).toHaveProperty("x-norn-workflow-ref.contributedParamsSchema.properties.next.x-norn-workflow-ref.contributedParamsSchema", inspectSchema(finalContributions));
107
+ const params = { records: { path: "records.json" }, next: { workflow: "refs.finish", forwardParams: { batch: 1 } } };
108
+ const transition = Value.Decode(reference, "intermediate")(params);
109
+ assert.deepEqual(transition.params, params);
110
+ assert.deepEqual(Value.Decode(contributions, transition.params).next({ summary: "done" }).params, { batch: 1, summary: "done" });
90
111
  });
91
112
 
92
- test("lazy recursive contribution schemas are resolved during inspection, not reference construction", () => {
93
- type Contribution = { label: string; children: Contribution[] };
94
- let contributions: z.ZodType<Contribution>;
95
- const lazyContributions = z.lazy(() => contributions);
96
- const reference = workflowRefSchema({ params: lazyContributions });
97
- contributions = z.object({ label: z.string(), children: z.array(z.lazy(() => contributions)) });
98
- const inspected = z.toJSONSchema(reference, { io: "input" });
99
- expect(inspected).toHaveProperty("x-norn-workflow-ref.contributedParamsSchema", z.toJSONSchema(lazyContributions, { io: "input" }));
100
- assert.deepEqual(reference.parse("refs.finish"), { workflow: "refs.finish", forwardParams: {} });
113
+ test("recursive contributions use native TypeBox definitions", () => {
114
+ const contributions = Type.Cyclic({ Contribution: Type.Object({ label: Type.String(), children: Type.Array(Type.Ref("Contribution")) }) }, "Contribution");
115
+ const reference = workflowRefSchema({ params: contributions });
116
+ expect(inspectSchema(reference)).toHaveProperty("x-norn-workflow-ref.contributedParamsSchema", inspectSchema(contributions));
117
+ const params = { label: "root", children: [{ label: "leaf", children: [] }] };
118
+ assert.deepEqual(Value.Decode(reference, "refs.finish")(params).params, params);
101
119
  });
102
120
 
103
- test("an unrepresentable contribution schema does not change reference parsing", () => {
104
- const reference = workflowRefSchema({ params: z.custom(() => true) });
105
- assert.deepEqual(reference.parse("refs.finish"), { workflow: "refs.finish", forwardParams: {} });
106
- assert.throws(() => z.toJSONSchema(reference, { io: "input" }));
121
+ test("object-valued unions, records, intersections and root codecs can contribute", () => {
122
+ const union = workflowRefSchema({ params: Type.Union([Type.Object({ kind: Type.Literal("ok"), report: Type.String() }), Type.Object({ kind: Type.Literal("error"), reason: Type.String() })]) });
123
+ assert.deepEqual(Value.Decode(union, "target")({ kind: "error", reason: "failed" }).params, { kind: "error", reason: "failed" });
124
+ const record = workflowRefSchema({ params: Type.Record(Type.String(), Type.Integer()) });
125
+ assert.deepEqual(Value.Decode(record, "target")({ first: 1 }).params, { first: 1 });
126
+ const intersection = workflowRefSchema({ params: Type.Intersect([Type.Object({ left: Type.String() }), Type.Object({ right: Type.Integer() })]) });
127
+ assert.deepEqual(Value.Decode(intersection, "target")({ left: "x", right: 2 }).params, { left: "x", right: 2 });
128
+ const codec = workflowRefSchema({ params: Type.Decode(Type.Object({ text: Type.String() }), value => value.text) });
129
+ assert.deepEqual(Value.Decode(codec, "target")({ text: "encoded" }).params, { text: "encoded" });
130
+ const unknown = Value.Decode(workflowRefSchema({ params: Type.Unknown() }), "target");
131
+ for (const value of [null, [], "text", 1]) assert.throws(() => Reflect.apply(unknown, undefined, [value]), /contributions must be objects/);
132
+ for (const value of [new Date(), { callback: () => undefined }]) assert.throws(() => Reflect.apply(unknown, undefined, [value]));
133
+ });
134
+
135
+ test("non-JSON contributions fail explicit inspection without preventing construction", () => {
136
+ const reference = workflowRefSchema({ params: Type.BigInt() });
137
+ assert.equal(typeof Value.Decode(reference, "refs.finish"), "function");
138
+ assert.throws(() => inspectSchema(reference));
139
+ const nested = workflowRefSchema({ params: Type.Object({ next: reference }) });
140
+ assert.throws(() => inspectSchema(Type.Object({ next: Type.Optional(Type.Array(nested)) })));
141
+ });
142
+
143
+ test("inspection traverses schema positions, not defaults, examples or property-name maps", () => {
144
+ const payload = { "x-norn-workflow-ref": { contributedParamsSchema: { type: "bigint" } } };
145
+ const schema = Type.Object({ "x-norn-workflow-ref": Type.String() }, { default: payload, examples: [payload] });
146
+ assert.doesNotThrow(() => inspectSchema(schema));
147
+ const invalid = workflowRefSchema({ params: Type.BigInt() });
148
+ for (const wrapper of [Type.Record(Type.String(), invalid), Type.Tuple([invalid]), Type.Object({}, { additionalProperties: invalid }), Type.Cyclic({ Root: Type.Object({ child: invalid }) }, "Root")]) {
149
+ assert.throws(() => inspectSchema(wrapper));
150
+ }
107
151
  });
108
152
 
109
- for (const input of [undefined, null, "", 3, {}, { id: "" }, { workflow: "refs.finish" }, { workflow: "", forwardParams: {} }, { workflow: "refs.finish", forwardParams: null }]) {
110
- test(`invalid workflow reference is rejected: ${JSON.stringify(input)}`, () => {
111
- assert.equal(workflowRefSchema().safeParse(input).success, false);
153
+ for (const input of [undefined, null, "", 3, {}, { workflow: "refs.finish" }, { workflow: "", forwardParams: {} }, { workflow: "refs.finish", forwardParams: null }, { workflow: target, forwardParams: {} }]) {
154
+ test(`reference input schema rejects invalid shape without coercion: ${JSON.stringify(input)}`, () => {
155
+ assert.equal(Value.Check(workflowRefSchema(), input), false);
112
156
  });
113
157
  }
package/dist/cli.js CHANGED
@@ -1,16 +1,5 @@
1
1
  // src/cli.ts
2
- import { spawn } from "node:child_process";
3
2
  import { main as runPi } from "@earendil-works/pi-coding-agent";
4
- import { createHash, randomUUID } from "node:crypto";
5
- import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
6
- import { dirname, isAbsolute, join, resolve } from "node:path";
7
- import { homedir } from "node:os";
8
- import { fileURLToPath } from "node:url";
9
- import { renderNornDocumentationIntro, resolveDocumentationCacheRoot, resolveNornDocumentation } from "./documentation.js";
10
- import { setTimeout as delay } from "node:timers/promises";
11
- import { discoverNornProject, findNornProject, inspectNornWorkflow, loadNornProject, NORN_PROJECT_FILE_NAME } from "./plugin-loader.js";
12
- import { NornEngine } from "./internal/engine.js";
13
- import { errorMessage, NornProjectLoadError, NornRunStoppedError } from "./internal/errors.js";
14
3
 
15
4
  // ../core/src/errors.ts
16
5
  function isNodeError(error) {
@@ -18,13 +7,25 @@ function isNodeError(error) {
18
7
  }
19
8
 
20
9
  // src/cli.ts
10
+ import { spawn } from "node:child_process";
11
+ import { createHash, randomUUID } from "node:crypto";
12
+ import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
13
+ import { homedir } from "node:os";
14
+ import { dirname, isAbsolute, join, resolve } from "node:path";
15
+ import { setTimeout as delay } from "node:timers/promises";
16
+ import { fileURLToPath } from "node:url";
17
+ import { Value } from "typebox/value";
18
+ import { NORN_BUILD_INFO } from "./build-info.js";
19
+ import { renderNornDocumentationIntro, resolveDocumentationCacheRoot, resolveNornDocumentation } from "./documentation.js";
20
+ import { NornEngine } from "./internal/engine.js";
21
+ import { errorMessage, NornProjectLoadError, NornRunStoppedError } from "./internal/errors.js";
21
22
  import { clearRunResumeRequest, readRunLaunchRequest, readRunResumeRequest, writeRunLaunchRequest, writeRunResumeRequest } from "./internal/launch-request.js";
22
- import { generateRunName } from "./internal/run-names.js";
23
- import { getRunLeaseOwner, NornRunLease } from "./internal/run-lease.js";
24
- import { NornRunStore } from "./internal/run-store.js";
25
23
  import { readRunMetrics } from "./internal/metrics.js";
24
+ import { getRunLeaseOwner, NornRunLease } from "./internal/run-lease.js";
25
+ import { generateRunName } from "./internal/run-names.js";
26
26
  import { getRunInfo, listRuns, mergeInterruptedWorkflowParams, resolveRunRoot } from "./internal/run-state.js";
27
- import { NORN_BUILD_INFO } from "./build-info.js";
27
+ import { NornRunStore } from "./internal/run-store.js";
28
+ import { discoverNornProject, findNornProject, inspectNornWorkflow, loadNornProject, NORN_PROJECT_FILE_NAME } from "./plugin-loader.js";
28
29
  var RUNS_ROOT = join(".norn", "runs");
29
30
  var RUN_WAIT_INTERVAL_MS = 1e3;
30
31
  var CLI_DESCRIPTION = "Norn is a harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents. Build workflows with the Norn SDK; use this JSON-native CLI to discover workflows, start or resume runs, inspect evidence, and manage the installed runtime.";
@@ -111,7 +112,7 @@ var COMMANDS = [
111
112
  {
112
113
  id: "project.inspect",
113
114
  path: ["project", "inspect"],
114
- description: "Use when discovering the active Norn project, plugins, workflow sources, and Seer mode.",
115
+ description: "Use when discovering the active Norn project, plugins, and workflow sources.",
115
116
  usage: "norn project inspect",
116
117
  output: "JSON object with project metadata under project, isComplete, and plugin diagnostics. Incomplete discovery does not permit execution.",
117
118
  examples: ["norn project inspect"],
@@ -120,18 +121,6 @@ var COMMANDS = [
120
121
  await inspectProject();
121
122
  }
122
123
  },
123
- {
124
- id: "seer.inspect",
125
- path: ["seer", "inspect"],
126
- description: "Use when checking the current project's resolved Seer mode before agent execution.",
127
- usage: "norn seer inspect",
128
- output: "JSON object with resolved Seer mode under seerMode.",
129
- examples: ["norn seer inspect"],
130
- execute: async (args) => {
131
- assertNoExtraArgs("seer inspect", args);
132
- await inspectSeerMode();
133
- }
134
- },
135
124
  {
136
125
  id: "workflows.list",
137
126
  path: ["workflows", "list"],
@@ -386,7 +375,6 @@ var HELP_COMMAND_ORDER = [
386
375
  "runs.delete",
387
376
  "project.init",
388
377
  "project.inspect",
389
- "seer.inspect",
390
378
  "pi",
391
379
  "upgrade",
392
380
  "version",
@@ -413,7 +401,6 @@ var HUMAN_COMMAND_SUMMARIES = {
413
401
  "runs.delete": "Delete an inactive run.",
414
402
  "project.init": "Create a Norn project in the current directory.",
415
403
  "project.inspect": "Inspect the Norn project.",
416
- "seer.inspect": "Inspect resolved Seer mode.",
417
404
  pi: "Run bundled Pi for authentication, providers, and model setup.",
418
405
  upgrade: "Upgrade the installed Norn CLI.",
419
406
  version: "Print version/build info.",
@@ -712,17 +699,14 @@ async function inspectProject() {
712
699
  const { project, isComplete, diagnostics } = await discoverNornProject(process.cwd());
713
700
  writeJson({ project, isComplete, diagnostics });
714
701
  }
715
- async function inspectSeerMode() {
716
- const project = await findNornProject(process.cwd());
717
- writeJson({ seerMode: project.seerMode ?? null });
718
- }
719
702
  async function startRun(workflowId, args) {
720
703
  assertNoStructuredInputArgs("runs start", args);
721
704
  const project = await loadNornProject(process.cwd());
722
705
  const workflow = project.registry.workflowById(workflowId);
723
706
  if (!workflow) throw new Error(`Unknown workflow: ${workflowId}`);
724
707
  const input = parseStartRunInput(await readStdinJson());
725
- const params = workflow.params.parse(input.params ?? {});
708
+ const params = input.params === void 0 ? {} : input.params;
709
+ Value.Decode(workflow.params, params);
726
710
  const configOverride = input.config;
727
711
  const id = randomUUID();
728
712
  const name = generateRunName(new Set((await listRuns(project.projectRoot)).map((run) => run.name)));
@@ -763,7 +747,7 @@ async function parseResumeParams(runInfo, params) {
763
747
  const project = await loadNornProject(process.cwd());
764
748
  const workflow = project.registry.workflowById(runInfo.currentWorkflowId);
765
749
  if (!workflow) throw new Error(`Unknown workflow for resumed run: ${runInfo.currentWorkflowId}`);
766
- workflow.params.parse(mergeInterruptedWorkflowParams(runInfo.interruption?.params, params, runInfo.interruption?.fields));
750
+ Value.Decode(workflow.params, mergeInterruptedWorkflowParams(runInfo.interruption?.params, params, runInfo.interruption?.fields));
767
751
  return params;
768
752
  }
769
753
  if (runInfo.status === "pendingResume") {
package/dist/client.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { DeletedNornRunInfo, NornProjectInspection, NornWorkflowCatalogInfo, NornWorkflowInspection, NornRunCheckpoint, NornRunInfo, NornRunMetrics, NornWorkflowStateReader } from "@vimhead.dev/norn";
2
2
  import type { NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
3
- import type { NornResolvedSeerModeConfig } from "@vimhead.dev/norn/seer";
4
3
  export { NornProjectLoadError } from "./internal/errors.ts";
5
4
  export type NornClientInput = {
6
5
  readonly spawnCwd?: string;
@@ -18,9 +17,6 @@ export type NornClient = {
18
17
  entries(): Promise<readonly NornRegisteredWorkflow[]>;
19
18
  };
20
19
  readonly state: NornWorkflowStateReader;
21
- readonly seer: {
22
- inspect(): Promise<NornResolvedSeerModeConfig | null>;
23
- };
24
20
  readonly runs: {
25
21
  start(input: {
26
22
  readonly workflowId: string;
package/dist/client.js CHANGED
@@ -18,9 +18,6 @@ function createNornClient(input = {}) {
18
18
  entries: async () => (await catalog.load()).workflows
19
19
  },
20
20
  state: catalog.state,
21
- seer: {
22
- inspect: async () => (await processRunner.readJson(["seer", "inspect"])).seerMode
23
- },
24
21
  runs: {
25
22
  start: async (startInput) => (await processRunner.readJson(["runs", "start", startInput.workflowId], JSON.stringify(runStartInput(startInput)))).run,
26
23
  resume: async (resumeInput) => (await processRunner.readJson(["runs", "resume", resumeInput.run], runResumeStdin(resumeInput))).run,
@@ -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.35240723931.1";
4
- readonly commit: "f5872d022f9a38d5c0828bf39fa4ad49ca536caa";
3
+ readonly version: "0.1.0-tip.35343816255.1";
4
+ readonly commit: "c38480c32d1c8ee786f12300bf9e21163440d0a7";
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.35240723931.1",
5
- "commit": "f5872d022f9a38d5c0828bf39fa4ad49ca536caa",
4
+ "version": "0.1.0-tip.35343816255.1",
5
+ "commit": "c38480c32d1c8ee786f12300bf9e21163440d0a7",
6
6
  "packageSpec": "@vimhead.dev/norn-cli@tip",
7
7
  "upgrade": {
8
8
  "supported": false,
@@ -1,13 +1,13 @@
1
1
  import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
- import type { z } from "zod";
3
2
  import type { NornDispose } from "@vimhead.dev/norn";
3
+ import { type TSchema } from "typebox";
4
4
  export type NornCapturedAgentResponse = {
5
5
  readonly called: boolean;
6
6
  readonly response?: unknown;
7
7
  };
8
8
  export declare class NornAgentResponseCollector {
9
9
  private readonly pending;
10
- begin(runId: string, label: string, responseSchema: z.ZodType): NornDispose;
10
+ begin(runId: string, label: string, responseSchema: TSchema): NornDispose;
11
11
  capture(runId: string, label: string, response: unknown): {
12
12
  label: string;
13
13
  response: unknown;
@@ -1,12 +1,13 @@
1
1
  // src/internal/agent-response-tool.ts
2
2
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
- import { Type } from "typebox";
4
3
 
5
4
  // ../core/src/agent-protocol.ts
6
5
  var AGENT_RESPONSE_TOOL_NAME = "pi_workflows_agent_response";
7
6
 
8
7
  // src/internal/agent-response-tool.ts
9
- import { zodErrorMessage } from "./errors.js";
8
+ import { Type } from "typebox";
9
+ import { Value } from "typebox/value";
10
+ import { errorMessage } from "./errors.js";
10
11
  var NornAgentResponseCollector = class {
11
12
  pending = /* @__PURE__ */ new Map();
12
13
  begin(runId, label, responseSchema) {
@@ -20,11 +21,10 @@ var NornAgentResponseCollector = class {
20
21
  throw new Error(`Workflow agent response label mismatch: expected ${pending.label}, received ${label}`);
21
22
  }
22
23
  if (pending.called) throw new Error(`Workflow agent response already recorded for ${runId}`);
23
- const parsed = pending.responseSchema.safeParse(response);
24
- if (!parsed.success) throw new Error(zodErrorMessage(parsed.error));
24
+ const decoded = Value.Decode(pending.responseSchema, response);
25
25
  pending.called = true;
26
- pending.response = parsed.data;
27
- return { label: pending.label, response: parsed.data };
26
+ pending.response = decoded;
27
+ return { label: pending.label, response: decoded };
28
28
  }
29
29
  get(runId) {
30
30
  const pending = this.pending.get(runId);
@@ -61,7 +61,7 @@ var NornAgentResponseToolFactory = class {
61
61
  terminate: true
62
62
  };
63
63
  } catch (error) {
64
- return invalidAgentResponseToolResult(error instanceof Error ? error.message : String(error));
64
+ return invalidAgentResponseToolResult(errorMessage(error));
65
65
  }
66
66
  }
67
67
  });
@@ -1,6 +1,6 @@
1
1
  import { type CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
2
- import { z } from "zod";
3
2
  import type { NornAgentCreateSessionInput, NornAgentSession, NornAgentSinglePromptInput } from "@vimhead.dev/norn";
3
+ import { type StaticDecode, type TSchema } from "typebox";
4
4
  import { NornAgentResponseCollector } from "./agent-response-tool.ts";
5
5
  import type { NornRunLogs } from "./logs.ts";
6
6
  import type { NornRunLogger } from "./run-log.ts";
@@ -23,7 +23,7 @@ export declare class NornAgentRunner {
23
23
  private readonly responseToolFactory;
24
24
  constructor(input: NornAgentRunnerInput);
25
25
  createSession(agentInput: NornAgentCreateSessionInput): Promise<NornAgentSession>;
26
- prompt<ResponseSchema extends z.ZodType>(agentInput: NornAgentSinglePromptInput<ResponseSchema>): Promise<z.output<ResponseSchema>>;
26
+ prompt<ResponseSchema extends TSchema>(agentInput: NornAgentSinglePromptInput<ResponseSchema>): Promise<StaticDecode<ResponseSchema>>;
27
27
  private resolveFromCwd;
28
28
  }
29
29
  export {};
@@ -1,24 +1,24 @@
1
1
  // src/internal/agents.ts
2
2
  import {
3
3
  SessionManager,
4
- createAgentSessionServices,
5
4
  createAgentSessionFromServices,
5
+ createAgentSessionServices,
6
6
  createEventBus
7
7
  } from "@earendil-works/pi-coding-agent";
8
- import { randomUUID } from "node:crypto";
9
- import { mkdir } from "node:fs/promises";
10
- import { homedir } from "node:os";
11
- import { isAbsolute, relative, resolve, sep } from "node:path";
12
- import { z } from "zod";
13
8
 
14
9
  // ../core/src/agent-protocol.ts
15
10
  var AGENT_RESPONSE_TOOL_NAME = "pi_workflows_agent_response";
16
11
 
17
12
  // src/internal/agents.ts
13
+ import { inspectSchema } from "@vimhead.dev/norn/schema";
14
+ import { randomUUID } from "node:crypto";
15
+ import { mkdir } from "node:fs/promises";
16
+ import { homedir } from "node:os";
17
+ import { isAbsolute, relative, resolve, sep } from "node:path";
18
+ import { resolveNornAgentDirectory } from "./agent-directory.js";
18
19
  import {
19
20
  NornAgentResponseToolFactory
20
21
  } from "./agent-response-tool.js";
21
- import { resolveNornAgentDirectory } from "./agent-directory.js";
22
22
  import { errorMessage } from "./errors.js";
23
23
  import { safeFileName } from "./file-names.js";
24
24
  import { NornSessionResourceBindings } from "./resource-bindings.js";
@@ -163,7 +163,7 @@ var CreatedNornAgentSession = class {
163
163
  attempts.push(rawAttempt);
164
164
  try {
165
165
  if (!raw.responseToolCalled) throw new Error(`Agent did not call ${AGENT_RESPONSE_TOOL_NAME}`);
166
- const response = agentInput.response.parse(raw.toolResponse);
166
+ const response = raw.toolResponse;
167
167
  const usage = totalAgentUsage(attempts.map((candidate) => candidate.usage));
168
168
  const result = {
169
169
  label: this.label,
@@ -310,7 +310,7 @@ function withResponseToolInstruction(prompt, responseSchema, label, responseRunI
310
310
  `Pass label exactly as: ${label}`,
311
311
  "Pass the structured workflow response in the tool's response argument.",
312
312
  "The response argument must match this JSON Schema:",
313
- JSON.stringify(z.toJSONSchema(responseSchema), null, 2),
313
+ JSON.stringify(inspectSchema(responseSchema), null, 2),
314
314
  ...attempt > 1 && previousError ? ["", `Previous structured response attempt failed: ${previousError}`] : []
315
315
  ].join("\n");
316
316
  }
@@ -323,7 +323,7 @@ function withResponseToolFinalizationInstruction(responseSchema, label, response
323
323
  `Pass label exactly as: ${label}`,
324
324
  "Pass the structured workflow response in the tool's response argument, based on the work you already completed in this session.",
325
325
  "The response argument must match this JSON Schema:",
326
- JSON.stringify(z.toJSONSchema(responseSchema), null, 2),
326
+ JSON.stringify(inspectSchema(responseSchema), null, 2),
327
327
  ...attempt > 1 && previousError ? ["", `Previous structured response attempt failed: ${previousError}`] : []
328
328
  ].join("\n");
329
329
  }
@@ -1,4 +1,4 @@
1
- import { type NornAnyWorkflowDeclaration, type NornAnyWorkflowPluginManifest, type NornDispose, type NornRunStartOptions, type NornRunResult, type NornRunCheckpoint, type NornRunInfo, type NornWorkflowPlugin } from "@vimhead.dev/norn";
1
+ import { type NornAnyWorkflowDeclaration, type NornAnyWorkflowPluginManifest, type NornDispose, type NornRunCheckpoint, type NornRunInfo, type NornRunResult, type NornRunStartOptions, type NornWorkflowPlugin } from "@vimhead.dev/norn";
2
2
  import { NornAgentResponseCollector } from "./agent-response-tool.ts";
3
3
  import { type NornRunResumeRequest } from "./launch-request.ts";
4
4
  import { type NornRegisteredWorkflow } from "./workflow-registry.ts";
@@ -1,21 +1,22 @@
1
1
  // src/internal/engine.ts
2
+ import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { mkdir } from "node:fs/promises";
4
5
  import { dirname, join } from "node:path";
5
- import { initializeRunResources } from "./run-resources.js";
6
- import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
6
+ import { Value } from "typebox/value";
7
7
  import { NornAgentResponseCollector } from "./agent-response-tool.js";
8
8
  import { NornArtifacts } from "./artifacts.js";
9
9
  import { errorMessage, NornRunStoppedError } from "./errors.js";
10
- import { NornRunLogs } from "./logs.js";
11
10
  import { clearRunResumeRequest, matchesRunResumeRequest, readOptionalRunResumeRequest, RESUME_START_GRACE_MS } from "./launch-request.js";
11
+ import { NornRunLogs } from "./logs.js";
12
12
  import { NornRunLease } from "./run-lease.js";
13
13
  import { NornRunLogger } from "./run-log.js";
14
+ import { initializeRunResources } from "./run-resources.js";
15
+ import { getRunInfo, mergeInterruptedWorkflowParams, NornRunStateStore, resolveRunRoot } from "./run-state.js";
14
16
  import { NornRunStore, runCurrentRoot } from "./run-store.js";
15
- import { NornRunStateStore, getRunInfo, mergeInterruptedWorkflowParams, resolveRunRoot } from "./run-state.js";
16
17
  import { NornRunContext } from "./run.js";
17
18
  import { NornMemoryWorkflowState } from "./state-store.js";
18
- import { NornWorkflowRegistry } from "./workflow-registry.js";
19
+ import { decodePluginConfiguration, NornWorkflowRegistry } from "./workflow-registry.js";
19
20
  var RUNS_DIR_NAME = ".norn";
20
21
  var MANIFEST_FILE_NAME = "manifest.json";
21
22
  var NornEngine = class {
@@ -32,10 +33,11 @@ var NornEngine = class {
32
33
  registerPlugin(plugin) {
33
34
  this.disposePlugin(plugin.manifest.id);
34
35
  const implementation = this.resolvePluginImplementation(plugin.implementation);
36
+ const configuration = decodePluginConfiguration({ pluginId: plugin.manifest.id, schema: plugin.manifest.config, value: this.input.config?.[plugin.manifest.id] });
35
37
  const disposers = Object.entries(plugin.manifest.workflows).map(([key, workflow]) => {
36
38
  const workflowImplementation = implementation.workflows[key];
37
39
  if (!workflowImplementation) throw new Error(`Missing implementation for workflow ${plugin.manifest.id}.${key}`);
38
- return this.registry.register(workflow, workflowImplementation, { plugin: { id: plugin.manifest.id }, configSchema: plugin.manifest.config, config: this.input.config?.[plugin.manifest.id] });
40
+ return this.registry.register(workflow, workflowImplementation, { plugin: { id: plugin.manifest.id }, configuration });
39
41
  });
40
42
  this.disposersByPlugin.set(plugin.manifest.id, disposers);
41
43
  return () => {
@@ -105,8 +107,9 @@ var NornEngine = class {
105
107
  const workflow = this.registry.workflowById(current.workflowId);
106
108
  if (!workflow) throw new Error(`Unknown workflow for resumed run: ${current.workflowId}`);
107
109
  if (state.status === "interrupted") {
108
- const parsedParams = workflow.params.parse(mergeInterruptedWorkflowParams(current.params, params, current.interruption?.fields));
109
- await session.state.replaceCurrentParams(parsedParams);
110
+ const rawParams = mergeInterruptedWorkflowParams(current.params, params, current.interruption?.fields);
111
+ Value.Decode(workflow.params, rawParams);
112
+ await session.state.replaceCurrentParams(rawParams);
110
113
  await recordRunEvent(session, { type: "run.resumed", workflowId: workflow.id, cwd: session.run.cwd });
111
114
  const resumedCurrent = session.state.currentState().current;
112
115
  if (!resumedCurrent) throw new Error(`Run is not resumable: ${runRoot}`);
@@ -134,13 +137,12 @@ var NornEngine = class {
134
137
  throwIfRunAborted(session.activeRun);
135
138
  const stepRuntime = session.run.forWorkflow(currentStep.workflow);
136
139
  if (shouldPauseForGate(currentStep)) {
137
- const parsedParams = currentStep.workflow.params.parse(currentStep.params);
138
- const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, parsedParams, session.state.currentState().configOverride);
140
+ const description = await this.registry.describeGate(currentStep.workflow, stepRuntime, currentStep.params, session.state.currentState().configOverride);
139
141
  const interruption = { description, fields: currentStep.workflow.gate?.fields };
140
- await session.state.interruptCurrent(parsedParams, interruption);
142
+ await session.state.interruptCurrent(currentStep.params, interruption);
141
143
  await recordRunEvent(session, { type: "run.interrupted", workflowId: currentStep.workflow.id });
142
144
  await commitRunBoundary(session, `run interrupted: ${currentStep.workflow.id}`);
143
- return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, parsedParams, interruption);
145
+ return interruptedLaunchResult({ id: stepRuntime.id, name: session.state.currentState().name, workspace: stepRuntime.workspace, cwd: stepRuntime.cwd }, currentStep.workflow, currentStep.params, interruption);
144
146
  }
145
147
  await session.state.startStep(toRunStateStep(currentStep));
146
148
  const stepResult = await this.executeWorkflowStep(session, currentStep, stepRuntime);