@noetic-tools/types 3.2.0 → 3.2.2

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.
@@ -2,8 +2,8 @@
2
2
  * Zod schemas for JSON-serialisable workflow definitions.
3
3
  *
4
4
  * A `WorkflowDocument` is a portable, JSON-safe representation of a noetic
5
- * step tree. It covers every step kind. The `run` node carries its body as a
6
- * code STRING (not an in-process closure) dispatched through a subprocess
5
+ * step tree. It covers every step kind. The `runCode` node carries its body as
6
+ * a code STRING (not an in-process closure) dispatched through a subprocess
7
7
  * adapter, keeping the document JSON-safe. The hydrator in
8
8
  * `@noetic-tools/core` (`builders/workflow-hydrator.ts`) converts a validated
9
9
  * document back into live `Step` objects via the existing builders.
@@ -21,11 +21,14 @@ const MaxCostPredicateSchema = z.object({
21
21
  });
22
22
  const MaxDurationPredicateSchema = z.object({
23
23
  kind: z.literal('maxDuration'),
24
- ms: z.number().positive(),
24
+ duration: z.number().positive(),
25
25
  });
26
26
  const NoToolCallsPredicateSchema = z.object({
27
27
  kind: z.literal('noToolCalls'),
28
28
  });
29
+ const NeverPredicateSchema = z.object({
30
+ kind: z.literal('never'),
31
+ });
29
32
  const OutputContainsPredicateSchema = z.object({
30
33
  kind: z.literal('outputContains'),
31
34
  marker: z.string().min(1),
@@ -54,6 +57,7 @@ export const UntilPredicateSchema = z
54
57
  MaxCostPredicateSchema,
55
58
  MaxDurationPredicateSchema,
56
59
  NoToolCallsPredicateSchema,
60
+ NeverPredicateSchema,
57
61
  OutputContainsPredicateSchema,
58
62
  OutputEqualsPredicateSchema,
59
63
  ConvergedPredicateSchema,
@@ -72,7 +76,7 @@ export const MergeStrategy = {
72
76
  First: 'first',
73
77
  Concat: 'concat',
74
78
  };
75
- /** @public Zod schema for the named merge strategy used by `fork` nodes. */
79
+ /** @public Zod schema for the named merge strategy used by `inParallel` nodes. */
76
80
  export const MergeStrategySchema = z.enum([
77
81
  'last',
78
82
  'first',
@@ -89,7 +93,7 @@ const ModelParamsSchema = z.object({
89
93
  //#endregion
90
94
  //#region Tool Entries
91
95
  /**
92
- * A uniform tool entry on an `llm` node. Every entry is an object keyed by
96
+ * A uniform tool entry on a `callModel` node. Every entry is an object keyed by
93
97
  * `type`:
94
98
  * - A CLIENT tool is `{ type: "<registered-tool-name>" }`. The hydrator
95
99
  * resolves `type` against the tool registry; `parameters` (if present) is
@@ -102,13 +106,13 @@ const ModelParamsSchema = z.object({
102
106
  * Server vs client is decided by the `type` value (a reserved `openrouter:*`
103
107
  * server-tool literal vs an arbitrary tool name), not by the entry's shape.
104
108
  */
105
- const LlmToolEntrySchema = z.object({
109
+ const CallModelToolEntrySchema = z.object({
106
110
  type: z.string().min(1),
107
111
  parameters: z.record(z.string(), z.unknown()).optional(),
108
112
  });
109
113
  //#endregion
110
114
  //#region Retry Policy
111
- /** Retry policy for a `run` node, mirroring `RetryPolicy` in `@noetic-tools/types`. */
115
+ /** Retry policy for a `runCode` node, mirroring `RetryPolicy` in `@noetic-tools/types`. */
112
116
  const RetryPolicySchema = z.object({
113
117
  maxAttempts: z.number().int().positive(),
114
118
  backoff: z.enum([
@@ -120,30 +124,84 @@ const RetryPolicySchema = z.object({
120
124
  maxDelay: z.number().nonnegative().optional(),
121
125
  });
122
126
  //#endregion
123
- //#region SubHarness Settings
124
- const HarnessSettingsSchema = z.object({
125
- model: z.string().optional(),
126
- permissionMode: z
127
- .enum([
128
- 'default',
129
- 'plan',
130
- 'acceptEdits',
131
- 'bypassPermissions',
132
- ])
133
- .optional(),
134
- maxTurns: z.number().int().positive().optional(),
135
- allowedTools: z.array(z.string()).optional(),
136
- extra: z.record(z.string(), z.unknown()).optional(),
127
+ //#region ACP Agent Settings
128
+ /** ACP `ToolKind` — the agent's own classification of a tool call. */
129
+ const AcpToolKindSchema = z.enum([
130
+ 'read',
131
+ 'edit',
132
+ 'delete',
133
+ 'move',
134
+ 'search',
135
+ 'execute',
136
+ 'think',
137
+ 'fetch',
138
+ 'switch_mode',
139
+ 'other',
140
+ ]);
141
+ const AcpPermissionDecisionSchema = z.enum([
142
+ 'allow',
143
+ 'deny',
144
+ 'cancel',
145
+ ]);
146
+ /**
147
+ * A permission rule. Both fields are optional matchers; an omitted field
148
+ * matches anything. `title` is a case-insensitive substring match here — the
149
+ * programmatic API additionally accepts a `RegExp`, which JSON cannot express.
150
+ */
151
+ const AcpPermissionRuleSchema = z.object({
152
+ kind: AcpToolKindSchema.optional(),
153
+ title: z.string().min(1).optional(),
154
+ });
155
+ const AcpPermissionPolicySchema = z.object({
156
+ default: AcpPermissionDecisionSchema.optional(),
157
+ allow: z.array(AcpPermissionRuleSchema).optional(),
158
+ deny: z.array(AcpPermissionRuleSchema).optional(),
159
+ persist: z.boolean().optional(),
160
+ });
161
+ const AcpClientCapabilityConfigSchema = z.object({
162
+ readTextFile: z.boolean().optional(),
163
+ writeTextFile: z.boolean().optional(),
164
+ terminal: z.boolean().optional(),
165
+ });
166
+ const AcpNamedValueSchema = z.object({
167
+ name: z.string(),
168
+ value: z.string(),
137
169
  });
138
- const HarnessSessionPolicySchema = z.object({
170
+ /** ACP `McpServer`. Stdio is untagged and mandatory for every agent; http/sse are capability-gated. */
171
+ const AcpMcpServerSchema = z.union([
172
+ z.object({
173
+ type: z.literal('http'),
174
+ name: z.string().min(1),
175
+ url: z.string(),
176
+ headers: z.array(AcpNamedValueSchema),
177
+ }),
178
+ z.object({
179
+ type: z.literal('sse'),
180
+ name: z.string().min(1),
181
+ url: z.string(),
182
+ headers: z.array(AcpNamedValueSchema),
183
+ }),
184
+ z.object({
185
+ name: z.string().min(1),
186
+ command: z.string().min(1),
187
+ args: z.array(z.string()),
188
+ env: z.array(AcpNamedValueSchema),
189
+ }),
190
+ ]);
191
+ const AcpSessionPolicySchema = z.object({
139
192
  reuse: z.string().min(1).optional(),
140
- onComplete: z
193
+ /**
194
+ * How long the connection is kept alive. Defaults to `step`; `run` and
195
+ * `harness` are explicit opt-ins because a connection owns a live agent.
196
+ */
197
+ keepAlive: z
141
198
  .enum([
142
- 'stop',
143
- 'detach',
144
- 'destroy',
199
+ 'step',
200
+ 'run',
201
+ 'harness',
145
202
  ])
146
203
  .optional(),
204
+ load: z.string().min(1).optional(),
147
205
  });
148
206
  //#endregion
149
207
  //#region Workflow Node Schema
@@ -155,41 +213,41 @@ const OutputCodecRefSchema = z.object({
155
213
  codec: z.literal('openui'),
156
214
  library: z.string().min(1),
157
215
  });
158
- const LlmNodeSchema = z.object({
159
- kind: z.literal('llm'),
216
+ const CallModelNodeSchema = z.object({
217
+ kind: z.literal('callModel'),
160
218
  ...SHARED_FIELDS,
161
219
  model: z.string().optional(),
162
220
  instructions: z.string(),
163
- tools: z.array(LlmToolEntrySchema).optional(),
221
+ tools: z.array(CallModelToolEntrySchema).optional(),
164
222
  params: ModelParamsSchema.optional(),
165
223
  output: OutputCodecRefSchema.optional(),
166
224
  });
167
- const ToolNodeSchema = z.object({
168
- kind: z.literal('tool'),
225
+ const InvokeToolNodeSchema = z.object({
226
+ kind: z.literal('invokeTool'),
169
227
  ...SHARED_FIELDS,
170
228
  toolName: z.string().min(1),
171
229
  args: z.record(z.string(), z.unknown()).optional(),
172
230
  });
173
- const RunNodeSchema = z.object({
174
- kind: z.literal('run'),
231
+ const RunCodeNodeSchema = z.object({
232
+ kind: z.literal('runCode'),
175
233
  ...SHARED_FIELDS,
176
234
  execute: z.string().min(1),
177
235
  retry: RetryPolicySchema.optional(),
178
236
  subprocess: z.string().min(1).optional(),
179
237
  });
180
- const BranchRouteSchema = z.object({
238
+ const ConditionalRouteSchema = z.object({
181
239
  match: z.string().min(1),
182
240
  target: z.lazy(() => WorkflowNodeSchema),
183
241
  });
184
- const BranchNodeSchema = z.object({
185
- kind: z.literal('branch'),
242
+ const ConditionalNodeSchema = z.object({
243
+ kind: z.literal('conditional'),
186
244
  ...SHARED_FIELDS,
187
- routes: z.array(BranchRouteSchema).min(1),
245
+ routes: z.array(ConditionalRouteSchema).min(1),
188
246
  default: WorkflowNodeRef.optional(),
189
247
  });
190
- const ForkNodeSchema = z
248
+ const InParallelNodeSchema = z
191
249
  .object({
192
- kind: z.literal('fork'),
250
+ kind: z.literal('inParallel'),
193
251
  ...SHARED_FIELDS,
194
252
  mode: z.enum([
195
253
  'race',
@@ -203,7 +261,7 @@ const ForkNodeSchema = z
203
261
  concurrency: z.number().int().positive().optional(),
204
262
  })
205
263
  .refine((node) => (node.paths === undefined) !== (node.each === undefined), {
206
- message: "fork node requires exactly one of 'paths' (static) or 'each' (dynamic).",
264
+ message: "inParallel node requires exactly one of 'paths' (static) or 'each' (dynamic).",
207
265
  });
208
266
  const SpawnNodeSchema = z.object({
209
267
  kind: z.literal('spawn'),
@@ -212,8 +270,8 @@ const SpawnNodeSchema = z.object({
212
270
  timeout: z.number().positive().optional(),
213
271
  layers: z.array(z.string().min(1)).min(1).optional(),
214
272
  });
215
- const ProvideNodeSchema = z.object({
216
- kind: z.literal('provide'),
273
+ const WithContextNodeSchema = z.object({
274
+ kind: z.literal('withContext'),
217
275
  ...SHARED_FIELDS,
218
276
  child: WorkflowNodeRef,
219
277
  layers: z.array(z.string().min(1)).min(1),
@@ -230,11 +288,11 @@ const SequenceNodeSchema = z.object({
230
288
  ...SHARED_FIELDS,
231
289
  steps: z.array(WorkflowNodeRef).min(1),
232
290
  });
233
- const EveryNodeSchema = z.object({
234
- kind: z.literal('every'),
291
+ const ScheduleNodeSchema = z.object({
292
+ kind: z.literal('schedule'),
235
293
  ...SHARED_FIELDS,
236
294
  step: WorkflowNodeRef,
237
- ms: z.number().nonnegative(),
295
+ interval: z.number().nonnegative(),
238
296
  onError: z
239
297
  .enum([
240
298
  'continue',
@@ -254,39 +312,34 @@ const SubflowNodeSchema = z
254
312
  .refine((node) => (node.document === undefined) !== (node.ref === undefined), {
255
313
  message: "subflow node requires exactly one of 'document' (inline) or 'ref' (named).",
256
314
  });
257
- /** Builds the schema for a single harness node kind (`claude-code`, `codex`, …). */
258
- function subHarnessNodeSchema(kind) {
259
- return z.object({
260
- kind: z.literal(kind),
261
- ...SHARED_FIELDS,
262
- prompt: z.string().min(1),
263
- instructions: z.string().optional(),
264
- settings: HarnessSettingsSchema.optional(),
265
- session: HarnessSessionPolicySchema.optional(),
266
- });
267
- }
268
- const ClaudeCodeNodeSchema = subHarnessNodeSchema('claude-code');
269
- const CodexNodeSchema = subHarnessNodeSchema('codex');
270
- const OpencodeNodeSchema = subHarnessNodeSchema('opencode');
271
- const PiNodeSchema = subHarnessNodeSchema('pi');
315
+ const AcpAgentNodeSchema = z.object({
316
+ kind: z.literal('acp-agent'),
317
+ ...SHARED_FIELDS,
318
+ agent: z.string().min(1),
319
+ prompt: z.string().min(1),
320
+ cwd: z.string().min(1).optional(),
321
+ mode: z.string().min(1).optional(),
322
+ model: z.string().min(1).optional(),
323
+ mcpServers: z.array(AcpMcpServerSchema).optional(),
324
+ permissions: AcpPermissionPolicySchema.optional(),
325
+ clientCapabilities: AcpClientCapabilityConfigSchema.optional(),
326
+ session: AcpSessionPolicySchema.optional(),
327
+ });
272
328
  /** @public Zod schema validating a single `WorkflowNode` (any JSON-safe kind). */
273
329
  export const WorkflowNodeSchema = z
274
330
  .discriminatedUnion('kind', [
275
- LlmNodeSchema,
276
- ToolNodeSchema,
277
- RunNodeSchema,
278
- BranchNodeSchema,
279
- ForkNodeSchema,
331
+ CallModelNodeSchema,
332
+ InvokeToolNodeSchema,
333
+ RunCodeNodeSchema,
334
+ ConditionalNodeSchema,
335
+ InParallelNodeSchema,
280
336
  SpawnNodeSchema,
281
- ProvideNodeSchema,
337
+ WithContextNodeSchema,
282
338
  LoopNodeSchema,
283
339
  SequenceNodeSchema,
284
- EveryNodeSchema,
340
+ ScheduleNodeSchema,
285
341
  SubflowNodeSchema,
286
- ClaudeCodeNodeSchema,
287
- CodexNodeSchema,
288
- OpencodeNodeSchema,
289
- PiNodeSchema,
342
+ AcpAgentNodeSchema,
290
343
  ])
291
344
  .meta({
292
345
  id: 'WorkflowNode',
@@ -310,7 +363,7 @@ function childNodes(node) {
310
363
  switch (node.kind) {
311
364
  case 'sequence':
312
365
  return node.steps;
313
- case 'fork':
366
+ case 'inParallel':
314
367
  if (node.each) {
315
368
  return [
316
369
  node.each,
@@ -318,7 +371,7 @@ function childNodes(node) {
318
371
  }
319
372
  return node.paths ?? [];
320
373
  case 'spawn':
321
- case 'provide':
374
+ case 'withContext':
322
375
  return [
323
376
  node.child,
324
377
  ];
@@ -326,11 +379,11 @@ function childNodes(node) {
326
379
  return [
327
380
  node.body,
328
381
  ];
329
- case 'every':
382
+ case 'schedule':
330
383
  return [
331
384
  node.step,
332
385
  ];
333
- case 'branch': {
386
+ case 'conditional': {
334
387
  const children = node.routes.map((r) => r.target);
335
388
  if (node.default) {
336
389
  children.push(node.default);
@@ -388,7 +441,7 @@ export function workflowGraph(root) {
388
441
  }
389
442
  /**
390
443
  * Returns the maximum depth of a workflow tree.
391
- * Leaf nodes (`llm`, `tool`) have depth 0. Structural nodes add +1.
444
+ * Leaf nodes (`callModel`, `invokeTool`) have depth 0. Structural nodes add +1.
392
445
  * A `subflow` ref counts as a leaf — depth does not see through named refs.
393
446
  */
394
447
  export function workflowDepth(node) {