@vimhead.dev/norn-cli 0.1.0-tip.35390859630.1 → 0.1.0-tip.35568631545.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 (97) hide show
  1. package/assets/README.md +36 -15
  2. package/assets/docs/README.md +4 -4
  3. package/assets/docs/agents.md +39 -11
  4. package/assets/docs/cli.md +6 -6
  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/recovery.md +1 -1
  9. package/assets/docs/workflows.md +29 -17
  10. package/assets/examples/agent-then-analysis/README.md +8 -8
  11. package/assets/examples/agent-then-analysis/plugin.ts +23 -21
  12. package/assets/examples/caller-selected-continuation/README.md +11 -10
  13. package/assets/examples/caller-selected-continuation/caller.ts +15 -13
  14. package/assets/examples/caller-selected-continuation/producer.ts +11 -8
  15. package/assets/examples/coordinating-multiple-agents/README.md +27 -17
  16. package/assets/examples/coordinating-multiple-agents/plugin.ts +64 -41
  17. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +43 -0
  18. package/assets/examples/coordinating-multiple-agents/work-queue.ts +60 -65
  19. package/assets/examples/getting-started/README.md +25 -10
  20. package/assets/examples/getting-started/plugin.ts +30 -11
  21. package/assets/examples/minimal-workflow/README.md +7 -6
  22. package/assets/examples/minimal-workflow/plugin.ts +8 -5
  23. package/assets/examples/shared-state/README.md +5 -5
  24. package/assets/examples/shared-state/plugin.ts +38 -25
  25. package/assets/examples/shared-state/shared-state.ts +30 -49
  26. package/assets/examples/shared-state/state-tools.ts +68 -0
  27. package/assets/examples/worktree-development-loop/README.md +14 -12
  28. package/assets/examples/worktree-development-loop/scope.ts +1 -1
  29. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +3 -3
  30. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +6 -4
  31. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +12 -11
  32. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +2 -3
  33. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +10 -6
  34. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +14 -11
  35. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +1 -2
  36. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +16 -15
  37. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +1 -2
  38. package/assets/package.json +1 -1
  39. package/assets/packages/cli/src/cli.ts +9 -3
  40. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  41. package/assets/packages/cli/src/internal/agents.ts +24 -32
  42. package/assets/packages/cli/src/internal/commands.ts +2 -14
  43. package/assets/packages/cli/src/internal/engine.ts +35 -49
  44. package/assets/packages/cli/src/internal/execution-context.ts +64 -0
  45. package/assets/packages/cli/src/internal/logs.ts +1 -1
  46. package/assets/packages/cli/src/internal/run-log.ts +1 -1
  47. package/assets/packages/cli/src/internal/run-state.ts +7 -2
  48. package/assets/packages/cli/src/internal/worker-directory.ts +17 -0
  49. package/assets/packages/cli/src/internal/workflow-registry.ts +24 -9
  50. package/assets/packages/cli/src/internal/working-directory.ts +6 -0
  51. package/assets/packages/cli/src/workflow-loader.ts +1 -2
  52. package/assets/packages/sdk/src/api.ts +65 -91
  53. package/assets/packages/sdk/src/index.ts +0 -3
  54. package/assets/tests/workflow-ref.test.ts +9 -9
  55. package/dist/cli.js +8 -3
  56. package/dist/generated-build-info.d.ts +2 -2
  57. package/dist/generated-build-info.js +2 -2
  58. package/dist/internal/agents.d.ts +0 -4
  59. package/dist/internal/agents.js +30 -46
  60. package/dist/internal/commands.d.ts +0 -4
  61. package/dist/internal/commands.js +2 -10
  62. package/dist/internal/engine.d.ts +1 -1
  63. package/dist/internal/engine.js +29 -43
  64. package/dist/internal/execution-context.d.ts +26 -0
  65. package/dist/internal/execution-context.js +55 -0
  66. package/dist/internal/file-coordinator.d.ts +17 -0
  67. package/dist/internal/file-coordinator.js +162 -0
  68. package/dist/internal/logs.d.ts +1 -1
  69. package/dist/internal/run-log.d.ts +1 -1
  70. package/dist/internal/run-state.d.ts +2 -0
  71. package/dist/internal/run-state.js +5 -2
  72. package/dist/internal/worker-directory.d.ts +1 -0
  73. package/dist/internal/worker-directory.js +26 -0
  74. package/dist/internal/workflow-registry.d.ts +13 -3
  75. package/dist/internal/workflow-registry.js +12 -7
  76. package/dist/internal/working-directory.d.ts +1 -0
  77. package/dist/internal/working-directory.js +9 -0
  78. package/dist/workflow-loader.js +1 -2
  79. package/package.json +2 -2
  80. package/assets/docs/resources.md +0 -46
  81. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +0 -51
  82. package/assets/examples/shared-state/state-adapter.ts +0 -76
  83. package/assets/packages/cli/src/internal/artifacts.ts +0 -26
  84. package/assets/packages/cli/src/internal/resource-bindings.ts +0 -35
  85. package/assets/packages/cli/src/internal/run.ts +0 -127
  86. package/assets/packages/cli/src/resources.ts +0 -69
  87. package/assets/packages/sdk/src/agent-resource-adapter.ts +0 -11
  88. package/assets/packages/sdk/src/resources.ts +0 -20
  89. package/dist/internal/artifacts.d.ts +0 -10
  90. package/dist/internal/artifacts.js +0 -29
  91. package/dist/internal/resource-bindings.d.ts +0 -13
  92. package/dist/internal/resource-bindings.js +0 -34
  93. package/dist/internal/run.d.ts +0 -49
  94. package/dist/internal/run.js +0 -103
  95. package/dist/resources.d.ts +0 -11
  96. package/dist/resources.js +0 -100
  97. /package/assets/packages/{sdk/src/files.ts → cli/src/internal/file-coordinator.ts} +0 -0
