@vimhead.dev/norn 0.1.0-tip.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Norn SDK
2
+
3
+ TypeScript authoring APIs for reusable Norn workflows.
4
+
5
+ ```bash
6
+ npm install -D @vimhead.dev/norn@tip
7
+ ```
8
+
9
+ Import declarations and helpers from `@vimhead.dev/norn`. Match the SDK version
10
+ to the runtime reported by `norn version`.
11
+
12
+ Use `norn docs inspect` for matching local documentation and runnable examples.
13
+ [Installation and project overview](https://github.com/vimhead/norn#readme).
@@ -0,0 +1,12 @@
1
+ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ export type NornAgentResourceBinding = {
3
+ readonly tools: readonly ToolDefinition[];
4
+ dispose(): Promise<void>;
5
+ };
6
+ export type NornAgentResourceAdapter = {
7
+ readonly name: string;
8
+ bind(context: {
9
+ readonly runId: string;
10
+ readonly label: string;
11
+ }): Promise<NornAgentResourceBinding>;
12
+ };
File without changes
package/dist/api.d.ts ADDED
@@ -0,0 +1,550 @@
1
+ import type { CreateAgentSessionOptions, EventBus, PromptOptions } from "@earendil-works/pi-coding-agent";
2
+ import { z } from "zod";
3
+ import type { NornResolvedSeerModeConfig } from "./seer/config.ts";
4
+ declare const WORKFLOW_DECLARATION_KIND = "norn.workflow";
5
+ export type MaybePromise<T> = T | Promise<T>;
6
+ export type NornDispose = () => void;
7
+ export type NornWorkflowAnyGate = {
8
+ readonly enabled: true;
9
+ readonly fields?: readonly string[];
10
+ };
11
+ export type NornWorkflowIsolationMode = "runWorkspace" | "project";
12
+ export type NornWorkflowIsolation<Mode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode> = {
13
+ readonly mode: Mode;
14
+ };
15
+ declare const nornWorkflowRefParamsBrand: unique symbol;
16
+ declare const nornWorkflowRefForwardParamsBrand: unique symbol;
17
+ type NornWorkflowRefForwardParams = Record<string, unknown> & {
18
+ readonly [nornWorkflowRefForwardParamsBrand]: true;
19
+ };
20
+ export type NornWorkflowRef<ParamsSchema extends z.ZodType = z.ZodType, Id extends string = string, ForwardParams = unknown> = Id & {
21
+ readonly [nornWorkflowRefParamsBrand]: (params: z.input<ParamsSchema> & ForwardParams) => z.input<ParamsSchema> & ForwardParams;
22
+ };
23
+ export type NornWorkflowDeclaration<Id extends string = string, ParamsSchema extends z.ZodType = z.ZodType, IsolationMode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode> = {
24
+ readonly kind: typeof WORKFLOW_DECLARATION_KIND;
25
+ readonly id: NornWorkflowRef<ParamsSchema, Id>;
26
+ readonly isEntrypoint: boolean;
27
+ readonly instructions?: string;
28
+ readonly params: ParamsSchema;
29
+ readonly gate?: NornWorkflowAnyGate;
30
+ readonly isolation: NornWorkflowIsolation<IsolationMode>;
31
+ };
32
+ export type NornAnyWorkflowDeclaration = NornWorkflowDeclaration<string, z.ZodType<any, any>>;
33
+ export type NornWorkflowTarget<ParamsSchema extends z.ZodType = z.ZodType> = NornWorkflowDeclaration<string, ParamsSchema> | NornWorkflowRef<ParamsSchema>;
34
+ export type NornWorkflowRefSchemaInput<ParamsSchema extends z.ZodType = z.ZodType> = string | {
35
+ readonly id: NornWorkflowRef<ParamsSchema>;
36
+ readonly workflow?: never;
37
+ readonly forwardParams?: never;
38
+ };
39
+ export type NornWorkflowRefSchemaOptions<ParamsSchema extends z.ZodType = z.ZodType> = {
40
+ readonly params?: ParamsSchema;
41
+ };
42
+ export type NornWorkflowRefInput<ParamsSchema extends z.ZodType> = NornWorkflowRefSchemaInput<ParamsSchema> | {
43
+ readonly workflow: NornWorkflowRefSchemaInput<z.ZodType<any, any>>;
44
+ readonly forwardParams: Record<string, unknown>;
45
+ };
46
+ export type NornWorkflowRefOutput<ParamsSchema extends z.ZodType> = {
47
+ readonly workflow: NornWorkflowRef<ParamsSchema, string, NornWorkflowRefForwardParams>;
48
+ readonly forwardParams: NornWorkflowRefForwardParams;
49
+ };
50
+ export type NornWorkflowParamsInput<TWorkflow extends NornAnyWorkflowDeclaration> = z.input<TWorkflow["params"]>;
51
+ export type NornWorkflowParams<TWorkflow extends NornAnyWorkflowDeclaration> = z.output<TWorkflow["params"]>;
52
+ export type NornWorkflowTargetParamsInput<TWorkflow extends NornWorkflowTarget<any>> = TWorkflow extends NornWorkflowDeclaration<string, infer ParamsSchema> ? z.input<ParamsSchema> : TWorkflow extends NornWorkflowRef<infer ParamsSchema, string, infer ForwardParams> ? z.input<ParamsSchema> & ForwardParams : never;
53
+ export type NornWorkflowGate<ParamsSchema extends z.ZodType> = unknown extends z.input<ParamsSchema> ? NornWorkflowAnyGate : z.input<ParamsSchema> extends Record<string, unknown> ? {
54
+ readonly enabled: true;
55
+ readonly fields?: readonly Extract<keyof z.input<ParamsSchema>, string>[];
56
+ } : {
57
+ readonly enabled: true;
58
+ readonly fields?: never;
59
+ };
60
+ type NornWorkflowInstructions = {
61
+ readonly isEntrypoint: true;
62
+ readonly instructions: string;
63
+ } | {
64
+ readonly isEntrypoint: false;
65
+ readonly instructions?: string;
66
+ };
67
+ export type NornWorkflowDefinition<Id extends string | undefined = string | undefined, ParamsSchema extends z.ZodType = z.ZodType, IsolationMode extends NornWorkflowIsolationMode = NornWorkflowIsolationMode> = {
68
+ readonly id?: Id;
69
+ readonly params: ParamsSchema;
70
+ readonly gate?: NornWorkflowGate<ParamsSchema>;
71
+ readonly isolation?: NornWorkflowIsolation<IsolationMode>;
72
+ } & NornWorkflowInstructions;
73
+ export type NornAnyWorkflowDefinition = {
74
+ readonly id?: string;
75
+ readonly params: z.ZodType;
76
+ readonly gate?: NornWorkflowAnyGate;
77
+ readonly isolation?: NornWorkflowIsolation;
78
+ } & NornWorkflowInstructions;
79
+ export type NornWorkflowStateDefinition<T = unknown, Id extends string = string> = {
80
+ readonly id: Id;
81
+ readonly description?: string;
82
+ readonly schema: z.ZodType<T>;
83
+ };
84
+ export type NornWorkflowStateDefinitionInput<T = unknown, Id extends string | undefined = string | undefined> = {
85
+ readonly id?: Id;
86
+ readonly description?: string;
87
+ readonly schema: z.ZodType<T>;
88
+ };
89
+ export type NornWorkflowPluginWorkflows = Record<string, NornAnyWorkflowDefinition>;
90
+ export type NornWorkflowPluginStateTree = {
91
+ readonly [key: string]: NornWorkflowPluginStateTreeNode;
92
+ };
93
+ export type NornWorkflowPluginStateTreeNode = z.ZodType | NornWorkflowStateDefinitionInput | NornWorkflowPluginStateTree;
94
+ type JoinPath<Head extends string, Parts extends readonly string[]> = Parts extends readonly [] ? Head : Parts extends readonly [infer First extends string, ...infer Rest extends string[]] ? JoinPath<`${Head}.${First}`, Rest> : string;
95
+ type NornInvalidGateFields<TWorkflow> = TWorkflow extends {
96
+ readonly params: infer ParamsSchema extends z.ZodType;
97
+ readonly gate: {
98
+ readonly fields: infer Fields extends readonly string[];
99
+ };
100
+ } ? Exclude<Fields[number], Extract<keyof z.input<ParamsSchema>, string>> : never;
101
+ type NornValidatedWorkflowGate<TWorkflow> = [NornInvalidGateFields<TWorkflow>] extends [never] ? unknown : {
102
+ readonly gate: {
103
+ readonly fields: readonly Extract<keyof z.input<TWorkflow extends {
104
+ readonly params: infer ParamsSchema extends z.ZodType;
105
+ } ? ParamsSchema : z.ZodType>, string>[];
106
+ };
107
+ };
108
+ export type NornValidatedWorkflowGates<Workflows> = {
109
+ readonly [Key in keyof Workflows]: NornValidatedWorkflowGate<Workflows[Key]>;
110
+ };
111
+ export type NornQualifiedPluginWorkflow<PluginId extends string, WorkflowKey extends string, TWorkflow extends NornAnyWorkflowDefinition> = TWorkflow extends {
112
+ readonly params: infer ParamsSchema extends z.ZodType;
113
+ } ? NornWorkflowDeclaration<TWorkflow extends {
114
+ readonly id: infer ExplicitId extends string;
115
+ } ? ExplicitId : `${PluginId}.${WorkflowKey}`, ParamsSchema, TWorkflow extends {
116
+ readonly isolation: {
117
+ readonly mode: infer IsolationMode extends NornWorkflowIsolationMode;
118
+ };
119
+ } ? IsolationMode : "runWorkspace"> : never;
120
+ export type NornQualifiedPluginWorkflows<PluginId extends string, Workflows extends NornWorkflowPluginWorkflows> = {
121
+ readonly [Key in keyof Workflows]: NornQualifiedPluginWorkflow<PluginId, Key & string, Workflows[Key]>;
122
+ };
123
+ export type NornQualifiedPluginStates<PluginId extends string, States, Path extends readonly string[] = []> = {
124
+ readonly [Key in keyof States]: States[Key] extends z.ZodType ? NornWorkflowStateDefinition<z.output<States[Key]>, JoinPath<PluginId, [...Path, Key & string]>> : States[Key] extends NornWorkflowStateDefinitionInput<infer Value> ? NornWorkflowStateDefinition<Value, States[Key] extends {
125
+ readonly id: infer ExplicitId extends string;
126
+ } ? ExplicitId : JoinPath<PluginId, [...Path, Key & string]>> : States[Key] extends NornWorkflowPluginStateTree ? NornQualifiedPluginStates<PluginId, States[Key], [...Path, Key & string]> : never;
127
+ };
128
+ export type NornWorkflowPluginManifest<PluginId extends string = string, ConfigSchema extends z.ZodType | undefined = z.ZodType | undefined, Workflows extends Record<string, NornAnyWorkflowDeclaration> = Record<string, NornAnyWorkflowDeclaration>, States = undefined> = {
129
+ readonly id: PluginId;
130
+ readonly config?: ConfigSchema;
131
+ readonly workflows: Workflows;
132
+ readonly states: States;
133
+ };
134
+ export type NornAnyWorkflowPluginManifest = NornWorkflowPluginManifest<string, z.ZodType | undefined, Record<string, NornAnyWorkflowDeclaration>, unknown>;
135
+ export type NornWorkflowPluginConfigSchema<TManifest extends NornAnyWorkflowPluginManifest> = TManifest extends {
136
+ readonly config?: infer ConfigSchema extends z.ZodType | undefined;
137
+ } ? ConfigSchema : undefined;
138
+ export type NornWorkflowPluginConfig<TManifest extends NornAnyWorkflowPluginManifest> = NonNullable<NornWorkflowPluginConfigSchema<TManifest>> extends z.ZodType ? z.output<NonNullable<NornWorkflowPluginConfigSchema<TManifest>>> : undefined;
139
+ export type NornDefinePluginManifestInput<PluginId extends string, ConfigSchema extends z.ZodType | undefined, Workflows extends NornWorkflowPluginWorkflows, States extends NornWorkflowPluginStateTree | undefined> = {
140
+ readonly id: PluginId;
141
+ readonly config?: ConfigSchema;
142
+ readonly workflows: Workflows & NornValidatedWorkflowGates<Workflows>;
143
+ readonly states?: States;
144
+ };
145
+ export declare function definePluginManifest<const PluginId extends string, const ConfigSchema extends z.ZodType | undefined = undefined, const Workflows extends NornWorkflowPluginWorkflows = NornWorkflowPluginWorkflows, const States extends NornWorkflowPluginStateTree | undefined = undefined>(input: NornDefinePluginManifestInput<PluginId, ConfigSchema, Workflows, States>): NornWorkflowPluginManifest<PluginId, ConfigSchema, NornQualifiedPluginWorkflows<PluginId, Workflows>, States extends NornWorkflowPluginStateTree ? NornQualifiedPluginStates<PluginId, States> : undefined>;
146
+ export type NornRunNext = {
147
+ readonly type: "next";
148
+ readonly workflowId: string;
149
+ readonly params: unknown;
150
+ };
151
+ export type NornRunOutcomeMetadata = {
152
+ readonly summary?: string;
153
+ readonly artifacts?: Record<string, NornArtifactRef>;
154
+ readonly logs?: Record<string, NornLogRef>;
155
+ readonly data?: Record<string, unknown>;
156
+ };
157
+ export type NornRunComplete = {
158
+ readonly type: "complete";
159
+ readonly metadata?: NornRunOutcomeMetadata;
160
+ };
161
+ export type NornRunFail = {
162
+ readonly type: "fail";
163
+ readonly metadata: NornRunOutcomeMetadata & {
164
+ readonly summary: string;
165
+ };
166
+ };
167
+ export type NornWorkflowExecutionResult = NornRunNext | NornRunComplete | NornRunFail;
168
+ export type NornRunFor<TWorkflow extends NornAnyWorkflowDeclaration> = TWorkflow extends {
169
+ readonly isolation: {
170
+ readonly mode: "project";
171
+ };
172
+ } ? NornProjectRun : NornRun;
173
+ export type NornWorkflowGateImplementation<TWorkflow extends NornAnyWorkflowDeclaration, TConfig> = {
174
+ describe(run: NornRunFor<TWorkflow>, params: NornWorkflowParams<TWorkflow>, config: TConfig): MaybePromise<string>;
175
+ };
176
+ export type NornWorkflowImplementation<TWorkflow extends NornAnyWorkflowDeclaration, TConfig = unknown> = {
177
+ readonly gate?: NornWorkflowGateImplementation<TWorkflow, TConfig>;
178
+ execute(run: NornRunFor<TWorkflow>, params: NornWorkflowParams<TWorkflow>, config: TConfig): MaybePromise<NornWorkflowExecutionResult>;
179
+ };
180
+ export type NornRunStartOptions = {
181
+ readonly id?: string;
182
+ readonly name?: string;
183
+ readonly configOverride?: unknown;
184
+ };
185
+ export type NornStartedRunResult = {
186
+ readonly status: "running";
187
+ readonly id: string;
188
+ readonly name: string;
189
+ readonly path: string;
190
+ readonly workspace: string;
191
+ readonly cwd: string;
192
+ readonly workflowId: string;
193
+ };
194
+ export type NornCompletedRunResult = {
195
+ readonly status: "completed";
196
+ readonly id: string;
197
+ readonly name: string;
198
+ readonly workspace: string;
199
+ readonly cwd: string;
200
+ readonly workflowId: string;
201
+ readonly metadata?: NornRunOutcomeMetadata;
202
+ };
203
+ export type NornFailedRunResult = {
204
+ readonly status: "failed";
205
+ readonly id: string;
206
+ readonly name: string;
207
+ readonly workspace: string;
208
+ readonly cwd: string;
209
+ readonly workflowId: string;
210
+ readonly metadata: NornRunOutcomeMetadata & {
211
+ readonly summary: string;
212
+ };
213
+ };
214
+ export type NornStoppedRunResult = {
215
+ readonly status: "stopped";
216
+ readonly id: string;
217
+ readonly name: string;
218
+ readonly workspace: string;
219
+ readonly cwd: string;
220
+ readonly workflowId: string;
221
+ };
222
+ export type NornRunInterruption = {
223
+ readonly workflowId: string;
224
+ readonly params: unknown;
225
+ readonly description: string;
226
+ readonly fields?: readonly string[];
227
+ };
228
+ export type NornInterruptedRunResult = {
229
+ readonly status: "interrupted";
230
+ readonly id: string;
231
+ readonly name: string;
232
+ readonly workspace: string;
233
+ readonly cwd: string;
234
+ readonly workflowId: string;
235
+ readonly interruption: NornRunInterruption;
236
+ };
237
+ export type NornRunResult = NornStartedRunResult | NornCompletedRunResult | NornFailedRunResult | NornStoppedRunResult | NornInterruptedRunResult;
238
+ export type NornRunStatus = "running" | "interrupted" | "stopped" | "pendingResume" | "completed" | "failed";
239
+ export type NornRunHealth = "healthy" | "unhealthy";
240
+ export type NornRunOutcomeInfo = {
241
+ readonly workflowId: string;
242
+ readonly completedAt: string;
243
+ readonly status: "completed" | "failed";
244
+ readonly metadata?: NornRunOutcomeMetadata;
245
+ };
246
+ export type NornRunFailureInfo = {
247
+ readonly workflowId: string;
248
+ readonly error: string;
249
+ readonly metadata?: NornRunOutcomeMetadata;
250
+ readonly failedAt: string;
251
+ };
252
+ export type NornRunInfo = {
253
+ readonly version: number;
254
+ readonly id: string;
255
+ readonly name: string;
256
+ readonly path: string;
257
+ readonly entrypointWorkflowId: string;
258
+ readonly currentWorkflowId?: string;
259
+ readonly status: NornRunStatus;
260
+ readonly health: NornRunHealth;
261
+ readonly interruption?: NornRunInterruption;
262
+ readonly outcome?: NornRunOutcomeInfo;
263
+ readonly failed?: NornRunFailureInfo;
264
+ readonly startedAt: string;
265
+ readonly updatedAt: string;
266
+ };
267
+ export type DeletedNornRunInfo = {
268
+ readonly id: string;
269
+ readonly name: string;
270
+ readonly path: string;
271
+ };
272
+ export type NornRunCheckpoint = {
273
+ readonly id: string;
274
+ readonly path: string;
275
+ readonly index: number;
276
+ readonly message: string;
277
+ readonly createdAt: string;
278
+ };
279
+ export type NornWorkflowStateReader = {
280
+ get<T>(state: NornWorkflowStateDefinition<T>): Promise<T>;
281
+ getOptional<T>(state: NornWorkflowStateDefinition<T>): Promise<T | undefined>;
282
+ };
283
+ export type NornWorkflowState = NornWorkflowStateReader & {
284
+ set<T>(state: NornWorkflowStateDefinition<T>, value: T): Promise<void>;
285
+ };
286
+ export type NornWorkflowPluginContext = {
287
+ readonly cwd: string;
288
+ readonly state: NornWorkflowState;
289
+ };
290
+ export type NornWorkflowPluginImplementation<TManifest extends NornAnyWorkflowPluginManifest> = {
291
+ readonly workflows: {
292
+ readonly [Key in keyof TManifest["workflows"]]: NornWorkflowImplementation<TManifest["workflows"][Key], NornWorkflowPluginConfig<TManifest>>;
293
+ };
294
+ };
295
+ export type NornWorkflowPluginImplementationFactory<TManifest extends NornAnyWorkflowPluginManifest> = (context: NornWorkflowPluginContext) => NornWorkflowPluginImplementation<TManifest>;
296
+ export type NornWorkflowPluginImplementationInput<TManifest extends NornAnyWorkflowPluginManifest> = NornWorkflowPluginImplementation<TManifest> | NornWorkflowPluginImplementationFactory<TManifest>;
297
+ export type NornWorkflowPlugin<TManifest extends NornAnyWorkflowPluginManifest = NornAnyWorkflowPluginManifest> = {
298
+ readonly manifest: TManifest;
299
+ readonly implementation: NornWorkflowPluginImplementationInput<TManifest>;
300
+ };
301
+ export declare function definePlugin<TManifest extends NornAnyWorkflowPluginManifest>(manifest: TManifest, implementation: NornWorkflowPluginImplementationInput<TManifest>): NornWorkflowPlugin<TManifest>;
302
+ export type NornCommandRunInput = {
303
+ readonly label: string;
304
+ readonly command: string | readonly [string, ...string[]];
305
+ readonly cwd?: string;
306
+ readonly env?: Record<string, string>;
307
+ readonly timeoutMs?: number;
308
+ };
309
+ export declare const artifactRefSchema: z.ZodObject<{
310
+ path: z.ZodString;
311
+ }, z.core.$strip>;
312
+ export type NornArtifactRef = z.output<typeof artifactRefSchema>;
313
+ declare const emptyWorkflowRefParamsSchema: z.ZodObject<{}, z.core.$strip>;
314
+ export declare function workflowRefSchema(): z.ZodType<NornWorkflowRefOutput<typeof emptyWorkflowRefParamsSchema>, NornWorkflowRefInput<typeof emptyWorkflowRefParamsSchema>>;
315
+ export declare function workflowRefSchema(options: {
316
+ readonly params?: undefined;
317
+ }): z.ZodType<NornWorkflowRefOutput<typeof emptyWorkflowRefParamsSchema>, NornWorkflowRefInput<typeof emptyWorkflowRefParamsSchema>>;
318
+ export declare function workflowRefSchema<ParamsSchema extends z.ZodType>(options: NornWorkflowRefSchemaOptions<ParamsSchema>): z.ZodType<NornWorkflowRefOutput<ParamsSchema>, NornWorkflowRefInput<ParamsSchema>>;
319
+ export type NornLogRef = {
320
+ readonly id: string;
321
+ };
322
+ export type NornCommandRunResult = {
323
+ readonly label: string;
324
+ readonly command: string | readonly [string, ...string[]];
325
+ readonly cwd: string;
326
+ readonly exitCode: number | null;
327
+ readonly stdoutTail: string;
328
+ readonly stderrTail: string;
329
+ readonly killed: boolean;
330
+ readonly stdoutLog: NornLogRef;
331
+ readonly stderrLog: NornLogRef;
332
+ };
333
+ export type NornAgentBeforeSessionStartContext = {
334
+ readonly events: EventBus;
335
+ };
336
+ export type NornAgentCreateSessionInput = {
337
+ readonly resourceAdapters?: readonly import("./agent-resource-adapter.ts").NornAgentResourceAdapter[];
338
+ readonly label: string;
339
+ readonly cwd?: string;
340
+ readonly tools?: string[];
341
+ readonly beforeSessionStart?: (context: NornAgentBeforeSessionStartContext) => MaybePromise<void>;
342
+ readonly model?: CreateAgentSessionOptions["model"];
343
+ readonly thinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
344
+ readonly systemPrompt?: string;
345
+ readonly appendSystemPrompt?: readonly string[];
346
+ };
347
+ export type NornAgentPromptInput<ResponseSchema extends z.ZodType> = {
348
+ readonly prompt: string;
349
+ readonly response: ResponseSchema;
350
+ readonly maxAttempts?: number;
351
+ readonly options?: PromptOptions;
352
+ };
353
+ export type NornAgentSinglePromptInput<ResponseSchema extends z.ZodType> = NornAgentCreateSessionInput & NornAgentPromptInput<ResponseSchema>;
354
+ export type NornAgentSessionEvents = EventBus;
355
+ export type NornAgentUsageCost = {
356
+ readonly input: number;
357
+ readonly output: number;
358
+ readonly cacheRead: number;
359
+ readonly cacheWrite: number;
360
+ readonly total: number;
361
+ };
362
+ export type NornAgentUsage = {
363
+ readonly input: number;
364
+ readonly output: number;
365
+ readonly cacheRead: number;
366
+ readonly cacheWrite: number;
367
+ readonly reasoning?: number;
368
+ readonly totalTokens: number;
369
+ readonly cost: NornAgentUsageCost;
370
+ };
371
+ export type NornAgentMetrics = {
372
+ readonly index: number;
373
+ readonly label: string;
374
+ readonly status: "running" | "completed" | "failed";
375
+ readonly startedAt: string;
376
+ readonly endedAt?: string;
377
+ readonly wallMs: number;
378
+ readonly attempts?: number;
379
+ readonly usage: NornAgentUsage;
380
+ };
381
+ export type NornCommandMetrics = {
382
+ readonly index: number;
383
+ readonly label: string;
384
+ readonly status: "running" | "completed" | "failed";
385
+ readonly startedAt: string;
386
+ readonly endedAt?: string;
387
+ readonly wallMs: number;
388
+ readonly exitCode?: number | null;
389
+ readonly killed?: boolean;
390
+ };
391
+ export type NornWorkflowMetrics = {
392
+ readonly index: number;
393
+ readonly workflowId: string;
394
+ readonly status: "running" | "completed" | "failed" | "transitioned";
395
+ readonly startedAt: string;
396
+ readonly endedAt?: string;
397
+ readonly wallMs: number;
398
+ readonly ownMs: number;
399
+ readonly agentsMs: number;
400
+ readonly commandsMs: number;
401
+ readonly agentUsage: NornAgentUsage;
402
+ readonly agents: readonly NornAgentMetrics[];
403
+ readonly commands: readonly NornCommandMetrics[];
404
+ };
405
+ export type NornRunMetrics = {
406
+ readonly status: NornRunStatus;
407
+ readonly startedAt: string;
408
+ readonly endedAt?: string;
409
+ readonly wallMs: number;
410
+ readonly activeMs: number;
411
+ readonly gateWaitMs: number;
412
+ readonly workflowsMs: number;
413
+ readonly workflowOwnMs: number;
414
+ readonly agentsMs: number;
415
+ readonly commandsMs: number;
416
+ readonly agentUsage: NornAgentUsage;
417
+ readonly workflows: readonly NornWorkflowMetrics[];
418
+ };
419
+ export type NornAgentRunRawAttempt = {
420
+ readonly attempt: number;
421
+ readonly text: string;
422
+ readonly messages: unknown[];
423
+ readonly responseToolCalled: boolean;
424
+ readonly usage: NornAgentUsage;
425
+ readonly toolResponse?: unknown;
426
+ readonly sessionFile?: string;
427
+ readonly error?: string;
428
+ };
429
+ export type NornAgentRunResult<ResponseSchema extends z.ZodType> = {
430
+ readonly label: string;
431
+ readonly cwd: string;
432
+ readonly response: z.output<ResponseSchema>;
433
+ readonly usage: NornAgentUsage;
434
+ readonly raw: {
435
+ readonly text: string;
436
+ readonly messages: unknown[];
437
+ readonly responseToolCalled: boolean;
438
+ readonly usage: NornAgentUsage;
439
+ readonly toolResponse?: unknown;
440
+ readonly sessionFile?: string;
441
+ readonly attempts: readonly NornAgentRunRawAttempt[];
442
+ };
443
+ };
444
+ export type NornAgentSession = {
445
+ readonly label: string;
446
+ readonly cwd: string;
447
+ readonly events: NornAgentSessionEvents;
448
+ prompt<ResponseSchema extends z.ZodType>(input: NornAgentPromptInput<ResponseSchema>): Promise<z.output<ResponseSchema>>;
449
+ dispose(): Promise<void>;
450
+ };
451
+ export type NornRun = NornRunBase;
452
+ export type NornProjectRun = NornRunBase & {
453
+ projectRoot: string;
454
+ projectPath(relativePath: string): string;
455
+ };
456
+ type NornRunBase = {
457
+ id: string;
458
+ workspace: string;
459
+ cwd: string;
460
+ path(relativePath: string): string;
461
+ next<TWorkflow extends NornWorkflowTarget<any>>(workflow: TWorkflow, params: NornWorkflowTargetParamsInput<TWorkflow>): NornRunNext;
462
+ complete(metadata?: NornRunOutcomeMetadata): NornRunComplete;
463
+ fail(metadata: NornRunOutcomeMetadata & {
464
+ readonly summary: string;
465
+ }): NornRunFail;
466
+ resources: import("./resources.ts").NornResources;
467
+ state: NornWorkflowState;
468
+ artifacts: {
469
+ write(path: string, content: string): Promise<NornArtifactRef>;
470
+ read(ref: NornArtifactRef): Promise<string>;
471
+ };
472
+ logs: {
473
+ read(log: NornLogRef): Promise<string>;
474
+ };
475
+ commands: {
476
+ run(input: NornCommandRunInput): Promise<NornCommandRunResult>;
477
+ };
478
+ agents: {
479
+ createSession(input: NornAgentCreateSessionInput): Promise<NornAgentSession>;
480
+ prompt<ResponseSchema extends z.ZodType>(input: NornAgentSinglePromptInput<ResponseSchema>): Promise<z.output<ResponseSchema>>;
481
+ };
482
+ };
483
+ export type NornWorkflowPluginInfo = {
484
+ readonly id: string;
485
+ readonly path?: string;
486
+ readonly configPath?: string;
487
+ };
488
+ export type NornJsonSchema = Record<string, unknown>;
489
+ export type NornWorkflowGateInfo = {
490
+ readonly enabled: true;
491
+ readonly fields?: readonly string[];
492
+ };
493
+ export type NornRegisteredWorkflowInfo = {
494
+ readonly id: string;
495
+ readonly instructions?: string;
496
+ readonly isEntrypoint: boolean;
497
+ readonly isolation: NornWorkflowIsolation;
498
+ readonly plugin?: NornWorkflowPluginInfo;
499
+ };
500
+ export type NornInspectedWorkflowInfo = NornRegisteredWorkflowInfo & {
501
+ readonly paramsSchema: NornJsonSchema;
502
+ readonly gate: NornWorkflowGateInfo | null;
503
+ };
504
+ export type NornPluginDiagnostic = {
505
+ readonly configPath: string;
506
+ readonly pluginPath: string;
507
+ readonly pluginId: string | null;
508
+ readonly workflowId: string | null;
509
+ readonly stage: "import" | "declaration" | "config" | "implementation" | "duplicate" | "schema";
510
+ readonly message: string;
511
+ readonly issues: readonly {
512
+ readonly path: readonly (string | number)[];
513
+ readonly code: string;
514
+ readonly message: string;
515
+ }[];
516
+ };
517
+ export type NornProjectLoadStatus = {
518
+ readonly isComplete: boolean;
519
+ readonly diagnostics: readonly NornPluginDiagnostic[];
520
+ };
521
+ export type NornWorkflowCatalogInfo = NornProjectLoadStatus & {
522
+ readonly workflows: readonly NornRegisteredWorkflowInfo[];
523
+ };
524
+ export type NornWorkflowInspection = NornProjectLoadStatus & {
525
+ readonly workflow: NornInspectedWorkflowInfo | null;
526
+ };
527
+ export type NornProjectInspection = NornProjectLoadStatus & {
528
+ readonly project: NornProjectInfo;
529
+ };
530
+ export type NornProjectPluginInfo = NornWorkflowPluginInfo & {
531
+ readonly configSchema: NornJsonSchema | null;
532
+ readonly config: unknown;
533
+ };
534
+ export type NornProjectInfo = {
535
+ readonly cwd: string;
536
+ readonly projectPath: string;
537
+ readonly projectRoot: string;
538
+ readonly configPath: string;
539
+ readonly configRoot: string;
540
+ readonly configFiles: readonly string[];
541
+ readonly plugins: readonly NornProjectPluginInfo[];
542
+ readonly seerMode: NornResolvedSeerModeConfig | null;
543
+ };
544
+ export declare function isWorkflowDeclaration(value: unknown): value is NornAnyWorkflowDeclaration;
545
+ export declare function isWorkflowPlugin(value: unknown): value is NornWorkflowPlugin;
546
+ export declare function isWorkflowPluginManifest(value: unknown): value is NornAnyWorkflowPluginManifest;
547
+ export declare function isWorkflowNext(value: unknown): value is NornRunNext;
548
+ export declare function isWorkflowComplete(value: unknown): value is NornRunComplete;
549
+ export declare function isWorkflowFail(value: unknown): value is NornRunFail;
550
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,120 @@
1
+ // src/api.ts
2
+ import { z } from "zod";
3
+ var WORKFLOW_DECLARATION_KIND = "norn.workflow";
4
+ function definePluginManifest(input) {
5
+ assertLocalDeclarationId(input.id, "plugin");
6
+ const declaredIds = /* @__PURE__ */ new Set();
7
+ const workflows = Object.fromEntries(
8
+ Object.entries(input.workflows).map(([key, workflow]) => [key, qualifyWorkflow(input.id, key, workflow, declaredIds)])
9
+ );
10
+ return {
11
+ id: input.id,
12
+ config: input.config,
13
+ workflows,
14
+ states: qualifyStateTree(input.id, input.states, [], declaredIds)
15
+ };
16
+ }
17
+ function definePlugin(manifest, implementation) {
18
+ return { manifest, implementation };
19
+ }
20
+ var artifactRefSchema = z.object({
21
+ path: z.string()
22
+ });
23
+ var emptyWorkflowRefParamsSchema = z.object({});
24
+ function workflowRefSchema(options) {
25
+ const contributedParams = options?.params ?? emptyWorkflowRefParamsSchema;
26
+ const targetSchema = z.union([
27
+ z.string().min(1),
28
+ z.object({ id: z.string().min(1) })
29
+ ]).transform((target) => typeof target === "string" ? target : target.id);
30
+ return z.union([
31
+ targetSchema,
32
+ z.object({ workflow: targetSchema, forwardParams: z.record(z.string(), z.unknown()) })
33
+ ]).transform((reference) => typeof reference === "string" ? { workflow: reference, forwardParams: {} } : reference).meta({
34
+ "x-norn-workflow-ref": {
35
+ get contributedParamsSchema() {
36
+ return z.toJSONSchema(contributedParams, { io: "input" });
37
+ }
38
+ }
39
+ });
40
+ }
41
+ function assertLocalDeclarationId(id, kind) {
42
+ if (id.length === 0) throw new Error(`Workflow ${kind} id must not be empty`);
43
+ if (id.includes(".")) throw new Error(`Workflow ${kind} id must not contain dots: ${id}`);
44
+ }
45
+ function resolveDeclarationId(pluginId, path, explicitId, kind, declaredIds) {
46
+ for (const segment of path) assertLocalDeclarationId(segment, kind);
47
+ const id = explicitId ?? [pluginId, ...path].join(".");
48
+ if (explicitId !== void 0) assertPluginQualifiedId(pluginId, explicitId, kind);
49
+ if (declaredIds.has(id)) throw new Error(`Duplicate Norn ${kind} id: ${id}`);
50
+ declaredIds.add(id);
51
+ return id;
52
+ }
53
+ function assertPluginQualifiedId(pluginId, id, kind) {
54
+ if (!id.startsWith(`${pluginId}.`)) throw new Error(`Explicit Norn ${kind} id must start with ${pluginId}.: ${id}`);
55
+ if (id.length === pluginId.length + 1) throw new Error(`Explicit Norn ${kind} id must not be empty after ${pluginId}.`);
56
+ }
57
+ function qualifyWorkflow(pluginId, key, workflow, declaredIds) {
58
+ const id = resolveDeclarationId(pluginId, [key], workflow.id, "workflow", declaredIds);
59
+ return { kind: WORKFLOW_DECLARATION_KIND, ...workflow, id, isolation: workflow.isolation ?? { mode: "runWorkspace" } };
60
+ }
61
+ function qualifyStateTree(pluginId, node, path, declaredIds) {
62
+ if (!node) return void 0;
63
+ if (isZodSchema(node)) return { id: resolveDeclarationId(pluginId, path, void 0, "state", declaredIds), schema: node };
64
+ if (isWorkflowStateDefinitionInput(node)) {
65
+ return { ...node, id: resolveDeclarationId(pluginId, path, node.id, "state", declaredIds) };
66
+ }
67
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, qualifyStateTree(pluginId, child, [...path, key], declaredIds)]));
68
+ }
69
+ function isWorkflowDeclaration(value) {
70
+ if (!value || typeof value !== "object") return false;
71
+ const candidate = value;
72
+ return candidate.kind === WORKFLOW_DECLARATION_KIND && typeof candidate.id === "string" && candidate.id.length > 0 && (candidate.instructions === void 0 || typeof candidate.instructions === "string") && typeof candidate.isEntrypoint === "boolean" && Boolean(candidate.params) && (candidate.isolation?.mode === "runWorkspace" || candidate.isolation?.mode === "project");
73
+ }
74
+ function isWorkflowStateDefinitionInput(value) {
75
+ if (!value || typeof value !== "object") return false;
76
+ const candidate = value;
77
+ return (candidate.id === void 0 || typeof candidate.id === "string") && isZodSchema(candidate.schema);
78
+ }
79
+ function isZodSchema(value) {
80
+ return Boolean(value && typeof value === "object" && typeof value.safeParse === "function");
81
+ }
82
+ function isWorkflowPlugin(value) {
83
+ if (!value || typeof value !== "object") return false;
84
+ const candidate = value;
85
+ return isWorkflowPluginManifest(candidate.manifest) && Boolean(candidate.implementation);
86
+ }
87
+ function isWorkflowPluginManifest(value) {
88
+ if (!value || typeof value !== "object") return false;
89
+ const candidate = value;
90
+ if (typeof candidate.id !== "string" || candidate.id.length === 0) return false;
91
+ if (!candidate.workflows || typeof candidate.workflows !== "object") return false;
92
+ return Object.values(candidate.workflows).every(isWorkflowDeclaration);
93
+ }
94
+ function isWorkflowNext(value) {
95
+ if (!value || typeof value !== "object") return false;
96
+ const candidate = value;
97
+ return candidate.type === "next" && typeof candidate.workflowId === "string" && candidate.workflowId.length > 0;
98
+ }
99
+ function isWorkflowComplete(value) {
100
+ if (!value || typeof value !== "object") return false;
101
+ const candidate = value;
102
+ return candidate.type === "complete";
103
+ }
104
+ function isWorkflowFail(value) {
105
+ if (!value || typeof value !== "object") return false;
106
+ const candidate = value;
107
+ return candidate.type === "fail" && typeof candidate.metadata?.summary === "string" && candidate.metadata.summary.length > 0;
108
+ }
109
+ export {
110
+ artifactRefSchema,
111
+ definePlugin,
112
+ definePluginManifest,
113
+ isWorkflowComplete,
114
+ isWorkflowDeclaration,
115
+ isWorkflowFail,
116
+ isWorkflowNext,
117
+ isWorkflowPlugin,
118
+ isWorkflowPluginManifest,
119
+ workflowRefSchema
120
+ };
@@ -0,0 +1,17 @@
1
+ export declare class NornFileCoordinator {
2
+ private readonly input;
3
+ private readonly lockRoot;
4
+ constructor(input: {
5
+ readonly lockRoot: string;
6
+ readonly waitTimeoutMs: number;
7
+ });
8
+ withExclusiveLock<T>(path: string, operation: (lockedPath: string) => Promise<T>): Promise<T>;
9
+ readText(path: string): Promise<string>;
10
+ writeText(path: string, content: string): Promise<void>;
11
+ private withCleanup;
12
+ private resolveTarget;
13
+ private acquire;
14
+ private reclaimDeadOwner;
15
+ private removeEmptyLock;
16
+ }
17
+ export declare function createRunFileCoordinator(runRoot: string): NornFileCoordinator;
package/dist/files.js ADDED
@@ -0,0 +1,161 @@
1
+ // src/files.ts
2
+ import { createHash, randomUUID as randomUUID2 } from "node:crypto";
3
+ import { lstat, mkdir as mkdir2, mkdtemp, readFile, readdir, realpath, rename as rename2, rm as rm2, rmdir, unlink, writeFile as writeFile2 } from "node:fs/promises";
4
+ import { hostname } from "node:os";
5
+ import { basename, dirname as dirname2, join, resolve } from "node:path";
6
+ import { setTimeout as delay } from "node:timers/promises";
7
+ import { z } from "zod";
8
+
9
+ // ../core/src/errors.ts
10
+ function isNodeError(error) {
11
+ return error instanceof Error && "code" in error;
12
+ }
13
+
14
+ // ../core/src/atomic-files.ts
15
+ import { randomUUID } from "node:crypto";
16
+ import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
17
+ import { dirname } from "node:path";
18
+ async function writeTextAtomically(path, content) {
19
+ await mkdir(dirname(path), { recursive: true });
20
+ let existingMode;
21
+ try {
22
+ existingMode = (await stat(path)).mode & 511;
23
+ } catch (error) {
24
+ if (!isNodeError(error) || error.code !== "ENOENT") throw error;
25
+ }
26
+ const tmpPath = `${path}.${randomUUID()}.tmp`;
27
+ try {
28
+ await writeFile(tmpPath, content, { encoding: "utf8", mode: existingMode });
29
+ if (existingMode !== void 0) await chmod(tmpPath, existingMode);
30
+ await rename(tmpPath, path);
31
+ } catch (error) {
32
+ await rm(tmpPath, { force: true }).catch(() => void 0);
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ // src/files.ts
38
+ var ownerSchema = z.strictObject({
39
+ token: z.uuid(),
40
+ pid: z.number().int().positive(),
41
+ host: z.string(),
42
+ target: z.string()
43
+ });
44
+ var NornFileCoordinator = class {
45
+ constructor(input) {
46
+ this.input = input;
47
+ if (!Number.isSafeInteger(input.waitTimeoutMs) || input.waitTimeoutMs <= 0) throw new Error("Lock wait timeout must be a positive integer");
48
+ this.lockRoot = resolve(input.lockRoot);
49
+ }
50
+ input;
51
+ lockRoot;
52
+ async withExclusiveLock(path, operation) {
53
+ const target = await this.resolveTarget(path);
54
+ await mkdir2(this.lockRoot, { recursive: true, mode: 448 });
55
+ const key = createHash("sha256").update(target).digest("hex");
56
+ const lockPath = join(this.lockRoot, `${key}.lock`);
57
+ const stagingPath = await mkdtemp(join(this.lockRoot, `${key}.pending-`));
58
+ const token = randomUUID2();
59
+ const marker = `${token}.json`;
60
+ return this.withCleanup({
61
+ operation: async () => {
62
+ await writeFile2(join(stagingPath, marker), JSON.stringify({ token, pid: process.pid, host: hostname(), target }), { mode: 384 });
63
+ await this.acquire({ stagingPath, lockPath, target });
64
+ return this.withCleanup({
65
+ operation: () => operation(target),
66
+ cleanup: async () => {
67
+ await unlink(join(lockPath, marker));
68
+ await this.removeEmptyLock(lockPath);
69
+ }
70
+ });
71
+ },
72
+ cleanup: () => rm2(stagingPath, { recursive: true, force: true })
73
+ });
74
+ }
75
+ async readText(path) {
76
+ return this.withExclusiveLock(path, (lockedPath) => readFile(lockedPath, "utf8"));
77
+ }
78
+ async writeText(path, content) {
79
+ await mkdir2(dirname2(path), { recursive: true });
80
+ await this.withExclusiveLock(path, (lockedPath) => writeTextAtomically(lockedPath, content));
81
+ }
82
+ async withCleanup(input) {
83
+ let result;
84
+ try {
85
+ result = await input.operation();
86
+ } catch (error) {
87
+ try {
88
+ await input.cleanup();
89
+ } catch (cleanupError) {
90
+ throw new AggregateError([error, cleanupError], "File operation and lock cleanup failed");
91
+ }
92
+ throw error;
93
+ }
94
+ await input.cleanup();
95
+ return result;
96
+ }
97
+ async resolveTarget(path) {
98
+ try {
99
+ return await realpath(path);
100
+ } catch (error) {
101
+ if (!isNodeError(error) || error.code !== "ENOENT") throw error;
102
+ const entry = await lstat(path).catch((error2) => {
103
+ if (isNodeError(error2) && error2.code === "ENOENT") return void 0;
104
+ throw error2;
105
+ });
106
+ if (entry?.isSymbolicLink()) throw new Error(`Cannot lock a dangling symbolic link: ${path}`);
107
+ return join(await realpath(dirname2(path)), basename(path));
108
+ }
109
+ }
110
+ async acquire(input) {
111
+ const deadline = Date.now() + this.input.waitTimeoutMs;
112
+ while (true) {
113
+ try {
114
+ await rename2(input.stagingPath, input.lockPath);
115
+ return;
116
+ } catch (error) {
117
+ if (!isNodeError(error) || !["ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
118
+ }
119
+ await this.reclaimDeadOwner(input);
120
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for file lock: ${input.target}`);
121
+ await delay(10);
122
+ }
123
+ }
124
+ async reclaimDeadOwner(input) {
125
+ try {
126
+ const entries = await readdir(input.lockPath);
127
+ if (entries.length === 0) return;
128
+ if (entries.length !== 1) throw new Error(`Invalid file lock ownership: ${input.lockPath}`);
129
+ const marker = entries[0];
130
+ const owner = ownerSchema.parse(JSON.parse(await readFile(join(input.lockPath, marker), "utf8")));
131
+ if (marker !== `${owner.token}.json` || owner.target !== input.target || owner.host !== hostname()) {
132
+ throw new Error(`Incompatible file lock ownership: ${input.lockPath}`);
133
+ }
134
+ try {
135
+ process.kill(owner.pid, 0);
136
+ return;
137
+ } catch (error) {
138
+ if (isNodeError(error) && error.code === "EPERM") return;
139
+ if (!isNodeError(error) || error.code !== "ESRCH") throw error;
140
+ }
141
+ await unlink(join(input.lockPath, marker));
142
+ await this.removeEmptyLock(input.lockPath);
143
+ } catch (error) {
144
+ if (!isNodeError(error) || error.code !== "ENOENT") throw error;
145
+ }
146
+ }
147
+ async removeEmptyLock(path) {
148
+ try {
149
+ await rmdir(path);
150
+ } catch (error) {
151
+ if (!isNodeError(error) || !["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
152
+ }
153
+ }
154
+ };
155
+ function createRunFileCoordinator(runRoot) {
156
+ return new NornFileCoordinator({ lockRoot: join(runRoot, "locks"), waitTimeoutMs: 3e4 });
157
+ }
158
+ export {
159
+ NornFileCoordinator,
160
+ createRunFileCoordinator
161
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./api.ts";
2
+ export { NornFileCoordinator } from "./files.ts";
3
+ export * from "./resources.ts";
4
+ export * from "./agent-resource-adapter.ts";
5
+ export * from "./state-adapter.ts";
6
+ export * from "./seer/index.ts";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // src/index.ts
2
+ export * from "./api.js";
3
+ import { NornFileCoordinator } from "./files.js";
4
+ export * from "./resources.js";
5
+ export * from "./agent-resource-adapter.js";
6
+ export * from "./state-adapter.js";
7
+ export * from "./seer/index.js";
8
+ export {
9
+ NornFileCoordinator
10
+ };
@@ -0,0 +1,17 @@
1
+ import type { z } from "zod";
2
+ import type { NornFileCoordinator } from "./files.ts";
3
+ export type NornResourceContext = {
4
+ readonly mode: "create" | "open";
5
+ readonly directory: string;
6
+ readonly files: NornFileCoordinator;
7
+ };
8
+ export type NornResourceDefinition<T> = {
9
+ readonly name: string;
10
+ readonly kind: string;
11
+ readonly configuration: z.infer<ReturnType<typeof z.json>>;
12
+ initialize(context: NornResourceContext): Promise<T>;
13
+ };
14
+ export type NornResources = {
15
+ readonly files: NornFileCoordinator;
16
+ ensure<T>(definition: NornResourceDefinition<T>): Promise<T>;
17
+ };
File without changes
@@ -0,0 +1,6 @@
1
+ import type { NornAnyWorkflowDeclaration } from "./api.ts";
2
+ export declare function assertWorkflowMetadata(workflow: NornAnyWorkflowDeclaration): void;
3
+ export declare function unwrapSchema(schema: unknown): unknown;
4
+ export declare function schemaShape(schema: unknown): Record<string, unknown>;
5
+ export declare function schemaType(schema: unknown): string | undefined;
6
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
package/dist/schema.js ADDED
@@ -0,0 +1,43 @@
1
+ // src/schema.ts
2
+ function assertWorkflowMetadata(workflow) {
3
+ if (workflow.instructions !== void 0 && (typeof workflow.instructions !== "string" || workflow.instructions.trim().length === 0)) {
4
+ throw new Error(`Workflow instructions must be a nonempty string: ${workflow.id}`);
5
+ }
6
+ if (workflow.isEntrypoint && workflow.instructions === void 0) {
7
+ throw new Error(`Entrypoint workflow requires instructions: ${workflow.id}`);
8
+ }
9
+ }
10
+ function unwrapSchema(schema) {
11
+ let current = schema;
12
+ while (true) {
13
+ const def = schemaDef(current);
14
+ if (["optional", "nullable", "default", "catch", "readonly", "prefault"].includes(def.type ?? "") && def.innerType) {
15
+ current = def.innerType;
16
+ continue;
17
+ }
18
+ return current;
19
+ }
20
+ }
21
+ function schemaShape(schema) {
22
+ const shape = schemaDef(unwrapSchema(schema)).shape;
23
+ if (!shape) return {};
24
+ return typeof shape === "function" ? shape() : shape;
25
+ }
26
+ function schemaType(schema) {
27
+ return schemaDef(unwrapSchema(schema)).type;
28
+ }
29
+ function isPlainObject(value) {
30
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+ function schemaDef(schema) {
33
+ if (!schema || typeof schema !== "object") return {};
34
+ const candidate = schema;
35
+ return candidate._def ?? candidate.def ?? {};
36
+ }
37
+ export {
38
+ assertWorkflowMetadata,
39
+ isPlainObject,
40
+ schemaShape,
41
+ schemaType,
42
+ unwrapSchema
43
+ };
@@ -0,0 +1,17 @@
1
+ export type NornSeerModeConfig = {
2
+ readonly writableRoots: readonly string[];
3
+ };
4
+ export type NornResolvedSeerModeConfig = {
5
+ readonly configPath: string;
6
+ readonly projectRoot: string;
7
+ readonly writableRoots: readonly string[];
8
+ };
9
+ type ResolveSeerModeConfigInput = {
10
+ readonly configPath: string;
11
+ readonly configRoot: string;
12
+ readonly seerMode: NornSeerModeConfig | undefined;
13
+ };
14
+ export declare function resolveSeerModeConfig(input: ResolveSeerModeConfigInput): NornResolvedSeerModeConfig | undefined;
15
+ export declare function assertSeerModeWritablePath(seerMode: NornResolvedSeerModeConfig, cwd: string, path: string): string;
16
+ export declare function isSeerModeWritablePath(seerMode: NornResolvedSeerModeConfig, cwd: string, path: string): boolean;
17
+ export {};
@@ -0,0 +1,45 @@
1
+ // src/seer/config.ts
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+ function resolveSeerModeConfig(input) {
4
+ if (!input.seerMode) return void 0;
5
+ if (input.seerMode.writableRoots.length === 0) throw new Error("Norn seerMode.writableRoots must not be empty");
6
+ const projectRoot = resolve(input.configRoot);
7
+ const writableRoots = Array.from(new Set(input.seerMode.writableRoots.map((path) => resolveWritableRoot(projectRoot, path))));
8
+ return { configPath: input.configPath, projectRoot, writableRoots };
9
+ }
10
+ function assertSeerModeWritablePath(seerMode, cwd, path) {
11
+ const resolvedPath = resolveSeerModePath(seerMode, cwd, path);
12
+ if (!isSeerModeWritableResolvedPath(seerMode, resolvedPath)) {
13
+ throw new Error(`Path is outside Norn seerMode writable roots: ${path}`);
14
+ }
15
+ return resolvedPath;
16
+ }
17
+ function isSeerModeWritablePath(seerMode, cwd, path) {
18
+ return isSeerModeWritableResolvedPath(seerMode, resolveSeerModePath(seerMode, cwd, path));
19
+ }
20
+ function resolveWritableRoot(projectRoot, path) {
21
+ if (path.length === 0) throw new Error("Norn seerMode writable root must not be empty");
22
+ const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(projectRoot, path);
23
+ if (!isInsideOrEqual(projectRoot, resolvedPath)) throw new Error(`Norn seerMode writable root escapes project root: ${path}`);
24
+ return resolvedPath;
25
+ }
26
+ function resolveSeerModePath(seerMode, cwd, path) {
27
+ if (path.length === 0) throw new Error("Norn seerMode path must not be empty");
28
+ const resolvedCwd = resolve(cwd);
29
+ if (!isInsideOrEqual(seerMode.projectRoot, resolvedCwd)) throw new Error(`Norn seerMode cwd escapes project root: ${cwd}`);
30
+ const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedCwd, path);
31
+ if (!isInsideOrEqual(seerMode.projectRoot, resolvedPath)) return resolvedPath;
32
+ return resolvedPath;
33
+ }
34
+ function isSeerModeWritableResolvedPath(seerMode, path) {
35
+ return seerMode.writableRoots.some((root) => isInsideOrEqual(root, path));
36
+ }
37
+ function isInsideOrEqual(root, path) {
38
+ const pathFromRoot = relative(root, path);
39
+ return pathFromRoot === "" || !pathFromRoot.startsWith(`..${sep}`) && pathFromRoot !== ".." && !isAbsolute(pathFromRoot);
40
+ }
41
+ export {
42
+ assertSeerModeWritablePath,
43
+ isSeerModeWritablePath,
44
+ resolveSeerModeConfig
45
+ };
@@ -0,0 +1 @@
1
+ export { assertSeerModeWritablePath, isSeerModeWritablePath, resolveSeerModeConfig, type NornResolvedSeerModeConfig, type NornSeerModeConfig, } from "./config.ts";
@@ -0,0 +1,11 @@
1
+ // src/seer/index.ts
2
+ import {
3
+ assertSeerModeWritablePath,
4
+ isSeerModeWritablePath,
5
+ resolveSeerModeConfig
6
+ } from "./config.js";
7
+ export {
8
+ assertSeerModeWritablePath,
9
+ isSeerModeWritablePath,
10
+ resolveSeerModeConfig
11
+ };
@@ -0,0 +1,10 @@
1
+ import type { NornWorkflowState, NornWorkflowStateDefinition } from "./api.ts";
2
+ import type { NornAgentResourceAdapter } from "./agent-resource-adapter.ts";
3
+ export type NornStateFieldAccess = {
4
+ readonly field: NornWorkflowStateDefinition;
5
+ readonly access: "read" | "write" | "read-write";
6
+ };
7
+ export declare function StateAdapter(input: {
8
+ readonly state: NornWorkflowState;
9
+ readonly fields: readonly NornStateFieldAccess[];
10
+ }): NornAgentResourceAdapter;
@@ -0,0 +1,70 @@
1
+ // src/state-adapter.ts
2
+ import { createHash } from "node:crypto";
3
+ import { defineTool } from "@earendil-works/pi-coding-agent";
4
+ import { Type } from "typebox";
5
+ import { z } from "zod";
6
+ var pageParameters = {
7
+ offset: Type.Integer({ minimum: 0, description: "Zero-based UTF-16 offset into the serialized JSON. Start at 0." }),
8
+ limit: Type.Integer({ minimum: 1, maximum: 1e4 })
9
+ };
10
+ function StateAdapter(input) {
11
+ const fields = new Map(input.fields.map((grant) => [grant.field.id, grant]));
12
+ if (fields.size !== input.fields.length || fields.size === 0) throw new Error("State attachment requires unique, explicitly selected fields");
13
+ const selectField = (key, access) => {
14
+ const grant = fields.get(key);
15
+ if (!grant || grant.access !== access && grant.access !== "read-write") throw new Error(`State ${access} is not attached: ${key}`);
16
+ return grant.field;
17
+ };
18
+ return {
19
+ name: "norn.state",
20
+ async bind() {
21
+ return {
22
+ tools: [
23
+ defineTool({
24
+ name: "norn_state_list",
25
+ label: "Attached workflow state",
26
+ description: "List only attached workflow-state field IDs, permissions and value schemas. JSON is paginated; use nextOffset until null.",
27
+ parameters: Type.Object(pageParameters),
28
+ async execute(_id, params) {
29
+ return serializePage({ value: [...fields.values()].map(({ field, access }) => ({ id: field.id, access, schema: z.toJSONSchema(field.schema, { io: "input" }) })), ...params });
30
+ }
31
+ }),
32
+ defineTool({
33
+ name: "norn_state_get",
34
+ label: "Read workflow state",
35
+ description: "Read a selected workflow-state field. Unset fields return isSet:false. JSON is paginated; concurrent writes can change later pages, so compare revision before combining pages.",
36
+ parameters: Type.Object({ key: Type.String(), ...pageParameters }),
37
+ async execute(_id, params) {
38
+ const value = await input.state.getOptional(selectField(params.key, "read"));
39
+ return serializePage({ value: value === void 0 ? { isSet: false } : { isSet: true, value }, ...params });
40
+ }
41
+ }),
42
+ defineTool({
43
+ name: "norn_state_set",
44
+ label: "Write workflow state",
45
+ description: "Set an explicitly writable workflow-state field. Validate the value against its schema from norn_state_list. A get followed by set is not a transaction.",
46
+ parameters: Type.Object({ key: Type.String(), value: Type.Unknown() }),
47
+ async execute(_id, params, signal) {
48
+ signal?.throwIfAborted();
49
+ const field = selectField(params.key, "write");
50
+ await input.state.set(field, params.value);
51
+ return { content: [{ type: "text", text: "Workflow state saved." }], details: {} };
52
+ }
53
+ })
54
+ ],
55
+ async dispose() {
56
+ }
57
+ };
58
+ }
59
+ };
60
+ }
61
+ function serializePage(input) {
62
+ const serialized = JSON.stringify(input.value);
63
+ if (!Number.isInteger(input.offset) || input.offset < 0 || !Number.isInteger(input.limit) || input.limit < 1 || input.limit > 1e4) throw new Error("Invalid state output page");
64
+ const end = Math.min(serialized.length, input.offset + input.limit);
65
+ const details = { text: serialized.slice(input.offset, end), nextOffset: end < serialized.length ? end : null, revision: createHash("sha256").update(serialized).digest("hex") };
66
+ return { content: [{ type: "text", text: JSON.stringify(details) }], details };
67
+ }
68
+ export {
69
+ StateAdapter
70
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@vimhead.dev/norn",
3
+ "version": "0.1.0-tip.0",
4
+ "description": "TypeScript SDK for building agent-driven and code-driven Norn workflows.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/vimhead/norn.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "homepage": "https://github.com/vimhead/norn#readme",
13
+ "engines": {
14
+ "node": ">=22.19.0"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "tag": "tip"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./files": {
30
+ "types": "./dist/files.d.ts",
31
+ "import": "./dist/files.js"
32
+ },
33
+ "./schema": {
34
+ "types": "./dist/schema.d.ts",
35
+ "import": "./dist/schema.js"
36
+ },
37
+ "./seer": {
38
+ "types": "./dist/seer/index.d.ts",
39
+ "import": "./dist/seer/index.js"
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "@vimhead.dev/norn-core": "0.0.0"
44
+ },
45
+ "dependencies": {
46
+ "@earendil-works/pi-coding-agent": "0.85.1",
47
+ "typebox": "^1.3.17",
48
+ "zod": "^4.4.3"
49
+ },
50
+ "scripts": {
51
+ "build": "node ../../scripts/build-package.ts"
52
+ }
53
+ }