@vimhead.dev/norn-cli 0.1.0-tip.35390859630.1 → 0.1.0-tip.35436871363.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/assets/README.md +8 -4
  2. package/assets/docs/README.md +4 -4
  3. package/assets/docs/agents.md +37 -9
  4. package/assets/docs/cli.md +3 -3
  5. package/assets/docs/composition.md +12 -12
  6. package/assets/docs/persistence.md +31 -17
  7. package/assets/docs/providers.md +2 -2
  8. package/assets/docs/workflows.md +15 -10
  9. package/assets/examples/agent-then-analysis/README.md +8 -8
  10. package/assets/examples/agent-then-analysis/plugin.ts +18 -16
  11. package/assets/examples/caller-selected-continuation/README.md +11 -10
  12. package/assets/examples/caller-selected-continuation/caller.ts +12 -10
  13. package/assets/examples/caller-selected-continuation/producer.ts +9 -6
  14. package/assets/examples/coordinating-multiple-agents/README.md +26 -16
  15. package/assets/examples/coordinating-multiple-agents/plugin.ts +58 -35
  16. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +43 -0
  17. package/assets/examples/coordinating-multiple-agents/work-queue.ts +60 -65
  18. package/assets/examples/getting-started/README.md +2 -2
  19. package/assets/examples/getting-started/plugin.ts +7 -3
  20. package/assets/examples/minimal-workflow/README.md +5 -4
  21. package/assets/examples/minimal-workflow/plugin.ts +7 -4
  22. package/assets/examples/shared-state/README.md +5 -5
  23. package/assets/examples/shared-state/plugin.ts +35 -22
  24. package/assets/examples/shared-state/shared-state.ts +30 -49
  25. package/assets/examples/shared-state/state-tools.ts +68 -0
  26. package/assets/examples/worktree-development-loop/README.md +14 -12
  27. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +2 -2
  28. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +5 -3
  29. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +8 -7
  30. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +2 -3
  31. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +8 -4
  32. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +10 -7
  33. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +1 -2
  34. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +15 -14
  35. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +1 -2
  36. package/assets/package.json +1 -1
  37. package/assets/packages/cli/src/cli.ts +9 -3
  38. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  39. package/assets/packages/cli/src/internal/agents.ts +24 -32
  40. package/assets/packages/cli/src/internal/commands.ts +2 -14
  41. package/assets/packages/cli/src/internal/engine.ts +30 -44
  42. package/assets/packages/cli/src/internal/logs.ts +1 -1
  43. package/assets/packages/cli/src/internal/run-log.ts +1 -1
  44. package/assets/packages/cli/src/internal/run-state.ts +7 -2
  45. package/assets/packages/cli/src/internal/run.ts +2 -57
  46. package/assets/packages/cli/src/internal/worker-directory.ts +17 -0
  47. package/assets/packages/cli/src/internal/workflow-registry.ts +20 -8
  48. package/assets/packages/cli/src/internal/working-directory.ts +6 -0
  49. package/assets/packages/cli/src/workflow-loader.ts +1 -2
  50. package/assets/packages/sdk/src/api.ts +27 -59
  51. package/assets/packages/sdk/src/index.ts +0 -3
  52. package/assets/tests/workflow-ref.test.ts +6 -7
  53. package/dist/cli.js +8 -3
  54. package/dist/generated-build-info.d.ts +2 -2
  55. package/dist/generated-build-info.js +2 -2
  56. package/dist/internal/agents.d.ts +0 -4
  57. package/dist/internal/agents.js +30 -46
  58. package/dist/internal/commands.d.ts +0 -4
  59. package/dist/internal/commands.js +2 -10
  60. package/dist/internal/engine.js +26 -40
  61. package/dist/internal/file-coordinator.d.ts +17 -0
  62. package/dist/internal/file-coordinator.js +162 -0
  63. package/dist/internal/logs.d.ts +1 -1
  64. package/dist/internal/run-log.d.ts +1 -1
  65. package/dist/internal/run-state.d.ts +2 -0
  66. package/dist/internal/run-state.js +5 -2
  67. package/dist/internal/run.d.ts +2 -20
  68. package/dist/internal/run.js +1 -45
  69. package/dist/internal/worker-directory.d.ts +1 -0
  70. package/dist/internal/worker-directory.js +26 -0
  71. package/dist/internal/workflow-registry.d.ts +13 -3
  72. package/dist/internal/workflow-registry.js +8 -6
  73. package/dist/internal/working-directory.d.ts +1 -0
  74. package/dist/internal/working-directory.js +9 -0
  75. package/dist/workflow-loader.js +1 -2
  76. package/package.json +2 -2
  77. package/assets/docs/resources.md +0 -46
  78. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +0 -51
  79. package/assets/examples/shared-state/state-adapter.ts +0 -76
  80. package/assets/packages/cli/src/internal/artifacts.ts +0 -26
  81. package/assets/packages/cli/src/internal/resource-bindings.ts +0 -35
  82. package/assets/packages/cli/src/resources.ts +0 -69
  83. package/assets/packages/sdk/src/agent-resource-adapter.ts +0 -11
  84. package/assets/packages/sdk/src/resources.ts +0 -20
  85. package/dist/internal/artifacts.d.ts +0 -10
  86. package/dist/internal/artifacts.js +0 -29
  87. package/dist/internal/resource-bindings.d.ts +0 -13
  88. package/dist/internal/resource-bindings.js +0 -34
  89. package/dist/resources.d.ts +0 -11
  90. package/dist/resources.js +0 -100
  91. /package/assets/packages/{sdk/src/files.ts → cli/src/internal/file-coordinator.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { isWorkflowComplete, isWorkflowDeclaration, isWorkflowFail, isWorkflowNext, type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRunFor, type NornRunNext, type NornWorkflowContext, type NornWorkflowGateInfo, type NornWorkflowSource, type NornWorkflowScopeInfo, type NornProjectConfigurationInfo } from "@vimhead.dev/norn";
1
+ import { isWorkflowComplete, isWorkflowDeclaration, isWorkflowFail, isWorkflowNext, type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRun, type NornRunNext, type NornWorkflowPaths, type NornWorkflowContext, type NornWorkflowGateInfo, type NornWorkflowSource, type NornWorkflowScopeInfo, type NornProjectConfigurationInfo } from "@vimhead.dev/norn";
2
2
  import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
3
3
  import type { TSchema } from "typebox";
4
4
  import { Value } from "typebox/value";
@@ -42,6 +42,15 @@ export type NornWorkflowStepResult =
42
42
  | { readonly type: "complete"; readonly workflow: NornAnyWorkflowDeclaration; readonly metadata?: NornRunComplete["metadata"] }
43
43
  | { readonly type: "fail"; readonly workflow: NornAnyWorkflowDeclaration; readonly metadata: NornRunFail["metadata"] };
44
44
 
45
+ type NornExecutionContextInput = {
46
+ readonly run: NornRun;
47
+ readonly paths: NornWorkflowPaths;
48
+ readonly args: unknown;
49
+ readonly configOverride: unknown;
50
+ };
51
+
52
+ type NornWorkflowExecutionInput = NornExecutionContextInput & { readonly workflow: NornAnyWorkflowDeclaration };
53
+
45
54
  export class NornWorkflowRegistry {
46
55
  private readonly entries = new Map<string, NornRegisteredWorkflow>();
47
56
  private readonly scopes = new Map<string, NornRegisteredScope>();
@@ -99,31 +108,34 @@ export class NornWorkflowRegistry {
99
108
  launchableEntries(): NornRegisteredWorkflow[] { return this.sortedEntries().filter(({ workflow }) => workflow.isEntrypoint); }
100
109
  workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined { return this.entries.get(workflowId)?.workflow; }
101
110
 
102
- async describeGate<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, args: unknown, configOverride?: unknown): Promise<string> {
111
+ async describeGate(input: NornWorkflowExecutionInput): Promise<string> {
112
+ const { workflow } = input;
103
113
  const entry = this.requireEntry(workflow.id);
104
114
  if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
105
- const context = this.createExecutionContext({ entry, run, args, configOverride });
115
+ const context = this.createExecutionContext({ ...input, entry });
106
116
  const description = await entry.workflow.gate?.describe?.(context) ?? workflow.id;
107
117
  if (typeof description !== "string" || description.trim().length === 0) throw new Error(`Workflow gate description must not be empty: ${workflow.id}`);
108
118
  return description.trim();
109
119
  }
110
120
 
111
- async execute<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, run: NornRunFor<TWorkflow>, args: unknown, configOverride?: unknown): Promise<NornWorkflowStepResult> {
121
+ async execute(input: NornWorkflowExecutionInput): Promise<NornWorkflowStepResult> {
122
+ const { workflow } = input;
112
123
  const entry = this.requireEntry(workflow.id);
113
- const result = await entry.workflow.execute(this.createExecutionContext({ entry, run, args, configOverride }));
124
+ const result = await entry.workflow.execute(this.createExecutionContext({ ...input, entry }));
114
125
  if (isWorkflowNext(result)) return result;
115
126
  if (isWorkflowComplete(result)) return { type: "complete", workflow, metadata: result.metadata };
116
127
  if (isWorkflowFail(result)) return { type: "fail", workflow, metadata: result.metadata };
117
128
  throw new Error(`Workflow returned invalid control result: ${workflow.id}`);
118
129
  }
119
130
 
120
- private createExecutionContext(input: { readonly entry: NornRegisteredWorkflow; readonly run: NornRunFor<NornAnyWorkflowDeclaration>; readonly args: unknown; readonly configOverride: unknown }): NornWorkflowContext {
121
- const { entry, run, configOverride } = input;
131
+ private createExecutionContext(input: NornExecutionContextInput & { readonly entry: NornRegisteredWorkflow }): NornWorkflowContext {
132
+ const { entry, run, paths, configOverride } = input;
122
133
  const scope = entry.scopeId ? this.scopes.get(entry.scopeId) : undefined;
123
134
  return {
124
135
  args: Value.Decode(entry.workflow.args, input.args),
125
136
  config: parseExecutionConfig(entry.configuration, configOverride),
126
137
  ...(scope ? { scope: { id: scope.definition.id, config: parseExecutionConfig(scope.configuration, configOverride) } } : {}),
138
+ paths,
127
139
  run,
128
140
  };
129
141
  }
@@ -175,7 +187,7 @@ function assertGateWorkflow(workflow: NornAnyWorkflowDeclaration): void {
175
187
  function workflowInfo(entry: NornRegisteredWorkflow): NornRegisteredWorkflowInfo {
176
188
  return {
177
189
  id: entry.workflow.id, instructions: entry.workflow.instructions, isEntrypoint: entry.workflow.isEntrypoint,
178
- isolation: entry.workflow.isolation, source: entry.source,
190
+ source: entry.source,
179
191
  configKey: entry.workflow.id,
180
192
  scope: entry.scopeId ? { id: entry.scopeId } : undefined,
181
193
  };
@@ -0,0 +1,6 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+
3
+ export function requireAbsoluteWorkingDirectory(cwd: string): string {
4
+ if (typeof cwd !== "string" || !isAbsolute(cwd)) throw new Error("Working directory (cwd) must be an absolute path");
5
+ return resolve(cwd);
6
+ }
@@ -1,7 +1,6 @@
1
1
  import * as nornModule from "@vimhead.dev/norn";
2
2
  import { isWorkflowDeclaration, type NornAnyWorkflowDeclaration, type NornDispose, type NornWorkflowDiagnostic, type NornProjectInfo, type NornProjectInspection, type NornProjectConfigurationInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowSource } from "@vimhead.dev/norn";
3
3
  import { isNodeError } from "@vimhead.dev/norn-core/errors";
4
- import * as nornFilesModule from "@vimhead.dev/norn/files";
5
4
  import * as nornSchemaModule from "@vimhead.dev/norn/schema";
6
5
  import { inspectSchema, isPlainObject } from "@vimhead.dev/norn/schema";
7
6
  import { createJiti, type ModuleCache } from "jiti/static";
@@ -178,7 +177,7 @@ function createDiagnostic(input: { readonly source: NornWorkflowSource; readonly
178
177
  return { configPath: input.source.configPath, modulePath: input.source.path, scopeId: input.scopeId, workflowId: input.workflowId, stage: input.stage, message: errorMessage(input.error), issues: input.error instanceof AssertError ? input.error.cause.errors : [] };
179
178
  }
180
179
  function nornWorkflowVirtualModules(): Record<string, unknown> {
181
- return { "@vimhead.dev/norn": nornModule, "@vimhead.dev/norn/files": nornFilesModule, "@vimhead.dev/norn/schema": nornSchemaModule, typebox: typeboxModule, "typebox/value": typeboxValueModule, "typebox/compile": typeboxCompileModule, "typebox/schema": typeboxSchemaModule };
180
+ return { "@vimhead.dev/norn": nornModule, "@vimhead.dev/norn/schema": nornSchemaModule, typebox: typeboxModule, "typebox/value": typeboxValueModule, "typebox/compile": typeboxCompileModule, "typebox/schema": typeboxSchemaModule };
182
181
  }
183
182
  function mergeProjectConfig(files: readonly NornConfigFile[]): Record<string, unknown> {
184
183
  const [project, ...reusable] = files;
@@ -1,4 +1,4 @@
1
- import type { CreateAgentSessionOptions, EventBus, PromptOptions } from "@earendil-works/pi-coding-agent";
1
+ import type { CreateAgentSessionOptions, EventBus, PromptOptions, ToolDefinition } from "@earendil-works/pi-coding-agent";
2
2
  import { Type, type StaticDecode, type StaticEncode, type TCodec, type TSchema } from "typebox";
3
3
  import type { TLocalizedValidationError } from "typebox/error";
4
4
  import { createWorkflowTransition } from "@vimhead.dev/norn-core/workflow-transition";
@@ -15,16 +15,17 @@ export type NornWorkflowAnyGate = {
15
15
  readonly fields?: readonly string[];
16
16
  };
17
17
 
18
- export type NornWorkflowIsolationMode = "runWorkspace" | "project";
18
+ export type NornWorkflowPaths = {
19
+ /** Absolute project root. Excluded from run checkpoints and rollback. */
20
+ readonly project: string;
19
21
 
20
- export type NornWorkflowIsolation<Mode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode> = {
21
- readonly mode: Mode;
22
+ /** Absolute, initially empty workspace. Saved in run checkpoints and restored on rollback. */
23
+ readonly workspace: string;
22
24
  };
23
25
 
24
26
  export type NornWorkflowDeclaration<
25
27
  Id extends string = string,
26
28
  ArgsSchema extends TSchema = TSchema,
27
- IsolationMode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode,
28
29
  ConfigSchema extends TSchema | undefined = TSchema | undefined,
29
30
  Scope extends NornWorkflowScopeInfo | undefined = NornWorkflowScopeInfo | undefined,
30
31
  > = {
@@ -35,12 +36,11 @@ export type NornWorkflowDeclaration<
35
36
  readonly instructions?: string;
36
37
  readonly args: ArgsSchema;
37
38
  readonly gate?: NornWorkflowGate<ArgsSchema> & {
38
- describe?(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<string>;
39
+ describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
39
40
  };
40
- readonly isolation: NornWorkflowIsolation<IsolationMode>;
41
41
  readonly config?: ConfigSchema;
42
42
  readonly scope: Scope;
43
- execute(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
43
+ execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
44
44
  };
45
45
 
46
46
  export type NornAnyWorkflowDeclaration = Pick<NornWorkflowDeclaration, keyof NornWorkflowDeclaration> & ((args: never) => NornRunNext);
@@ -69,18 +69,17 @@ type NornWorkflowInstructions =
69
69
  | { readonly isEntrypoint: true; readonly instructions: string }
70
70
  | { readonly isEntrypoint: false; readonly instructions?: string };
71
71
 
72
- type RunForIsolation<Mode extends NornWorkflowIsolationMode> = Mode extends "project" ? NornProjectRun : NornRun;
73
72
  type WorkflowConfig<Schema extends TSchema | undefined> = Schema extends TSchema ? StaticDecode<Schema> : undefined;
74
73
 
75
74
  export type NornWorkflowContext<
76
75
  ArgsSchema extends TSchema = TSchema,
77
- Mode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode,
78
76
  ConfigSchema extends TSchema | undefined = TSchema | undefined,
79
77
  Scope extends NornWorkflowScopeInfo | undefined = NornWorkflowScopeInfo | undefined,
80
78
  > = {
81
79
  readonly args: StaticDecode<ArgsSchema>;
82
80
  readonly config: WorkflowConfig<ConfigSchema>;
83
- readonly run: RunForIsolation<Mode>;
81
+ readonly paths: NornWorkflowPaths;
82
+ readonly run: NornRun;
84
83
  } & (Scope extends NornWorkflowScopeInfo ? {
85
84
  readonly scope: { readonly id: Scope["id"]; readonly config: WorkflowConfig<Scope["config"]> };
86
85
  } : {});
@@ -88,18 +87,16 @@ export type NornWorkflowContext<
88
87
  export type NornWorkflowDefinition<
89
88
  Id extends string = string,
90
89
  ArgsSchema extends TSchema = TSchema,
91
- IsolationMode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode,
92
90
  ConfigSchema extends TSchema | undefined = undefined,
93
91
  Scope extends NornWorkflowScopeInfo | undefined = undefined,
94
92
  > = {
95
93
  readonly id: Id;
96
94
  readonly args: ArgsSchema;
97
95
  readonly config?: ConfigSchema;
98
- readonly isolation?: NornWorkflowIsolation<IsolationMode>;
99
96
  readonly gate?: NornWorkflowGate<ArgsSchema> & {
100
- describe?(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<string>;
97
+ describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
101
98
  };
102
- execute(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
99
+ execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
103
100
  } & NornWorkflowInstructions;
104
101
 
105
102
  export type NornWorkflowScopeInfo = {
@@ -110,31 +107,29 @@ export type NornWorkflowScopeInfo = {
110
107
  export type NornWorkflowScope<Id extends string, ConfigSchema extends TSchema | undefined> = {
111
108
  readonly id: Id;
112
109
  readonly config: ConfigSchema;
113
- workflow<const LocalId extends string, ArgsSchema extends TSchema, const Mode extends NornWorkflowIsolationMode = "runWorkspace", LocalConfig extends TSchema | undefined = undefined>(
114
- definition: NornWorkflowDefinition<LocalId, ArgsSchema, Mode, LocalConfig, { readonly id: Id; readonly config: ConfigSchema }>,
115
- ): NornWorkflowDeclaration<`${Id}.${LocalId}`, ArgsSchema, Mode, LocalConfig, { readonly id: Id; readonly config: ConfigSchema }>;
110
+ workflow<const LocalId extends string, ArgsSchema extends TSchema, LocalConfig extends TSchema | undefined = undefined>(
111
+ definition: NornWorkflowDefinition<LocalId, ArgsSchema, LocalConfig, { readonly id: Id; readonly config: ConfigSchema }>,
112
+ ): NornWorkflowDeclaration<`${Id}.${LocalId}`, ArgsSchema, LocalConfig, { readonly id: Id; readonly config: ConfigSchema }>;
116
113
  };
117
114
 
118
115
  export function workflow<
119
116
  const Id extends string,
120
117
  ArgsSchema extends TSchema,
121
- const Mode extends NornWorkflowIsolationMode = "runWorkspace",
122
118
  ConfigSchema extends TSchema | undefined = undefined,
123
- >(definition: NornWorkflowDefinition<Id, ArgsSchema, Mode, ConfigSchema>): NornWorkflowDeclaration<Id, ArgsSchema, Mode, ConfigSchema, undefined> {
119
+ >(definition: NornWorkflowDefinition<Id, ArgsSchema, ConfigSchema>): NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, undefined> {
124
120
  return createWorkflow({ definition, scope: undefined });
125
121
  }
126
122
 
127
- function createWorkflow<Id extends string, ArgsSchema extends TSchema, Mode extends NornWorkflowIsolationMode, ConfigSchema extends TSchema | undefined, Scope extends NornWorkflowScopeInfo | undefined>(
128
- input: { readonly definition: NornWorkflowDefinition<Id, ArgsSchema, Mode, ConfigSchema, Scope>; readonly scope: Scope },
129
- ): NornWorkflowDeclaration<Id, ArgsSchema, Mode, ConfigSchema, Scope> {
123
+ function createWorkflow<Id extends string, ArgsSchema extends TSchema, ConfigSchema extends TSchema | undefined, Scope extends NornWorkflowScopeInfo | undefined>(
124
+ input: { readonly definition: NornWorkflowDefinition<Id, ArgsSchema, ConfigSchema, Scope>; readonly scope: Scope },
125
+ ): NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, Scope> {
130
126
  const { definition, scope } = input;
131
127
  assertDeclarationId(definition.id);
132
128
  const callable = (args: StaticEncode<ArgsSchema>): NornRunNext => createWorkflowTransition({ workflowId: definition.id, args });
133
129
  const result = Object.assign(callable, definition, {
134
130
  kind: WORKFLOW_DECLARATION_KIND,
135
131
  scope,
136
- isolation: definition.isolation ?? { mode: "runWorkspace" },
137
- }) as NornWorkflowDeclaration<Id, ArgsSchema, Mode, ConfigSchema, Scope>;
132
+ }) as NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, Scope>;
138
133
  if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${definition.id}`);
139
134
  assertWorkflowMetadata(result);
140
135
  return result;
@@ -163,7 +158,6 @@ export type NornRunNext = {
163
158
 
164
159
  export type NornRunOutcomeMetadata = {
165
160
  readonly summary?: string;
166
- readonly artifacts?: Record<string, NornArtifactRef>;
167
161
  readonly logs?: Record<string, NornLogRef>;
168
162
  readonly data?: Record<string, unknown>;
169
163
  };
@@ -180,10 +174,6 @@ export type NornRunFail = {
180
174
 
181
175
  export type WorkflowResult = NornRunNext | NornRunComplete | NornRunFail;
182
176
 
183
- export type NornRunFor<TWorkflow extends NornAnyWorkflowDeclaration> = TWorkflow extends { readonly isolation: { readonly mode: "project" } }
184
- ? NornProjectRun
185
- : NornRun;
186
-
187
177
  export type NornRunStartOptions = {
188
178
  readonly id?: string;
189
179
  readonly name?: string;
@@ -270,6 +260,7 @@ export type NornRunInfo = {
270
260
  readonly id: string;
271
261
  readonly name: string;
272
262
  readonly path: string;
263
+ readonly paths: NornWorkflowPaths;
273
264
  readonly entrypointWorkflowId: string;
274
265
  readonly currentWorkflowId?: string;
275
266
  readonly status: NornRunStatus;
@@ -298,17 +289,11 @@ export type NornRunCheckpoint = {
298
289
  export type NornCommandRunInput = {
299
290
  readonly label: string;
300
291
  readonly command: string | readonly [string, ...string[]];
301
- readonly cwd?: string;
292
+ readonly cwd: string;
302
293
  readonly env?: Record<string, string>;
303
294
  readonly timeoutMs?: number;
304
295
  };
305
296
 
306
- export const artifactRefSchema = Type.Object({
307
- path: Type.String(),
308
- });
309
-
310
- export type NornArtifactRef = StaticDecode<typeof artifactRefSchema>;
311
-
312
297
  const emptyWorkflowRefArgsSchema = Type.Object({});
313
298
  const workflowReferenceInputSchema = Type.Union([
314
299
  Type.String({ minLength: 1 }),
@@ -350,9 +335,9 @@ export type NornAgentBeforeSessionStartContext = {
350
335
  };
351
336
 
352
337
  export type NornAgentCreateSessionInput = {
353
- readonly resourceAdapters?: readonly import("./agent-resource-adapter.ts").NornAgentResourceAdapter[];
338
+ readonly customTools?: readonly ToolDefinition[];
354
339
  readonly label: string;
355
- readonly cwd?: string;
340
+ readonly cwd: string;
356
341
  readonly tools?: string[];
357
342
  readonly beforeSessionStart?: (context: NornAgentBeforeSessionStartContext) => MaybePromise<void>;
358
343
  readonly model?: CreateAgentSessionOptions["model"];
@@ -477,26 +462,11 @@ export type NornAgentSession = {
477
462
  dispose(): Promise<void>;
478
463
  };
479
464
 
480
- export type NornRun = NornRunBase;
481
-
482
- export type NornProjectRun = NornRunBase & {
483
- projectRoot: string;
484
- projectPath(relativePath: string): string;
485
- };
486
-
487
- type NornRunBase = {
465
+ export type NornRun = {
488
466
  id: string;
489
- workspace: string;
490
- cwd: string;
491
- path(relativePath: string): string;
492
467
  next(workflowId: string, args: unknown): NornRunNext;
493
468
  complete(metadata?: NornRunOutcomeMetadata): NornRunComplete;
494
469
  fail(metadata: NornRunOutcomeMetadata & { readonly summary: string }): NornRunFail;
495
- resources: import("./resources.ts").NornResources;
496
- artifacts: {
497
- write(path: string, content: string): Promise<NornArtifactRef>;
498
- read(ref: NornArtifactRef): Promise<string>;
499
- };
500
470
  logs: {
501
471
  read(log: NornLogRef): Promise<string>;
502
472
  };
@@ -525,7 +495,6 @@ export type NornRegisteredWorkflowInfo = {
525
495
  readonly id: string;
526
496
  readonly instructions?: string;
527
497
  readonly isEntrypoint: boolean;
528
- readonly isolation: NornWorkflowIsolation;
529
498
  readonly scope?: { readonly id: string };
530
499
  readonly source?: NornWorkflowSource;
531
500
  readonly configKey: string;
@@ -589,7 +558,7 @@ function assertDeclarationId(id: string): void {
589
558
 
590
559
  export function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowDeclaration {
591
560
  if (typeof value !== "function") return false;
592
- const candidate = value as { kind?: unknown; id?: unknown; instructions?: unknown; isEntrypoint?: unknown; args?: unknown; execute?: unknown; isolation?: { mode?: unknown } };
561
+ const candidate = value as { kind?: unknown; id?: unknown; instructions?: unknown; isEntrypoint?: unknown; args?: unknown; execute?: unknown };
593
562
  return (
594
563
  candidate.kind === WORKFLOW_DECLARATION_KIND &&
595
564
  typeof candidate.id === "string" &&
@@ -597,8 +566,7 @@ export function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowD
597
566
  (candidate.instructions === undefined || typeof candidate.instructions === "string") &&
598
567
  typeof candidate.isEntrypoint === "boolean" &&
599
568
  Boolean(candidate.args) &&
600
- typeof candidate.execute === "function" &&
601
- (candidate.isolation?.mode === "runWorkspace" || candidate.isolation?.mode === "project")
569
+ typeof candidate.execute === "function"
602
570
  );
603
571
  }
604
572
 
@@ -1,5 +1,2 @@
1
1
  export type { CreateAgentSessionOptions, EventBus, PromptOptions, ToolDefinition } from "@earendil-works/pi-coding-agent";
2
2
  export * from "./api.ts";
3
- export { NornFileCoordinator } from "./files.ts";
4
- export * from "./resources.ts";
5
- export * from "./agent-resource-adapter.ts";
@@ -1,4 +1,4 @@
1
- import { artifactRefSchema, workflow, isWorkflowDeclaration, workflowRefSchema, type NornWorkflowRefSchemaOptions } from "@vimhead.dev/norn";
1
+ import { workflow, isWorkflowDeclaration, workflowRefSchema, type NornWorkflowRefSchemaOptions } from "@vimhead.dev/norn";
2
2
  import { inspectSchema } from "@vimhead.dev/norn/schema";
3
3
  import assert from "node:assert/strict";
4
4
  import { Type } from "typebox";
@@ -15,7 +15,6 @@ test("callable declarations retain metadata and construct transitions without ex
15
15
  assert.equal(isWorkflowDeclaration(target), true);
16
16
  assert.equal(isWorkflowDeclaration(() => undefined), false);
17
17
  assert.equal(Value.Check(target.args, { report: "done" }), true);
18
- assert.deepEqual(target.isolation, { mode: "runWorkspace" });
19
18
  assert.deepEqual(target({ report: "done" }), { type: "next", workflowId: target.id, args: { report: "done" } });
20
19
  });
21
20
 
@@ -57,7 +56,7 @@ test("optional reference properties remain optional in validation and inspection
57
56
  test("inspection advertises encoded contributions and invocation does not decode them", () => {
58
57
  let decodes = 0;
59
58
  const contributions = Type.Object({
60
- records: artifactRefSchema,
59
+ records: Type.String(),
61
60
  count: Type.Decode(Type.String(), value => { decodes++; return Number(value); }),
62
61
  label: Type.Optional(Type.String({ default: "collected" })),
63
62
  });
@@ -72,10 +71,10 @@ test("inspection advertises encoded contributions and invocation does not decode
72
71
  expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedArgsSchema.properties.count.type", "string");
73
72
  const forwardArgs = { taskId: "task-42", nested: { labels: ["one", "two"], enabled: false, absent: null } };
74
73
  const next = Value.Decode(schema, { next: { workflow: "refs.finish", forwardArgs } }).next;
75
- const contribution = { records: { path: "records.json" }, count: "2" };
74
+ const contribution = { records: "records.json", count: "2" };
76
75
  assert.deepEqual(next(contribution).args, { ...forwardArgs, ...contribution });
77
76
  assert.equal(decodes, 0);
78
- assert.throws(() => Reflect.apply(next, undefined, [{ records: { path: "records.json" }, count: 2 }]));
77
+ assert.throws(() => Reflect.apply(next, undefined, [{ records: "records.json", count: 2 }]));
79
78
  });
80
79
 
81
80
  test("references without contributions advertise an empty object schema", () => {
@@ -100,10 +99,10 @@ test("optional, nullable and array references retain annotations and callable co
100
99
 
101
100
  test("nested references retain encoded payloads in contributed parameters", () => {
102
101
  const finalContributions = Type.Object({ summary: Type.String() });
103
- const contributions = Type.Object({ records: artifactRefSchema, next: workflowRefSchema({ args: finalContributions }) });
102
+ const contributions = Type.Object({ records: Type.String(), next: workflowRefSchema({ args: finalContributions }) });
104
103
  const reference = workflowRefSchema({ args: contributions });
105
104
  expect(inspectSchema(reference)).toHaveProperty("x-norn-workflow-ref.contributedArgsSchema.properties.next.x-norn-workflow-ref.contributedArgsSchema", inspectSchema(finalContributions));
106
- const args = { records: { path: "records.json" }, next: { workflow: "refs.finish", forwardArgs: { batch: 1 } } };
105
+ const args = { records: "records.json", next: { workflow: "refs.finish", forwardArgs: { batch: 1 } } };
107
106
  const transition = Value.Decode(reference, "intermediate")(args);
108
107
  assert.deepEqual(transition.args, args);
109
108
  assert.deepEqual(Value.Decode(contributions, transition.args).next({ summary: "done" }).args, { batch: 1, summary: "done" });
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ import { Value } from "typebox/value";
18
18
  import { NORN_BUILD_INFO } from "./build-info.js";
19
19
  import { renderNornDocumentationIntro, resolveDocumentationCacheRoot, resolveNornDocumentation } from "./documentation.js";
20
20
  import { NornEngine } from "./internal/engine.js";
21
+ import { resolveNornAgentDirectory } from "./internal/agent-directory.js";
21
22
  import { errorMessage, NornProjectLoadError, NornRunStoppedError } from "./internal/errors.js";
22
23
  import { clearRunResumeRequest, readRunLaunchRequest, readRunResumeRequest, writeRunLaunchRequest, writeRunResumeRequest } from "./internal/launch-request.js";
23
24
  import { readRunMetrics } from "./internal/metrics.js";
@@ -25,6 +26,7 @@ import { getRunLeaseOwner, NornRunLease } from "./internal/run-lease.js";
25
26
  import { generateRunName } from "./internal/run-names.js";
26
27
  import { assertRunVersion, getRunInfo, listRuns, mergeInterruptedWorkflowArgs, resolveRunRoot } from "./internal/run-state.js";
27
28
  import { NornRunStore } from "./internal/run-store.js";
29
+ import { prepareRunWorkerDirectory } from "./internal/worker-directory.js";
28
30
  import { discoverNornProject, findNornProject, inspectNornWorkflow, loadNornProject, NORN_PROJECT_FILE_NAME } from "./workflow-loader.js";
29
31
  var RUNS_ROOT = join(".norn", "runs");
30
32
  var RUN_WAIT_INTERVAL_MS = 1e3;
@@ -715,7 +717,7 @@ async function startRun(workflowId, commandArgs) {
715
717
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
716
718
  await writeRunLaunchRequest(runRoot, { version: 2, type: "run", id, name, workflowId, args, configOverride, createdAt });
717
719
  await startDetachedExecuteRun(id, project.projectRoot);
718
- writeJson({ run: startedRunInfo({ id, name, workflow, runRoot, createdAt }) });
720
+ writeJson({ run: startedRunInfo({ id, name, workflow, runRoot, projectRoot: project.projectRoot, createdAt }) });
719
721
  }
720
722
  async function resumeRun(run, args) {
721
723
  assertNoStructuredInputArgs("runs resume", args);
@@ -822,8 +824,10 @@ async function executeRun(runId) {
822
824
  const runRoot = resolve(location.projectRoot, RUNS_ROOT, runId);
823
825
  const request = await readOptionalRunLaunchRequest(runRoot) ?? await readRunResumeRequest(runRoot);
824
826
  try {
825
- const project = await loadNornProject(process.cwd());
826
- const engine = new NornEngine({ cwd: project.projectRoot, signal: abortController.signal, gateMode: "pause", config: project.projectConfig });
827
+ const agentDir = resolveNornAgentDirectory({ home: homedir(), environment: process.env });
828
+ process.chdir(await prepareRunWorkerDirectory(runRoot));
829
+ const project = await loadNornProject(location.projectRoot);
830
+ const engine = new NornEngine({ cwd: project.projectRoot, agentDir, signal: abortController.signal, gateMode: "pause", config: project.projectConfig });
827
831
  engine.registerWorkflows(project.definitions);
828
832
  if (request.type === "run") {
829
833
  const workflow = project.registry.workflowById(request.workflowId);
@@ -918,6 +922,7 @@ function startedRunInfo(input) {
918
922
  id: input.id,
919
923
  name: input.name,
920
924
  path: input.runRoot,
925
+ paths: { project: input.projectRoot, workspace: join(input.runRoot, "current", "workspace") },
921
926
  entrypointWorkflowId: input.workflow.id,
922
927
  currentWorkflowId: input.workflow.id,
923
928
  status: "running",
@@ -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.35390859630.1";
4
- readonly commit: "7a7976170d62c342c29c6479444394d542c3f22b";
3
+ readonly version: "0.1.0-tip.35436871363.1";
4
+ readonly commit: "49b14d0ed0c9633fc6ee530a557ec7babbf52b22";
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.35390859630.1",
5
- "commit": "7a7976170d62c342c29c6479444394d542c3f22b",
4
+ "version": "0.1.0-tip.35436871363.1",
5
+ "commit": "49b14d0ed0c9633fc6ee530a557ec7babbf52b22",
6
6
  "packageSpec": "@vimhead.dev/norn-cli@tip",
7
7
  "upgrade": {
8
8
  "supported": false,
@@ -7,9 +7,6 @@ import type { NornRunLogger } from "./run-log.ts";
7
7
  type NornAgentRunnerInput = {
8
8
  readonly id: string;
9
9
  readonly runRoot: string;
10
- readonly boundaryRoot: string;
11
- readonly boundaryName: string;
12
- readonly cwd: string;
13
10
  readonly signal?: AbortSignal;
14
11
  readonly model?: CreateAgentSessionOptions["model"];
15
12
  readonly thinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
@@ -24,6 +21,5 @@ export declare class NornAgentRunner {
24
21
  constructor(input: NornAgentRunnerInput);
25
22
  createSession(agentInput: NornAgentCreateSessionInput): Promise<NornAgentSession>;
26
23
  prompt<ResponseSchema extends TSchema>(agentInput: NornAgentSinglePromptInput<ResponseSchema>): Promise<StaticDecode<ResponseSchema>>;
27
- private resolveFromCwd;
28
24
  }
29
25
  export {};
@@ -14,17 +14,16 @@ import { inspectSchema } from "@vimhead.dev/norn/schema";
14
14
  import { randomUUID } from "node:crypto";
15
15
  import { mkdir } from "node:fs/promises";
16
16
  import { homedir } from "node:os";
17
- import { isAbsolute, relative, resolve, sep } from "node:path";
17
+ import { resolve } from "node:path";
18
+ import { requireAbsoluteWorkingDirectory } from "./working-directory.js";
18
19
  import { resolveNornAgentDirectory } from "./agent-directory.js";
19
20
  import {
20
21
  NornAgentResponseToolFactory
21
22
  } from "./agent-response-tool.js";
22
23
  import { errorMessage } from "./errors.js";
23
24
  import { safeFileName } from "./file-names.js";
24
- import { NornSessionResourceBindings } from "./resource-bindings.js";
25
25
  import { agentUsageFromValue, emptyAgentUsage, totalAgentUsage } from "./usage.js";
26
26
  var DEFAULT_AGENT_ATTEMPTS = 3;
27
- var DEFAULT_AGENT_TOOL_ALLOWLIST = ["read", "bash", "edit", "write", AGENT_RESPONSE_TOOL_NAME];
28
27
  var NornAgentRunner = class {
29
28
  constructor(input) {
30
29
  this.input = input;
@@ -33,7 +32,7 @@ var NornAgentRunner = class {
33
32
  input;
34
33
  responseToolFactory;
35
34
  async createSession(agentInput) {
36
- const cwd = agentInput.cwd ? this.resolveFromCwd(agentInput.cwd) : this.input.cwd;
35
+ const cwd = requireAbsoluteWorkingDirectory(agentInput.cwd);
37
36
  const sessionDir = resolve(this.input.runRoot, "sessions");
38
37
  await mkdir(sessionDir, { recursive: true });
39
38
  const eventBus = createEventBus();
@@ -53,31 +52,29 @@ var NornAgentRunner = class {
53
52
  ...loader.getExtensions().errors.map((error) => `Failed to load extension ${error.path}: ${error.error}`)
54
53
  ];
55
54
  if (errors.length > 0) throw new Error(errors.join("\n"));
56
- const resourceBindings = new NornSessionResourceBindings();
55
+ const customTools = agentInput.customTools ?? [];
56
+ assertCustomToolNames({
57
+ tools: customTools,
58
+ reservedNames: [
59
+ "read",
60
+ "bash",
61
+ "edit",
62
+ "write",
63
+ "grep",
64
+ "find",
65
+ "ls",
66
+ "powershell",
67
+ AGENT_RESPONSE_TOOL_NAME,
68
+ ...loader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()])
69
+ ]
70
+ });
57
71
  let session;
58
72
  try {
59
- await resourceBindings.bind({
60
- adapters: agentInput.resourceAdapters ?? [],
61
- runId: this.input.id,
62
- label: agentInput.label,
63
- reservedTools: [
64
- "read",
65
- "bash",
66
- "edit",
67
- "write",
68
- "grep",
69
- "find",
70
- "ls",
71
- "powershell",
72
- AGENT_RESPONSE_TOOL_NAME,
73
- ...loader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()])
74
- ]
75
- });
76
73
  ({ session } = await createAgentSessionFromServices({
77
74
  services,
78
75
  sessionManager: SessionManager.create(cwd, sessionDir),
79
- tools: [...withAgentResponseTool(agentInput.tools), ...resourceBindings.tools.map((tool) => tool.name)],
80
- customTools: [this.responseToolFactory.create(), ...resourceBindings.tools],
76
+ tools: withAgentResponseTool(agentInput.tools),
77
+ customTools: [this.responseToolFactory.create(), ...customTools],
81
78
  model: agentInput.model ?? this.input.model,
82
79
  thinkingLevel: agentInput.thinkingLevel ?? this.input.thinkingLevel
83
80
  }));
@@ -89,7 +86,6 @@ var NornAgentRunner = class {
89
86
  label: agentInput.label,
90
87
  cwd,
91
88
  session,
92
- resourceBindings,
93
89
  events: eventBus
94
90
  });
95
91
  } catch (error) {
@@ -99,12 +95,7 @@ var NornAgentRunner = class {
99
95
  } catch (cleanupError) {
100
96
  errors2.push(cleanupError);
101
97
  }
102
- try {
103
- await resourceBindings.dispose();
104
- } catch (cleanupError) {
105
- errors2.push(cleanupError);
106
- }
107
- if (errors2.length > 1) throw new AggregateError(errors2, "Agent creation and resource cleanup failed");
98
+ if (errors2.length > 1) throw new AggregateError(errors2, "Agent creation and cleanup failed");
108
99
  throw error;
109
100
  }
110
101
  }
@@ -116,14 +107,6 @@ var NornAgentRunner = class {
116
107
  await agent.dispose();
117
108
  }
118
109
  }
119
- resolveFromCwd(path) {
120
- const resolvedPath = isAbsolute(path) ? path : resolve(this.input.cwd, path);
121
- const pathFromBoundary = relative(this.input.boundaryRoot, resolvedPath);
122
- if (pathFromBoundary === ".." || pathFromBoundary.startsWith(`..${sep}`) || isAbsolute(pathFromBoundary)) {
123
- throw new Error(`Agent cwd escapes ${this.input.boundaryName} isolation: ${path}`);
124
- }
125
- return resolvedPath;
126
- }
127
110
  };
128
111
  function systemPromptOverride(agentInput) {
129
112
  const systemPrompt = agentInput.systemPrompt;
@@ -223,11 +206,6 @@ var CreatedNornAgentSession = class {
223
206
  } catch (error) {
224
207
  errors.push(error);
225
208
  }
226
- try {
227
- await this.input.resourceBindings.dispose();
228
- } catch (error) {
229
- errors.push(error);
230
- }
231
209
  if (errors.length) throw new AggregateError(errors, "Agent disposal failed");
232
210
  await this.input.logger.record({ type: "agent.disposed", label: this.label });
233
211
  }
@@ -328,8 +306,14 @@ function withResponseToolFinalizationInstruction(responseSchema, label, response
328
306
  ].join("\n");
329
307
  }
330
308
  function withAgentResponseTool(tools) {
331
- if (!tools) return [...DEFAULT_AGENT_TOOL_ALLOWLIST];
332
- return Array.from(/* @__PURE__ */ new Set([...tools, AGENT_RESPONSE_TOOL_NAME]));
309
+ return tools === void 0 ? void 0 : Array.from(/* @__PURE__ */ new Set([...tools, AGENT_RESPONSE_TOOL_NAME]));
310
+ }
311
+ function assertCustomToolNames(input) {
312
+ const names = new Set(input.reservedNames);
313
+ for (const tool of input.tools) {
314
+ if (names.has(tool.name)) throw new Error(`Custom tool name collision: ${tool.name}`);
315
+ names.add(tool.name);
316
+ }
333
317
  }
334
318
  export {
335
319
  NornAgentRunner
@@ -2,9 +2,6 @@ import type { NornCommandRunInput, NornCommandRunResult } from "@vimhead.dev/nor
2
2
  import type { NornRunLogs } from "./logs.ts";
3
3
  import type { NornRunLogger } from "./run-log.ts";
4
4
  type NornCommandRunnerInput = {
5
- readonly boundaryRoot: string;
6
- readonly boundaryName: string;
7
- readonly cwd: string;
8
5
  readonly signal?: AbortSignal;
9
6
  readonly logs: NornRunLogs;
10
7
  readonly logger: NornRunLogger;
@@ -13,6 +10,5 @@ export declare class NornCommandRunner {
13
10
  private readonly input;
14
11
  constructor(input: NornCommandRunnerInput);
15
12
  run(commandInput: NornCommandRunInput): Promise<NornCommandRunResult>;
16
- private resolveFromCwd;
17
13
  }
18
14
  export {};