@@ -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,37 +69,37 @@ 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 agents: NornAgents;
83
+ readonly commands: NornCommands;
84
+ readonly logs: NornLogs;
85
+ readonly run: NornRun;
84
86
  } & (Scope extends NornWorkflowScopeInfo ? {
85
87
  readonly scope: { readonly id: Scope["id"]; readonly config: WorkflowConfig<Scope["config"]> };
86
88
  } : {});
87
89
 
88
90
  export type NornWorkflowDefinition<
89
- Id extends string = string,
91
+ Name extends string = string,
90
92
  ArgsSchema extends TSchema = TSchema,
91
- IsolationMode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode,
92
93
  ConfigSchema extends TSchema | undefined = undefined,
93
94
  Scope extends NornWorkflowScopeInfo | undefined = undefined,
94
95
  > = {
95
- readonly id: Id;
96
+ readonly name: Name;
96
97
  readonly args: ArgsSchema;
97
98
  readonly config?: ConfigSchema;
98
- readonly isolation?: NornWorkflowIsolation<IsolationMode>;
99
99
  readonly gate?: NornWorkflowGate<ArgsSchema> & {
100
- describe?(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<string>;
100
+ describe?(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<string>;
101
101
  };
102
- execute(context: NornWorkflowContext<ArgsSchema, IsolationMode, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
102
+ execute(context: NornWorkflowContext<ArgsSchema, ConfigSchema, Scope>): MaybePromise<WorkflowResult>;
103
103
  } & NornWorkflowInstructions;
104
104
 
105
105
  export type NornWorkflowScopeInfo = {
@@ -107,50 +107,48 @@ export type NornWorkflowScopeInfo = {
107
107
  readonly config?: TSchema;
108
108
  };
109
109
 
110
- export type NornWorkflowScope<Id extends string, ConfigSchema extends TSchema | undefined> = {
111
- readonly id: Id;
110
+ export type NornWorkflowScope<Name extends string, ConfigSchema extends TSchema | undefined> = {
111
+ readonly id: Name;
112
112
  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 }>;
113
+ workflow<const LocalName extends string, ArgsSchema extends TSchema, LocalConfig extends TSchema | undefined = undefined>(
114
+ definition: NornWorkflowDefinition<LocalName, ArgsSchema, LocalConfig, { readonly id: Name; readonly config: ConfigSchema }>,
115
+ ): NornWorkflowDeclaration<`${Name}.${LocalName}`, ArgsSchema, LocalConfig, { readonly id: Name; readonly config: ConfigSchema }>;
116
116
  };
117
117
 
118
118
  export function workflow<
119
- const Id extends string,
119
+ const Name extends string,
120
120
  ArgsSchema extends TSchema,
121
- const Mode extends NornWorkflowIsolationMode = "runWorkspace",
122
121
  ConfigSchema extends TSchema | undefined = undefined,
123
- >(definition: NornWorkflowDefinition<Id, ArgsSchema, Mode, ConfigSchema>): NornWorkflowDeclaration<Id, ArgsSchema, Mode, ConfigSchema, undefined> {
124
- return createWorkflow({ definition, scope: undefined });
122
+ >(definition: NornWorkflowDefinition<Name, ArgsSchema, ConfigSchema>): NornWorkflowDeclaration<Name, ArgsSchema, ConfigSchema, undefined> {
123
+ return createWorkflow({ definition, id: definition.name, scope: undefined });
125
124
  }
126
125
 
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> {
130
- const { definition, scope } = input;
131
- assertDeclarationId(definition.id);
132
- const callable = (args: StaticEncode<ArgsSchema>): NornRunNext => createWorkflowTransition({ workflowId: definition.id, args });
133
- const result = Object.assign(callable, definition, {
126
+ function createWorkflow<Id extends string, ArgsSchema extends TSchema, ConfigSchema extends TSchema | undefined, Scope extends NornWorkflowScopeInfo | undefined>(
127
+ input: { readonly definition: NornWorkflowDefinition<string, ArgsSchema, ConfigSchema, Scope>; readonly id: Id; readonly scope: Scope },
128
+ ): NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, Scope> {
129
+ const { definition: { name, ...properties }, id, scope } = input;
130
+ assertDeclarationName(name);
131
+ const callable = (args: StaticEncode<ArgsSchema>): NornRunNext => createWorkflowTransition({ workflowId: id, args });
132
+ const result = Object.assign(callable, properties, {
134
133
  kind: WORKFLOW_DECLARATION_KIND,
134
+ id,
135
135
  scope,
136
- isolation: definition.isolation ?? { mode: "runWorkspace" },
137
- }) as NornWorkflowDeclaration<Id, ArgsSchema, Mode, ConfigSchema, Scope>;
138
- if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${definition.id}`);
136
+ }) as NornWorkflowDeclaration<Id, ArgsSchema, ConfigSchema, Scope>;
137
+ if (!isWorkflowDeclaration(result)) throw new Error(`Invalid workflow definition: ${id}`);
139
138
  assertWorkflowMetadata(result);
140
139
  return result;
141
140
  }
142
141
 
143
- export function workflowScope<const Id extends string, ConfigSchema extends TSchema | undefined = undefined>(
144
- input: { readonly id: Id; readonly config?: ConfigSchema },
145
- ): NornWorkflowScope<Id, ConfigSchema> {
146
- assertDeclarationId(input.id);
147
- const scope = { id: input.id, config: input.config as ConfigSchema };
142
+ export function workflowScope<const Name extends string, ConfigSchema extends TSchema | undefined = undefined>(
143
+ input: { readonly name: Name; readonly config?: ConfigSchema },
144
+ ): NornWorkflowScope<Name, ConfigSchema> {
145
+ assertDeclarationName(input.name);
146
+ const scope = { id: input.name, config: input.config as ConfigSchema };
148
147
  return {
149
- id: input.id,
150
- config: input.config as ConfigSchema,
148
+ id: scope.id,
149
+ config: scope.config,
151
150
  workflow(definition) {
152
- assertDeclarationId(definition.id);
153
- return createWorkflow({ definition: { ...definition, id: `${input.id}.${definition.id}` }, scope });
151
+ return createWorkflow({ definition, id: `${scope.id}.${definition.name}`, scope });
154
152
  },
155
153
  };
156
154
  }
@@ -163,7 +161,6 @@ export type NornRunNext = {
163
161
 
164
162
  export type NornRunOutcomeMetadata = {
165
163
  readonly summary?: string;
166
- readonly artifacts?: Record<string, NornArtifactRef>;
167
164
  readonly logs?: Record<string, NornLogRef>;
168
165
  readonly data?: Record<string, unknown>;
169
166
  };
@@ -180,10 +177,6 @@ export type NornRunFail = {
180
177
 
181
178
  export type WorkflowResult = NornRunNext | NornRunComplete | NornRunFail;
182
179
 
183
- export type NornRunFor<TWorkflow extends NornAnyWorkflowDeclaration> = TWorkflow extends { readonly isolation: { readonly mode: "project" } }
184
- ? NornProjectRun
185
- : NornRun;
186
-
187
180
  export type NornRunStartOptions = {
188
181
  readonly id?: string;
189
182
  readonly name?: string;
@@ -270,6 +263,7 @@ export type NornRunInfo = {
270
263
  readonly id: string;
271
264
  readonly name: string;
272
265
  readonly path: string;
266
+ readonly paths: NornWorkflowPaths;
273
267
  readonly entrypointWorkflowId: string;
274
268
  readonly currentWorkflowId?: string;
275
269
  readonly status: NornRunStatus;
@@ -298,17 +292,11 @@ export type NornRunCheckpoint = {
298
292
  export type NornCommandRunInput = {
299
293
  readonly label: string;
300
294
  readonly command: string | readonly [string, ...string[]];
301
- readonly cwd?: string;
295
+ readonly cwd: string;
302
296
  readonly env?: Record<string, string>;
303
297
  readonly timeoutMs?: number;
304
298
  };
305
299
 
306
- export const artifactRefSchema = Type.Object({
307
- path: Type.String(),
308
- });
309
-
310
- export type NornArtifactRef = StaticDecode<typeof artifactRefSchema>;
311
-
312
300
  const emptyWorkflowRefArgsSchema = Type.Object({});
313
301
  const workflowReferenceInputSchema = Type.Union([
314
302
  Type.String({ minLength: 1 }),
@@ -350,9 +338,9 @@ export type NornAgentBeforeSessionStartContext = {
350
338
  };
351
339
 
352
340
  export type NornAgentCreateSessionInput = {
353
- readonly resourceAdapters?: readonly import("./agent-resource-adapter.ts").NornAgentResourceAdapter[];
341
+ readonly customTools?: readonly ToolDefinition[];
354
342
  readonly label: string;
355
- readonly cwd?: string;
343
+ readonly cwd: string;
356
344
  readonly tools?: string[];
357
345
  readonly beforeSessionStart?: (context: NornAgentBeforeSessionStartContext) => MaybePromise<void>;
358
346
  readonly model?: CreateAgentSessionOptions["model"];
@@ -477,36 +465,24 @@ export type NornAgentSession = {
477
465
  dispose(): Promise<void>;
478
466
  };
479
467
 
480
- export type NornRun = NornRunBase;
481
-
482
- export type NornProjectRun = NornRunBase & {
483
- projectRoot: string;
484
- projectPath(relativePath: string): string;
485
- };
486
-
487
- type NornRunBase = {
468
+ export type NornRun = {
488
469
  id: string;
489
- workspace: string;
490
- cwd: string;
491
- path(relativePath: string): string;
492
470
  next(workflowId: string, args: unknown): NornRunNext;
493
471
  complete(metadata?: NornRunOutcomeMetadata): NornRunComplete;
494
472
  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
- logs: {
501
- read(log: NornLogRef): Promise<string>;
502
- };
503
- commands: {
504
- run(input: NornCommandRunInput): Promise<NornCommandRunResult>;
505
- };
506
- agents: {
507
- createSession(input: NornAgentCreateSessionInput): Promise<NornAgentSession>;
508
- prompt<ResponseSchema extends TSchema>(input: NornAgentSinglePromptInput<ResponseSchema>): Promise<StaticDecode<ResponseSchema>>;
509
- };
473
+ };
474
+
475
+ export type NornLogs = {
476
+ read(log: NornLogRef): Promise<string>;
477
+ };
478
+
479
+ export type NornCommands = {
480
+ run(input: NornCommandRunInput): Promise<NornCommandRunResult>;
481
+ };
482
+
483
+ export type NornAgents = {
484
+ createSession(input: NornAgentCreateSessionInput): Promise<NornAgentSession>;
485
+ prompt<ResponseSchema extends TSchema>(input: NornAgentSinglePromptInput<ResponseSchema>): Promise<StaticDecode<ResponseSchema>>;
510
486
  };
511
487
 
512
488
  export type NornWorkflowSource = {
@@ -525,7 +501,6 @@ export type NornRegisteredWorkflowInfo = {
525
501
  readonly id: string;
526
502
  readonly instructions?: string;
527
503
  readonly isEntrypoint: boolean;
528
- readonly isolation: NornWorkflowIsolation;
529
504
  readonly scope?: { readonly id: string };
530
505
  readonly source?: NornWorkflowSource;
531
506
  readonly configKey: string;
@@ -583,13 +558,13 @@ export type NornProjectInfo = {
583
558
  readonly modules: readonly NornWorkflowSource[];
584
559
  };
585
560
 
586
- function assertDeclarationId(id: string): void {
587
- if (typeof id !== "string" || id.trim().length === 0 || id.split(".").some(segment => segment.length === 0)) throw new Error(`Invalid workflow or scope id: ${id}`);
561
+ function assertDeclarationName(name: string): void {
562
+ if (typeof name !== "string" || name.trim().length === 0 || name.includes(".")) throw new Error(`Invalid workflow or scope name: ${name}; expected a nonempty name without dots`);
588
563
  }
589
564
 
590
565
  export function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowDeclaration {
591
566
  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 } };
567
+ const candidate = value as { kind?: unknown; id?: unknown; instructions?: unknown; isEntrypoint?: unknown; args?: unknown; execute?: unknown };
593
568
  return (
594
569
  candidate.kind === WORKFLOW_DECLARATION_KIND &&
595
570
  typeof candidate.id === "string" &&
@@ -597,8 +572,7 @@ export function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowD
597
572
  (candidate.instructions === undefined || typeof candidate.instructions === "string") &&
598
573
  typeof candidate.isEntrypoint === "boolean" &&
599
574
  Boolean(candidate.args) &&
600
- typeof candidate.execute === "function" &&
601
- (candidate.isolation?.mode === "runWorkspace" || candidate.isolation?.mode === "project")
575
+ typeof candidate.execute === "function"
602
576
  );
603
577
  }
604
578
 
@@ -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,12 +1,13 @@
1
- import { artifactRefSchema, workflow, isWorkflowDeclaration, workflowRefSchema, type NornWorkflowRefSchemaOptions } from "@vimhead.dev/norn";
1
+ import { workflowScope, 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";
5
5
  import { Value } from "typebox/value";
6
6
  import { expect, expectTypeOf, test } from "vitest";
7
7
 
8
- const target = workflow({
9
- id: "refs.finish", isEntrypoint: false, args: Type.Object({ report: Type.String() }),
8
+ const refs = workflowScope({ name: "refs" });
9
+ const target = refs.workflow({
10
+ name: "finish", isEntrypoint: false, args: Type.Object({ report: Type.String() }),
10
11
  execute() { throw new Error("A callable must select a transition, not execute it"); },
11
12
  });
12
13
 
@@ -15,7 +16,6 @@ test("callable declarations retain metadata and construct transitions without ex
15
16
  assert.equal(isWorkflowDeclaration(target), true);
16
17
  assert.equal(isWorkflowDeclaration(() => undefined), false);
17
18
  assert.equal(Value.Check(target.args, { report: "done" }), true);
18
- assert.deepEqual(target.isolation, { mode: "runWorkspace" });
19
19
  assert.deepEqual(target({ report: "done" }), { type: "next", workflowId: target.id, args: { report: "done" } });
20
20
  });
21
21
 
@@ -57,7 +57,7 @@ test("optional reference properties remain optional in validation and inspection
57
57
  test("inspection advertises encoded contributions and invocation does not decode them", () => {
58
58
  let decodes = 0;
59
59
  const contributions = Type.Object({
60
- records: artifactRefSchema,
60
+ records: Type.String(),
61
61
  count: Type.Decode(Type.String(), value => { decodes++; return Number(value); }),
62
62
  label: Type.Optional(Type.String({ default: "collected" })),
63
63
  });
@@ -72,10 +72,10 @@ test("inspection advertises encoded contributions and invocation does not decode
72
72
  expect(inspected).toHaveProperty("properties.next.x-norn-workflow-ref.contributedArgsSchema.properties.count.type", "string");
73
73
  const forwardArgs = { taskId: "task-42", nested: { labels: ["one", "two"], enabled: false, absent: null } };
74
74
  const next = Value.Decode(schema, { next: { workflow: "refs.finish", forwardArgs } }).next;
75
- const contribution = { records: { path: "records.json" }, count: "2" };
75
+ const contribution = { records: "records.json", count: "2" };
76
76
  assert.deepEqual(next(contribution).args, { ...forwardArgs, ...contribution });
77
77
  assert.equal(decodes, 0);
78
- assert.throws(() => Reflect.apply(next, undefined, [{ records: { path: "records.json" }, count: 2 }]));
78
+ assert.throws(() => Reflect.apply(next, undefined, [{ records: "records.json", count: 2 }]));
79
79
  });
80
80
 
81
81
  test("references without contributions advertise an empty object schema", () => {
@@ -100,10 +100,10 @@ test("optional, nullable and array references retain annotations and callable co
100
100
 
101
101
  test("nested references retain encoded payloads in contributed parameters", () => {
102
102
  const finalContributions = Type.Object({ summary: Type.String() });
103
- const contributions = Type.Object({ records: artifactRefSchema, next: workflowRefSchema({ args: finalContributions }) });
103
+ const contributions = Type.Object({ records: Type.String(), next: workflowRefSchema({ args: finalContributions }) });
104
104
  const reference = workflowRefSchema({ args: contributions });
105
105
  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 } } };
106
+ const args = { records: "records.json", next: { workflow: "refs.finish", forwardArgs: { batch: 1 } } };
107
107
  const transition = Value.Decode(reference, "intermediate")(args);
108
108
  assert.deepEqual(transition.args, args);
109
109
  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.35568631545.1";
4
+ readonly commit: "f16f4b16ede3d325d56cdbd1f21193b3a3d45d38";
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.35568631545.1",
5
+ "commit": "f16f4b16ede3d325d56cdbd1f21193b3a3d45d38",
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 {};
@@ -1,7 +1,7 @@
1
1
  // src/internal/commands.ts
2
2
  import { spawn } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
- import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { requireAbsoluteWorkingDirectory } from "./working-directory.js";
5
5
  import { errorMessage } from "./errors.js";
6
6
  import { safeFileName } from "./file-names.js";
7
7
  var NornCommandRunner = class {
@@ -10,7 +10,7 @@ var NornCommandRunner = class {
10
10
  }
11
11
  input;
12
12
  async run(commandInput) {
13
- const cwd = this.resolveFromCwd(commandInput.cwd ?? this.input.cwd);
13
+ const cwd = requireAbsoluteWorkingDirectory(commandInput.cwd);
14
14
  const startedAtMs = Date.now();
15
15
  const invocationId = randomUUID();
16
16
  const stdoutRef = commandLog({ label: commandInput.label, invocationId, stream: "stdout" });
@@ -56,14 +56,6 @@ var NornCommandRunner = class {
56
56
  });
57
57
  return commandResult;
58
58
  }
59
- resolveFromCwd(path) {
60
- const resolvedPath = isAbsolute(path) ? path : resolve(this.input.cwd, path);
61
- const pathFromBoundary = relative(this.input.boundaryRoot, resolvedPath);
62
- if (pathFromBoundary === ".." || pathFromBoundary.startsWith(`..${sep}`) || isAbsolute(pathFromBoundary)) {
63
- throw new Error(`Command cwd escapes ${this.input.boundaryName} isolation: ${path}`);
64
- }
65
- return resolvedPath;
66
- }
67
59
  };
68
60
  async function spawnCommand(input) {
69
61
  const command = typeof input.command === "string" ? "bash" : input.command[0];