@noetic-tools/core 0.0.1 → 0.1.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/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
- package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
- package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
- package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
- package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
- package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
- package/dist/adapters/in-memory-subprocess/types.js +2 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
- package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
- package/dist/adapters/openrouter.d.ts +11 -2
- package/dist/adapters/openrouter.js +66 -5
- package/dist/builders/control-flow-builders.js +8 -3
- package/dist/builders/every.js +4 -1
- package/dist/builders/layer-provides-builders.d.ts +1 -32
- package/dist/builders/layer-provides-builders.js +1 -33
- package/dist/builders/loop-builder.js +4 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.js +4 -1
- package/dist/builders/spawn-builder.d.ts +20 -7
- package/dist/builders/spawn-builder.js +16 -2
- package/dist/builders/step-builders.d.ts +49 -28
- package/dist/builders/step-builders.js +30 -11
- package/dist/builders/tool-builder.d.ts +5 -1
- package/dist/builders/tool-builder.js +2 -0
- package/dist/builders/workflow-hydrator.d.ts +38 -0
- package/dist/builders/workflow-hydrator.js +336 -0
- package/dist/conditions/conditions.js +1 -1
- package/dist/errors/noetic-error.js +2 -0
- package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
- package/dist/harness/agent-harness.js +576 -0
- package/dist/harness/deps/adapters.d.ts +4 -0
- package/dist/harness/deps/adapters.js +4 -0
- package/dist/harness/deps/interpreter.d.ts +5 -0
- package/dist/harness/deps/interpreter.js +4 -0
- package/dist/harness/deps/memory.d.ts +5 -0
- package/dist/harness/deps/memory.js +4 -0
- package/dist/harness/deps/runtime.d.ts +10 -0
- package/dist/harness/deps/runtime.js +7 -0
- package/dist/harness/deps/types.d.ts +17 -0
- package/dist/harness/deps/types.js +1 -0
- package/dist/harness/model-call.d.ts +50 -0
- package/dist/harness/model-call.js +694 -0
- package/dist/harness/model-schema.d.ts +19 -0
- package/dist/harness/model-schema.js +46 -0
- package/dist/harness/step-bootstrap.d.ts +25 -0
- package/dist/harness/step-bootstrap.js +170 -0
- package/dist/index.d.ts +48 -12
- package/dist/index.js +29 -7
- package/dist/interpreter/action-deps.d.ts +12 -0
- package/dist/interpreter/action-deps.js +10 -0
- package/dist/interpreter/action-types.d.ts +8 -0
- package/dist/interpreter/action-types.js +1 -0
- package/dist/interpreter/collect-tools.d.ts +1 -1
- package/dist/interpreter/collect-tools.js +6 -2
- package/dist/interpreter/execute-action.d.ts +22 -0
- package/dist/interpreter/execute-action.js +527 -0
- package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +7 -1
- package/dist/interpreter/execute-control.js +530 -0
- package/dist/interpreter/execute.d.ts +13 -0
- package/dist/interpreter/execute.js +151 -48
- package/dist/interpreter/message-helpers.d.ts +1 -9
- package/dist/interpreter/message-helpers.js +1 -42
- package/dist/interpreter/step-dispatcher.d.ts +61 -0
- package/dist/interpreter/step-dispatcher.js +67 -0
- package/dist/interpreter/typeguards.d.ts +3 -10
- package/dist/interpreter/typeguards.js +3 -20
- package/dist/{runtime → memory}/exec-context-factory.js +1 -1
- package/dist/memory/flow-schema.d.ts +52 -0
- package/dist/memory/flow-schema.js +102 -0
- package/dist/memory/function-call-utils.js +4 -2
- package/dist/memory/layer-api.d.ts +1 -1
- package/dist/memory/layer-api.js +2 -2
- package/dist/memory/layer-lifecycle.d.ts +1 -1
- package/dist/memory/layer-lifecycle.js +2 -2
- package/dist/memory/layer-provides.d.ts +32 -0
- package/dist/memory/layer-provides.js +33 -0
- package/dist/memory/layer-usage.d.ts +3 -2
- package/dist/memory/layer-usage.js +9 -3
- package/dist/memory/layers/durable-task-state.js +1 -1
- package/dist/memory/layers/file-reference.js +302 -246
- package/dist/memory/layers/history-window.js +1 -1
- package/dist/memory/layers/observational-memory.js +1 -2
- package/dist/memory/layers/plan.d.ts +1 -1
- package/dist/memory/layers/plan.js +13 -8
- package/dist/memory/layers/steering.js +1 -1
- package/dist/memory/layers/tool-memory-layer.d.ts +1 -1
- package/dist/memory/layers/working-memory.js +2 -2
- package/dist/patterns/dynamic-workflow.d.ts +55 -0
- package/dist/patterns/dynamic-workflow.js +194 -0
- package/dist/patterns/flow.d.ts +2 -52
- package/dist/patterns/flow.js +1 -103
- package/dist/patterns/interview.js +1 -1
- package/dist/patterns/plans.d.ts +3 -3
- package/dist/patterns/plans.js +3 -3
- package/dist/patterns/ralph-wiggum.d.ts +1 -1
- package/dist/patterns/react.d.ts +1 -1
- package/dist/portable/adapters.d.ts +6 -0
- package/dist/portable/adapters.js +6 -0
- package/dist/portable/ask-user.d.ts +4 -0
- package/dist/portable/ask-user.js +2 -0
- package/dist/portable/builders.d.ts +12 -0
- package/dist/portable/builders.js +12 -0
- package/dist/portable/memory-layers.d.ts +12 -0
- package/dist/portable/memory-layers.js +12 -0
- package/dist/portable/runtime.d.ts +4 -0
- package/dist/portable/runtime.js +4 -0
- package/dist/portable/until.d.ts +4 -0
- package/dist/portable/until.js +4 -0
- package/dist/portable.d.ts +8 -26
- package/dist/portable.js +8 -34
- package/dist/runtime/broadcaster-utils.d.ts +5 -1
- package/dist/runtime/broadcaster-utils.js +11 -6
- package/dist/runtime/channel-store.js +1 -1
- package/dist/runtime/context-deps.d.ts +4 -0
- package/dist/runtime/context-deps.js +2 -0
- package/dist/runtime/context-impl.d.ts +42 -12
- package/dist/runtime/context-impl.js +51 -4
- package/dist/runtime/context-type-deps.d.ts +14 -0
- package/dist/runtime/context-type-deps.js +1 -0
- package/dist/runtime/detached-handle.d.ts +50 -2
- package/dist/runtime/detached-handle.js +216 -12
- package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
- package/dist/runtime/durable/checkpoint-store.js +101 -0
- package/dist/runtime/durable/detached-signal.d.ts +37 -0
- package/dist/runtime/durable/detached-signal.js +52 -0
- package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
- package/dist/runtime/durable/harness-checkpoints.js +104 -0
- package/dist/runtime/durable/index.d.ts +10 -0
- package/dist/runtime/durable/index.js +5 -0
- package/dist/runtime/durable/runnable-loop.d.ts +100 -0
- package/dist/runtime/durable/runnable-loop.js +90 -0
- package/dist/runtime/durable/session-seed.d.ts +28 -0
- package/dist/runtime/durable/session-seed.js +27 -0
- package/dist/runtime/durable/stall-nudge.d.ts +82 -0
- package/dist/runtime/durable/stall-nudge.js +87 -0
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +1 -1
- package/dist/runtime/step-registry.d.ts +42 -0
- package/dist/runtime/step-registry.js +77 -0
- package/dist/schemas/workflow.d.ts +139 -0
- package/dist/schemas/workflow.js +232 -0
- package/dist/types/ask-user-types.d.ts +57 -0
- package/dist/types/ask-user-types.js +74 -0
- package/dist/types/checkpoint.d.ts +98 -0
- package/dist/types/checkpoint.js +81 -0
- package/dist/types/common.d.ts +1 -50
- package/dist/types/context-parts/item-log.d.ts +6 -0
- package/dist/types/context-parts/item-log.js +1 -0
- package/dist/types/context-parts/layer-usage.d.ts +18 -0
- package/dist/types/context-parts/layer-usage.js +1 -0
- package/dist/types/context.d.ts +135 -26
- package/dist/types/error.d.ts +15 -0
- package/dist/types/harness-result.d.ts +1 -1
- package/dist/types/items.d.ts +18 -2
- package/dist/types/memory-context.d.ts +39 -0
- package/dist/types/memory-context.js +1 -0
- package/dist/types/memory.d.ts +35 -65
- package/dist/types/runtime.d.ts +2 -1
- package/dist/types/steering.d.ts +1 -1
- package/dist/types/step-registrar.d.ts +46 -0
- package/dist/types/step-registrar.js +56 -0
- package/dist/types/step.d.ts +30 -5
- package/dist/types/subprocess-adapter.d.ts +119 -3
- package/dist/types/subprocess-adapter.js +2 -0
- package/dist/types/tool.d.ts +64 -0
- package/dist/types/tool.js +1 -0
- package/dist/unstable.d.ts +2 -2
- package/dist/unstable.js +2 -2
- package/dist/until/predicates.d.ts +9 -0
- package/dist/until/predicates.js +14 -0
- package/dist/util/message-helpers.d.ts +17 -0
- package/dist/util/message-helpers.js +64 -0
- package/package.json +1 -1
- package/dist/adapters/local-fs-adapter.d.ts +0 -3
- package/dist/adapters/local-fs-adapter.js +0 -43
- package/dist/adapters/local-shell-adapter.d.ts +0 -51
- package/dist/adapters/local-shell-adapter.js +0 -167
- package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
- package/dist/adapters/local-subprocess-adapter.js +0 -187
- package/dist/interpreter/execute-branch.d.ts +0 -3
- package/dist/interpreter/execute-branch.js +0 -10
- package/dist/interpreter/execute-every.js +0 -143
- package/dist/interpreter/execute-fork.d.ts +0 -3
- package/dist/interpreter/execute-fork.js +0 -215
- package/dist/interpreter/execute-llm.d.ts +0 -4
- package/dist/interpreter/execute-llm.js +0 -217
- package/dist/interpreter/execute-loop.d.ts +0 -3
- package/dist/interpreter/execute-loop.js +0 -171
- package/dist/interpreter/execute-provide.d.ts +0 -10
- package/dist/interpreter/execute-provide.js +0 -64
- package/dist/interpreter/execute-run.d.ts +0 -3
- package/dist/interpreter/execute-run.js +0 -39
- package/dist/interpreter/execute-spawn.d.ts +0 -11
- package/dist/interpreter/execute-spawn.js +0 -113
- package/dist/interpreter/execute-tool.d.ts +0 -5
- package/dist/interpreter/execute-tool.js +0 -81
- package/dist/runtime/agent-harness.js +0 -1170
- /package/dist/{runtime → memory}/exec-context-factory.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.js +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-strike stall-nudge helper composable with `runnableLoop`.
|
|
3
|
+
*
|
|
4
|
+
* A runner's first turn can end without settling the outcome signal
|
|
5
|
+
* and without the agent asking for external input — i.e. the agent
|
|
6
|
+
* stopped without calling either a terminal tool or an ask-user-style
|
|
7
|
+
* tool. That's a soft failure: rather than wait forever, we nudge the
|
|
8
|
+
* agent once to remind it of its options. If the second turn also
|
|
9
|
+
* ends in the same stalled shape we escalate: the caller-supplied
|
|
10
|
+
* `onStall` callback records the escalation (task pause, metrics,
|
|
11
|
+
* whatever the domain demands) and we settle the signal ourselves with
|
|
12
|
+
* a canonical "stalled" outcome so the parent unwinds cleanly.
|
|
13
|
+
*
|
|
14
|
+
* The helper factors the state machine out of the loop so callers can
|
|
15
|
+
* opt in per-runner. Task-specific concerns (how "pending external
|
|
16
|
+
* input" is detected, how escalation is recorded, what outcome
|
|
17
|
+
* represents a stalled failure) stay with the caller and flow in via
|
|
18
|
+
* the config.
|
|
19
|
+
*/
|
|
20
|
+
import type { InputMessageItem } from '../../types/items';
|
|
21
|
+
import type { DetachedSignal } from './detached-signal';
|
|
22
|
+
import type { AfterFirstTurnContext, RunnableLoopHarness } from './runnable-loop';
|
|
23
|
+
/**
|
|
24
|
+
* Default developer-role text sent when the agent finishes a turn
|
|
25
|
+
* without calling a terminal tool or an ask-user tool. Kept short and
|
|
26
|
+
* unambiguous — the agent should be reminded of its options without
|
|
27
|
+
* being lectured. Callers may override via `createNudgeMessage(text)`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_NUDGE_MESSAGE_TEXT = "You finished a turn without calling a terminal tool or AskUserQuestion. If you need user input or instruction to continue, call AskUserQuestion now. Otherwise call your terminal tool to complete this phase.";
|
|
30
|
+
export interface CreateNudgeMessageOpts {
|
|
31
|
+
/** Unique id for the message so item-log dedupe can distinguish re-spawns. */
|
|
32
|
+
readonly id: string;
|
|
33
|
+
/** Override the developer-role text. Defaults to `DEFAULT_NUDGE_MESSAGE_TEXT`. */
|
|
34
|
+
readonly text?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface StallNudgeOpts<TOutcome> {
|
|
37
|
+
readonly harness: RunnableLoopHarness;
|
|
38
|
+
readonly threadId: string;
|
|
39
|
+
readonly signal: DetachedSignal<TOutcome>;
|
|
40
|
+
/**
|
|
41
|
+
* The framing message sent as the nudge. Use `createNudgeMessage`
|
|
42
|
+
* for the canonical developer-role item, or pass your own when a
|
|
43
|
+
* domain needs a custom shape.
|
|
44
|
+
*/
|
|
45
|
+
readonly nudgeMessage: InputMessageItem;
|
|
46
|
+
/**
|
|
47
|
+
* True when the agent intentionally awaits external input (e.g. an
|
|
48
|
+
* outstanding ask-user request). When true the nudge is suppressed
|
|
49
|
+
* — the agent is not stalled, it is waiting for us.
|
|
50
|
+
*/
|
|
51
|
+
hasPendingExternal(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Record-side-effect callback fired when the agent stalls a second
|
|
54
|
+
* time. Use this to mark the task paused, emit a metric, append an
|
|
55
|
+
* event log entry — whatever the domain needs before the signal
|
|
56
|
+
* settles.
|
|
57
|
+
*/
|
|
58
|
+
onStall(): Promise<void>;
|
|
59
|
+
/** Build the outcome value the signal resolves to on stall escalation. */
|
|
60
|
+
buildStalledOutcome(): TOutcome;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the canonical developer-role nudge message item. Callers who
|
|
64
|
+
* want a custom framing build their own `InputMessageItem`; this
|
|
65
|
+
* helper exists so the common case stays one line.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createNudgeMessage(opts: CreateNudgeMessageOpts): InputMessageItem;
|
|
68
|
+
/**
|
|
69
|
+
* Build a `runnableLoop`-compatible `afterFirstTurn` hook that
|
|
70
|
+
* implements the two-strike nudge state machine described in the
|
|
71
|
+
* module doc. The hook is a no-op when:
|
|
72
|
+
*
|
|
73
|
+
* - the signal already settled (agent called a terminal tool), or
|
|
74
|
+
* - the caller reports pending external input (`hasPendingExternal`
|
|
75
|
+
* returns true — e.g. an outstanding ask-user request).
|
|
76
|
+
*
|
|
77
|
+
* Otherwise it sends the nudge message via `harness.execute`, waits
|
|
78
|
+
* for microtasks to flush, and re-checks the same two conditions. If
|
|
79
|
+
* the agent still hasn't progressed, the hook fires `onStall` and
|
|
80
|
+
* resolves the signal with `buildStalledOutcome()`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createStallNudgeHook<TOutcome>(opts: StallNudgeOpts<TOutcome>): (ctx: AfterFirstTurnContext) => Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-strike stall-nudge helper composable with `runnableLoop`.
|
|
3
|
+
*
|
|
4
|
+
* A runner's first turn can end without settling the outcome signal
|
|
5
|
+
* and without the agent asking for external input — i.e. the agent
|
|
6
|
+
* stopped without calling either a terminal tool or an ask-user-style
|
|
7
|
+
* tool. That's a soft failure: rather than wait forever, we nudge the
|
|
8
|
+
* agent once to remind it of its options. If the second turn also
|
|
9
|
+
* ends in the same stalled shape we escalate: the caller-supplied
|
|
10
|
+
* `onStall` callback records the escalation (task pause, metrics,
|
|
11
|
+
* whatever the domain demands) and we settle the signal ourselves with
|
|
12
|
+
* a canonical "stalled" outcome so the parent unwinds cleanly.
|
|
13
|
+
*
|
|
14
|
+
* The helper factors the state machine out of the loop so callers can
|
|
15
|
+
* opt in per-runner. Task-specific concerns (how "pending external
|
|
16
|
+
* input" is detected, how escalation is recorded, what outcome
|
|
17
|
+
* represents a stalled failure) stay with the caller and flow in via
|
|
18
|
+
* the config.
|
|
19
|
+
*/
|
|
20
|
+
//#region Constants
|
|
21
|
+
/**
|
|
22
|
+
* Default developer-role text sent when the agent finishes a turn
|
|
23
|
+
* without calling a terminal tool or an ask-user tool. Kept short and
|
|
24
|
+
* unambiguous — the agent should be reminded of its options without
|
|
25
|
+
* being lectured. Callers may override via `createNudgeMessage(text)`.
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_NUDGE_MESSAGE_TEXT = 'You finished a turn without calling a terminal tool or AskUserQuestion. If you need user input or instruction to continue, call AskUserQuestion now. Otherwise call your terminal tool to complete this phase.';
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region Public API
|
|
30
|
+
/**
|
|
31
|
+
* Build the canonical developer-role nudge message item. Callers who
|
|
32
|
+
* want a custom framing build their own `InputMessageItem`; this
|
|
33
|
+
* helper exists so the common case stays one line.
|
|
34
|
+
*/
|
|
35
|
+
export function createNudgeMessage(opts) {
|
|
36
|
+
return {
|
|
37
|
+
id: opts.id,
|
|
38
|
+
type: 'message',
|
|
39
|
+
role: 'developer',
|
|
40
|
+
status: 'completed',
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: 'input_text',
|
|
44
|
+
text: opts.text ?? DEFAULT_NUDGE_MESSAGE_TEXT,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a `runnableLoop`-compatible `afterFirstTurn` hook that
|
|
51
|
+
* implements the two-strike nudge state machine described in the
|
|
52
|
+
* module doc. The hook is a no-op when:
|
|
53
|
+
*
|
|
54
|
+
* - the signal already settled (agent called a terminal tool), or
|
|
55
|
+
* - the caller reports pending external input (`hasPendingExternal`
|
|
56
|
+
* returns true — e.g. an outstanding ask-user request).
|
|
57
|
+
*
|
|
58
|
+
* Otherwise it sends the nudge message via `harness.execute`, waits
|
|
59
|
+
* for microtasks to flush, and re-checks the same two conditions. If
|
|
60
|
+
* the agent still hasn't progressed, the hook fires `onStall` and
|
|
61
|
+
* resolves the signal with `buildStalledOutcome()`.
|
|
62
|
+
*/
|
|
63
|
+
export function createStallNudgeHook(opts) {
|
|
64
|
+
return async (ctx) => {
|
|
65
|
+
if (ctx.signalSettled()) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (opts.hasPendingExternal()) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// First stall — send one nudge.
|
|
72
|
+
await opts.harness.execute(opts.nudgeMessage, {
|
|
73
|
+
threadId: opts.threadId,
|
|
74
|
+
});
|
|
75
|
+
await Promise.resolve();
|
|
76
|
+
if (ctx.signalSettled()) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (opts.hasPendingExternal()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Second stall — escalate and settle.
|
|
83
|
+
await opts.onStall();
|
|
84
|
+
opts.signal.resolve(opts.buildStalledOutcome());
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ItemSchemaRegistry } from '../schemas/item';
|
|
2
|
-
import type { ItemLog } from '../types/context';
|
|
2
|
+
import type { ItemLog } from '../types/context-parts/item-log';
|
|
3
3
|
import type { Item } from '../types/items';
|
|
4
4
|
export declare class ItemLogImpl implements ItemLog {
|
|
5
5
|
private readonly itemSchemas;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ContextMemory } from '../types/memory';
|
|
2
|
+
import type { Step } from '../types/step';
|
|
3
|
+
/** Registry-wide step type — stored at the widest generic parameters so
|
|
4
|
+
* callers can register steps with narrower input/output types without
|
|
5
|
+
* fighting variance. The public `Step` default already uses `unknown` for
|
|
6
|
+
* the input and output parameters, matching this shape. */
|
|
7
|
+
type RegisteredStep = Step<ContextMemory, unknown, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* @public
|
|
10
|
+
* Registers a step so the subprocess adapter can look it up by id.
|
|
11
|
+
*
|
|
12
|
+
* Called automatically by the step builders when a `Step` is constructed.
|
|
13
|
+
* Policy: **latest registration wins**. Dispatch-and-lookup happen in the
|
|
14
|
+
* same tick, so the live entry is always the one the caller just built;
|
|
15
|
+
* cross-test pollution from bun's per-file process model (describe blocks
|
|
16
|
+
* rebuild the same step id) is not a safety concern here. Strict
|
|
17
|
+
* "reject duplicate id with a different body" detection + a test-side
|
|
18
|
+
* `clearRegistry` wiring is deferred — see the tracked follow-up task on
|
|
19
|
+
* the team's board (strict-registry-plus-test-clear).
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerStep<TMemory, I, O>(step: Step<TMemory, I, O>): void;
|
|
22
|
+
/**
|
|
23
|
+
* @public
|
|
24
|
+
* Looks up a registered step by id. Returns `null` when the id is not known
|
|
25
|
+
* — callers (e.g. the in-memory adapter's step dispatch path) translate the
|
|
26
|
+
* miss into a typed configuration error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lookupStep(id: string): RegisteredStep | null;
|
|
29
|
+
/**
|
|
30
|
+
* @public
|
|
31
|
+
* Returns a read-only view over the registry. Primarily for tests and
|
|
32
|
+
* debugging; production callers should use `lookupStep`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getRegistry(): ReadonlyMap<string, RegisteredStep>;
|
|
35
|
+
/**
|
|
36
|
+
* @public
|
|
37
|
+
* Clears the registry. Test-only helper — exported because several suites
|
|
38
|
+
* need a clean registry between describe blocks. Not part of the public API
|
|
39
|
+
* surface consumed by agent authors.
|
|
40
|
+
*/
|
|
41
|
+
export declare function clearRegistry(): void;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NoeticConfigError } from '../errors/noetic-config-error';
|
|
2
|
+
import { setDefaultRegistrar } from '../types/step-registrar';
|
|
3
|
+
import { frameworkCast } from '../util/framework-cast';
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region Module state
|
|
6
|
+
const registry = new Map();
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region Public API
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
* Registers a step so the subprocess adapter can look it up by id.
|
|
12
|
+
*
|
|
13
|
+
* Called automatically by the step builders when a `Step` is constructed.
|
|
14
|
+
* Policy: **latest registration wins**. Dispatch-and-lookup happen in the
|
|
15
|
+
* same tick, so the live entry is always the one the caller just built;
|
|
16
|
+
* cross-test pollution from bun's per-file process model (describe blocks
|
|
17
|
+
* rebuild the same step id) is not a safety concern here. Strict
|
|
18
|
+
* "reject duplicate id with a different body" detection + a test-side
|
|
19
|
+
* `clearRegistry` wiring is deferred — see the tracked follow-up task on
|
|
20
|
+
* the team's board (strict-registry-plus-test-clear).
|
|
21
|
+
*/
|
|
22
|
+
export function registerStep(step) {
|
|
23
|
+
if (!step.id || step.id.trim() === '') {
|
|
24
|
+
throw new NoeticConfigError({
|
|
25
|
+
code: 'EMPTY_STEP_ID',
|
|
26
|
+
message: 'registerStep() requires a step with a non-empty id.',
|
|
27
|
+
hint: 'Set `id` on the step before constructing it.',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// `frameworkCast` is the approved single-point escape for generic variance:
|
|
31
|
+
// narrowing I/O from specific types to the registry's widened `unknown`
|
|
32
|
+
// shape is structurally safe at runtime because the registry only holds
|
|
33
|
+
// references and dispatches them through the same `execute()` pipeline.
|
|
34
|
+
const widened = frameworkCast(step);
|
|
35
|
+
registry.set(step.id, widened);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @public
|
|
39
|
+
* Looks up a registered step by id. Returns `null` when the id is not known
|
|
40
|
+
* — callers (e.g. the in-memory adapter's step dispatch path) translate the
|
|
41
|
+
* miss into a typed configuration error.
|
|
42
|
+
*/
|
|
43
|
+
export function lookupStep(id) {
|
|
44
|
+
return registry.get(id) ?? null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @public
|
|
48
|
+
* Returns a read-only view over the registry. Primarily for tests and
|
|
49
|
+
* debugging; production callers should use `lookupStep`.
|
|
50
|
+
*/
|
|
51
|
+
export function getRegistry() {
|
|
52
|
+
return registry;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @public
|
|
56
|
+
* Clears the registry. Test-only helper — exported because several suites
|
|
57
|
+
* need a clean registry between describe blocks. Not part of the public API
|
|
58
|
+
* surface consumed by agent authors.
|
|
59
|
+
*/
|
|
60
|
+
export function clearRegistry() {
|
|
61
|
+
registry.clear();
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region Default-registrar installation
|
|
65
|
+
/**
|
|
66
|
+
* Install this module's `registerStep` as the runtime default registrar.
|
|
67
|
+
* Executes on first import — which happens transitively from
|
|
68
|
+
* `agent-harness.ts` and from anywhere that imports `@noetic-tools/core`'s
|
|
69
|
+
* top-level barrel. Builders call `getDefaultRegistrar().register(step)`
|
|
70
|
+
* from `../types/step-registrar` and get this concrete implementation
|
|
71
|
+
* without depending on `core-runtime` themselves (fixes the
|
|
72
|
+
* `core-primitives → core-runtime` layer_direction violations).
|
|
73
|
+
*/
|
|
74
|
+
setDefaultRegistrar({
|
|
75
|
+
register: registerStep,
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for JSON-serialisable workflow definitions.
|
|
3
|
+
*
|
|
4
|
+
* A `WorkflowDocument` is a portable, JSON-safe representation of a noetic
|
|
5
|
+
* step tree. It covers every step kind except `run` (which carries arbitrary
|
|
6
|
+
* closures). The hydrator in `builders/workflow-hydrator.ts` converts a
|
|
7
|
+
* validated document back into live `Step` objects via the existing builders.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
interface UntilAnyPredicate {
|
|
11
|
+
kind: 'any';
|
|
12
|
+
predicates: UntilPredicate[];
|
|
13
|
+
}
|
|
14
|
+
interface UntilAllPredicate {
|
|
15
|
+
kind: 'all';
|
|
16
|
+
predicates: UntilPredicate[];
|
|
17
|
+
}
|
|
18
|
+
/** @public Discriminated union of named until predicates usable in JSON workflows. */
|
|
19
|
+
export type UntilPredicate = {
|
|
20
|
+
kind: 'maxSteps';
|
|
21
|
+
n: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'maxCost';
|
|
24
|
+
usd: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'maxDuration';
|
|
27
|
+
ms: number;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'noToolCalls';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'outputContains';
|
|
32
|
+
marker: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'outputEquals';
|
|
35
|
+
sentinel: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'converged';
|
|
38
|
+
threshold?: number;
|
|
39
|
+
} | UntilAnyPredicate | UntilAllPredicate;
|
|
40
|
+
/** @public Zod schema validating a named `until` predicate (including `any`/`all` combinators). */
|
|
41
|
+
export declare const UntilPredicateSchema: z.ZodType<UntilPredicate>;
|
|
42
|
+
/** @public ESM literal enum for merge strategies. */
|
|
43
|
+
export declare const MergeStrategy: {
|
|
44
|
+
readonly Last: "last";
|
|
45
|
+
readonly First: "first";
|
|
46
|
+
readonly Concat: "concat";
|
|
47
|
+
};
|
|
48
|
+
/** @public Named merge strategy for fork nodes. */
|
|
49
|
+
export type MergeStrategy = (typeof MergeStrategy)[keyof typeof MergeStrategy];
|
|
50
|
+
/** @public Zod schema for the named merge strategy used by `fork` nodes. */
|
|
51
|
+
export declare const MergeStrategySchema: z.ZodEnum<{
|
|
52
|
+
concat: "concat";
|
|
53
|
+
last: "last";
|
|
54
|
+
first: "first";
|
|
55
|
+
}>;
|
|
56
|
+
declare const ModelParamsSchema: z.ZodObject<{
|
|
57
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
58
|
+
topP: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
stopSequences: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
interface WorkflowNodeBase {
|
|
63
|
+
id: string;
|
|
64
|
+
}
|
|
65
|
+
export interface LlmWorkflowNode extends WorkflowNodeBase {
|
|
66
|
+
kind: 'llm';
|
|
67
|
+
model?: string;
|
|
68
|
+
instructions: string;
|
|
69
|
+
tools?: string[];
|
|
70
|
+
params?: z.infer<typeof ModelParamsSchema>;
|
|
71
|
+
}
|
|
72
|
+
export interface ToolWorkflowNode extends WorkflowNodeBase {
|
|
73
|
+
kind: 'tool';
|
|
74
|
+
toolName: string;
|
|
75
|
+
args?: Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
export interface BranchRoute {
|
|
78
|
+
match: string;
|
|
79
|
+
target: WorkflowNode;
|
|
80
|
+
}
|
|
81
|
+
export interface BranchWorkflowNode extends WorkflowNodeBase {
|
|
82
|
+
kind: 'branch';
|
|
83
|
+
routes: BranchRoute[];
|
|
84
|
+
default?: WorkflowNode;
|
|
85
|
+
}
|
|
86
|
+
export interface ForkWorkflowNode extends WorkflowNodeBase {
|
|
87
|
+
kind: 'fork';
|
|
88
|
+
mode: 'race' | 'all' | 'settle';
|
|
89
|
+
paths: WorkflowNode[];
|
|
90
|
+
merge?: MergeStrategy;
|
|
91
|
+
concurrency?: number;
|
|
92
|
+
}
|
|
93
|
+
export interface SpawnWorkflowNode extends WorkflowNodeBase {
|
|
94
|
+
kind: 'spawn';
|
|
95
|
+
child: WorkflowNode;
|
|
96
|
+
timeout?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface ProvideWorkflowNode extends WorkflowNodeBase {
|
|
99
|
+
kind: 'provide';
|
|
100
|
+
child: WorkflowNode;
|
|
101
|
+
layers: string[];
|
|
102
|
+
}
|
|
103
|
+
export interface LoopWorkflowNode extends WorkflowNodeBase {
|
|
104
|
+
kind: 'loop';
|
|
105
|
+
body: WorkflowNode;
|
|
106
|
+
until: UntilPredicate;
|
|
107
|
+
maxIterations?: number;
|
|
108
|
+
}
|
|
109
|
+
export interface SequenceWorkflowNode extends WorkflowNodeBase {
|
|
110
|
+
kind: 'sequence';
|
|
111
|
+
steps: WorkflowNode[];
|
|
112
|
+
}
|
|
113
|
+
export interface EveryWorkflowNode extends WorkflowNodeBase {
|
|
114
|
+
kind: 'every';
|
|
115
|
+
step: WorkflowNode;
|
|
116
|
+
ms: number;
|
|
117
|
+
onError?: 'continue' | 'fail';
|
|
118
|
+
}
|
|
119
|
+
/** @public Discriminated union of all JSON-serialisable workflow node kinds. */
|
|
120
|
+
export type WorkflowNode = LlmWorkflowNode | ToolWorkflowNode | BranchWorkflowNode | ForkWorkflowNode | SpawnWorkflowNode | ProvideWorkflowNode | LoopWorkflowNode | SequenceWorkflowNode | EveryWorkflowNode;
|
|
121
|
+
/** @public Zod schema validating a single `WorkflowNode` (any of the 9 JSON-safe kinds). */
|
|
122
|
+
export declare const WorkflowNodeSchema: z.ZodType<WorkflowNode>;
|
|
123
|
+
/** @public Top-level envelope for a JSON workflow definition. */
|
|
124
|
+
export interface WorkflowDocument {
|
|
125
|
+
version: 1;
|
|
126
|
+
root: WorkflowNode;
|
|
127
|
+
}
|
|
128
|
+
/** @public Zod schema validating a complete `WorkflowDocument`. */
|
|
129
|
+
export declare const WorkflowDocumentSchema: z.ZodType<WorkflowDocument>;
|
|
130
|
+
/** Validates a candidate workflow document. Throws `ZodError` on invalid input. */
|
|
131
|
+
export declare function validateWorkflow(input: unknown): WorkflowDocument;
|
|
132
|
+
/** Walks a workflow tree depth-first, yielding each node. */
|
|
133
|
+
export declare function walkWorkflow(node: WorkflowNode): Iterable<WorkflowNode>;
|
|
134
|
+
/**
|
|
135
|
+
* Returns the maximum depth of a workflow tree.
|
|
136
|
+
* Leaf nodes (`llm`, `tool`) have depth 0. Structural nodes add +1.
|
|
137
|
+
*/
|
|
138
|
+
export declare function workflowDepth(node: WorkflowNode): number;
|
|
139
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for JSON-serialisable workflow definitions.
|
|
3
|
+
*
|
|
4
|
+
* A `WorkflowDocument` is a portable, JSON-safe representation of a noetic
|
|
5
|
+
* step tree. It covers every step kind except `run` (which carries arbitrary
|
|
6
|
+
* closures). The hydrator in `builders/workflow-hydrator.ts` converts a
|
|
7
|
+
* validated document back into live `Step` objects via the existing builders.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region Until Predicate Schemas
|
|
12
|
+
const MaxStepsPredicateSchema = z.object({
|
|
13
|
+
kind: z.literal('maxSteps'),
|
|
14
|
+
n: z.number().int().positive(),
|
|
15
|
+
});
|
|
16
|
+
const MaxCostPredicateSchema = z.object({
|
|
17
|
+
kind: z.literal('maxCost'),
|
|
18
|
+
usd: z.number().positive(),
|
|
19
|
+
});
|
|
20
|
+
const MaxDurationPredicateSchema = z.object({
|
|
21
|
+
kind: z.literal('maxDuration'),
|
|
22
|
+
ms: z.number().positive(),
|
|
23
|
+
});
|
|
24
|
+
const NoToolCallsPredicateSchema = z.object({
|
|
25
|
+
kind: z.literal('noToolCalls'),
|
|
26
|
+
});
|
|
27
|
+
const OutputContainsPredicateSchema = z.object({
|
|
28
|
+
kind: z.literal('outputContains'),
|
|
29
|
+
marker: z.string().min(1),
|
|
30
|
+
});
|
|
31
|
+
const OutputEqualsPredicateSchema = z.object({
|
|
32
|
+
kind: z.literal('outputEquals'),
|
|
33
|
+
sentinel: z.string().min(1),
|
|
34
|
+
});
|
|
35
|
+
const ConvergedPredicateSchema = z.object({
|
|
36
|
+
kind: z.literal('converged'),
|
|
37
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
38
|
+
});
|
|
39
|
+
const UntilPredicateRef = z.lazy(() => UntilPredicateSchema);
|
|
40
|
+
const AnyPredicateSchema = z.object({
|
|
41
|
+
kind: z.literal('any'),
|
|
42
|
+
predicates: z.array(UntilPredicateRef).min(1),
|
|
43
|
+
});
|
|
44
|
+
const AllPredicateSchema = z.object({
|
|
45
|
+
kind: z.literal('all'),
|
|
46
|
+
predicates: z.array(UntilPredicateRef).min(1),
|
|
47
|
+
});
|
|
48
|
+
/** @public Zod schema validating a named `until` predicate (including `any`/`all` combinators). */
|
|
49
|
+
export const UntilPredicateSchema = z.union([
|
|
50
|
+
MaxStepsPredicateSchema,
|
|
51
|
+
MaxCostPredicateSchema,
|
|
52
|
+
MaxDurationPredicateSchema,
|
|
53
|
+
NoToolCallsPredicateSchema,
|
|
54
|
+
OutputContainsPredicateSchema,
|
|
55
|
+
OutputEqualsPredicateSchema,
|
|
56
|
+
ConvergedPredicateSchema,
|
|
57
|
+
AnyPredicateSchema,
|
|
58
|
+
AllPredicateSchema,
|
|
59
|
+
]);
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region Merge Strategy
|
|
62
|
+
/** @public ESM literal enum for merge strategies. */
|
|
63
|
+
export const MergeStrategy = {
|
|
64
|
+
Last: 'last',
|
|
65
|
+
First: 'first',
|
|
66
|
+
Concat: 'concat',
|
|
67
|
+
};
|
|
68
|
+
/** @public Zod schema for the named merge strategy used by `fork` nodes. */
|
|
69
|
+
export const MergeStrategySchema = z.enum([
|
|
70
|
+
'last',
|
|
71
|
+
'first',
|
|
72
|
+
'concat',
|
|
73
|
+
]);
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region Model Params
|
|
76
|
+
const ModelParamsSchema = z.object({
|
|
77
|
+
temperature: z.number().min(0).optional(),
|
|
78
|
+
topP: z.number().min(0).max(1).optional(),
|
|
79
|
+
maxTokens: z.number().int().positive().optional(),
|
|
80
|
+
stopSequences: z.array(z.string()).optional(),
|
|
81
|
+
});
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region Workflow Node Schema
|
|
84
|
+
const WorkflowNodeRef = z.lazy(() => WorkflowNodeSchema);
|
|
85
|
+
const SHARED_FIELDS = {
|
|
86
|
+
id: z.string().min(1),
|
|
87
|
+
};
|
|
88
|
+
const LlmNodeSchema = z.object({
|
|
89
|
+
kind: z.literal('llm'),
|
|
90
|
+
...SHARED_FIELDS,
|
|
91
|
+
model: z.string().optional(),
|
|
92
|
+
instructions: z.string(),
|
|
93
|
+
tools: z.array(z.string()).optional(),
|
|
94
|
+
params: ModelParamsSchema.optional(),
|
|
95
|
+
});
|
|
96
|
+
const ToolNodeSchema = z.object({
|
|
97
|
+
kind: z.literal('tool'),
|
|
98
|
+
...SHARED_FIELDS,
|
|
99
|
+
toolName: z.string().min(1),
|
|
100
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
101
|
+
});
|
|
102
|
+
const BranchRouteSchema = z.object({
|
|
103
|
+
match: z.string().min(1),
|
|
104
|
+
target: z.lazy(() => WorkflowNodeSchema),
|
|
105
|
+
});
|
|
106
|
+
const BranchNodeSchema = z.object({
|
|
107
|
+
kind: z.literal('branch'),
|
|
108
|
+
...SHARED_FIELDS,
|
|
109
|
+
routes: z.array(BranchRouteSchema).min(1),
|
|
110
|
+
default: WorkflowNodeRef.optional(),
|
|
111
|
+
});
|
|
112
|
+
const ForkNodeSchema = z.object({
|
|
113
|
+
kind: z.literal('fork'),
|
|
114
|
+
...SHARED_FIELDS,
|
|
115
|
+
mode: z.enum([
|
|
116
|
+
'race',
|
|
117
|
+
'all',
|
|
118
|
+
'settle',
|
|
119
|
+
]),
|
|
120
|
+
paths: z.array(WorkflowNodeRef).min(1),
|
|
121
|
+
merge: MergeStrategySchema.optional(),
|
|
122
|
+
concurrency: z.number().int().positive().optional(),
|
|
123
|
+
});
|
|
124
|
+
const SpawnNodeSchema = z.object({
|
|
125
|
+
kind: z.literal('spawn'),
|
|
126
|
+
...SHARED_FIELDS,
|
|
127
|
+
child: WorkflowNodeRef,
|
|
128
|
+
timeout: z.number().positive().optional(),
|
|
129
|
+
});
|
|
130
|
+
const ProvideNodeSchema = z.object({
|
|
131
|
+
kind: z.literal('provide'),
|
|
132
|
+
...SHARED_FIELDS,
|
|
133
|
+
child: WorkflowNodeRef,
|
|
134
|
+
layers: z.array(z.string().min(1)).min(1),
|
|
135
|
+
});
|
|
136
|
+
const LoopNodeSchema = z.object({
|
|
137
|
+
kind: z.literal('loop'),
|
|
138
|
+
...SHARED_FIELDS,
|
|
139
|
+
body: WorkflowNodeRef,
|
|
140
|
+
until: UntilPredicateSchema,
|
|
141
|
+
maxIterations: z.number().int().positive().optional(),
|
|
142
|
+
});
|
|
143
|
+
const SequenceNodeSchema = z.object({
|
|
144
|
+
kind: z.literal('sequence'),
|
|
145
|
+
...SHARED_FIELDS,
|
|
146
|
+
steps: z.array(WorkflowNodeRef).min(1),
|
|
147
|
+
});
|
|
148
|
+
const EveryNodeSchema = z.object({
|
|
149
|
+
kind: z.literal('every'),
|
|
150
|
+
...SHARED_FIELDS,
|
|
151
|
+
step: WorkflowNodeRef,
|
|
152
|
+
ms: z.number().nonnegative(),
|
|
153
|
+
onError: z
|
|
154
|
+
.enum([
|
|
155
|
+
'continue',
|
|
156
|
+
'fail',
|
|
157
|
+
])
|
|
158
|
+
.optional(),
|
|
159
|
+
});
|
|
160
|
+
/** @public Zod schema validating a single `WorkflowNode` (any of the 9 JSON-safe kinds). */
|
|
161
|
+
export const WorkflowNodeSchema = z.discriminatedUnion('kind', [
|
|
162
|
+
LlmNodeSchema,
|
|
163
|
+
ToolNodeSchema,
|
|
164
|
+
BranchNodeSchema,
|
|
165
|
+
ForkNodeSchema,
|
|
166
|
+
SpawnNodeSchema,
|
|
167
|
+
ProvideNodeSchema,
|
|
168
|
+
LoopNodeSchema,
|
|
169
|
+
SequenceNodeSchema,
|
|
170
|
+
EveryNodeSchema,
|
|
171
|
+
]);
|
|
172
|
+
/** @public Zod schema validating a complete `WorkflowDocument`. */
|
|
173
|
+
export const WorkflowDocumentSchema = z.object({
|
|
174
|
+
version: z.literal(1),
|
|
175
|
+
root: WorkflowNodeSchema,
|
|
176
|
+
});
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region Helpers
|
|
179
|
+
/** Returns the direct child nodes of a workflow node. Leaf nodes return an empty array. */
|
|
180
|
+
function childNodes(node) {
|
|
181
|
+
switch (node.kind) {
|
|
182
|
+
case 'sequence':
|
|
183
|
+
return node.steps;
|
|
184
|
+
case 'fork':
|
|
185
|
+
return node.paths;
|
|
186
|
+
case 'spawn':
|
|
187
|
+
case 'provide':
|
|
188
|
+
return [
|
|
189
|
+
node.child,
|
|
190
|
+
];
|
|
191
|
+
case 'loop':
|
|
192
|
+
return [
|
|
193
|
+
node.body,
|
|
194
|
+
];
|
|
195
|
+
case 'every':
|
|
196
|
+
return [
|
|
197
|
+
node.step,
|
|
198
|
+
];
|
|
199
|
+
case 'branch': {
|
|
200
|
+
const children = node.routes.map((r) => r.target);
|
|
201
|
+
if (node.default) {
|
|
202
|
+
children.push(node.default);
|
|
203
|
+
}
|
|
204
|
+
return children;
|
|
205
|
+
}
|
|
206
|
+
default:
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Validates a candidate workflow document. Throws `ZodError` on invalid input. */
|
|
211
|
+
export function validateWorkflow(input) {
|
|
212
|
+
return WorkflowDocumentSchema.parse(input);
|
|
213
|
+
}
|
|
214
|
+
/** Walks a workflow tree depth-first, yielding each node. */
|
|
215
|
+
export function* walkWorkflow(node) {
|
|
216
|
+
yield node;
|
|
217
|
+
for (const child of childNodes(node)) {
|
|
218
|
+
yield* walkWorkflow(child);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Returns the maximum depth of a workflow tree.
|
|
223
|
+
* Leaf nodes (`llm`, `tool`) have depth 0. Structural nodes add +1.
|
|
224
|
+
*/
|
|
225
|
+
export function workflowDepth(node) {
|
|
226
|
+
const children = childNodes(node);
|
|
227
|
+
if (children.length === 0) {
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
return 1 + Math.max(0, ...children.map(workflowDepth));
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|