@noetic-tools/core 3.2.0 → 5.0.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-subprocess/manifest-persistence.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/types.d.ts +1 -1
- package/dist/adapters/openrouter.d.ts +2 -2
- package/dist/adapters/openrouter.js +2 -2
- package/dist/builders/context-builder.d.ts +11 -0
- package/dist/builders/context-builder.js +15 -0
- package/dist/builders/context-option.d.ts +23 -0
- package/dist/builders/context-option.js +22 -0
- package/dist/builders/control-flow-builders.d.ts +19 -19
- package/dist/builders/every.d.ts +4 -4
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/loop-builder.d.ts +3 -3
- package/dist/builders/provide-builder.d.ts +14 -7
- package/dist/builders/provide-builder.js +15 -4
- package/dist/builders/spawn-builder.d.ts +8 -6
- package/dist/builders/spawn-builder.js +7 -2
- package/dist/builders/step-builders.d.ts +19 -20
- package/dist/builders/tool-builder.d.ts +9 -5
- package/dist/builders/tool-builder.js +3 -2
- package/dist/builders/workflow-hydrator.d.ts +17 -4
- package/dist/builders/workflow-hydrator.js +87 -8
- package/dist/builders/workflow-step.d.ts +61 -0
- package/dist/builders/workflow-step.js +126 -0
- package/dist/conditions/conditions.d.ts +17 -17
- package/dist/conditions/conditions.js +1 -1
- package/dist/deprecated.d.ts +30 -0
- package/dist/deprecated.js +28 -0
- package/dist/harness/agent-harness.d.ts +42 -26
- package/dist/harness/agent-harness.js +68 -21
- package/dist/harness/deps/{memory.d.ts → context.d.ts} +2 -2
- package/dist/harness/deps/{memory.js → context.js} +1 -1
- package/dist/harness/deps/types.d.ts +1 -1
- package/dist/harness/model-schema.d.ts +2 -2
- package/dist/index.d.ts +21 -17
- package/dist/index.js +14 -8
- package/dist/interpreter/action-deps.d.ts +3 -3
- package/dist/interpreter/action-deps.js +2 -2
- package/dist/interpreter/action-types.d.ts +1 -1
- package/dist/interpreter/collect-tools.d.ts +1 -1
- package/dist/interpreter/execute-action.d.ts +10 -10
- package/dist/interpreter/execute-action.js +13 -13
- package/dist/interpreter/execute-control.d.ts +4 -4
- package/dist/interpreter/execute-control.js +1 -1
- package/dist/interpreter/execute-sub-harness.d.ts +1 -1
- package/dist/interpreter/execute.d.ts +3 -3
- package/dist/interpreter/execute.js +78 -31
- package/dist/interpreter/step-dispatcher.d.ts +3 -3
- package/dist/interpreter/sub-harness-events.d.ts +2 -2
- package/dist/interpreter/typeguards.d.ts +4 -4
- package/dist/patterns/dynamic-workflow.d.ts +7 -4
- package/dist/patterns/dynamic-workflow.js +2 -0
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/plans.d.ts +5 -5
- package/dist/patterns/ralph-wiggum.d.ts +2 -2
- package/dist/patterns/react.d.ts +7 -5
- package/dist/patterns/react.js +6 -4
- package/dist/portable/builders.js +2 -0
- package/dist/portable/context-layers.d.ts +8 -0
- package/dist/portable/context-layers.js +8 -0
- package/dist/portable.d.ts +2 -2
- package/dist/portable.js +2 -2
- package/dist/runtime/channel-store.d.ts +33 -0
- package/dist/runtime/channel-store.js +297 -11
- package/dist/runtime/context-deps.d.ts +2 -2
- package/dist/runtime/context-deps.js +1 -1
- package/dist/runtime/context-impl.d.ts +10 -8
- package/dist/runtime/context-impl.js +11 -7
- package/dist/runtime/context-type-deps.d.ts +1 -1
- package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
- package/dist/runtime/durable/harness-checkpoints.d.ts +2 -3
- package/dist/runtime/durable/harness-checkpoints.js +2 -3
- package/dist/runtime/durable/step-ledger.d.ts +1 -1
- package/dist/runtime/durable/step-ledger.js +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/session-runner.d.ts +1 -1
- package/dist/runtime/step-registry.d.ts +3 -3
- package/dist/runtime/{tool-memory.d.ts → tool-context.d.ts} +1 -2
- package/dist/runtime/{tool-memory.js → tool-context.js} +5 -2
- package/dist/runtime/tool-ui.d.ts +1 -1
- package/dist/runtime/tool-ui.js +1 -1
- package/dist/schemas/workflow.d.ts +5 -279
- package/dist/schemas/workflow.js +4 -376
- package/dist/types/mutable-context.d.ts +2 -2
- package/dist/types/step-registrar.d.ts +1 -1
- package/dist/unstable.d.ts +5 -5
- package/dist/unstable.js +3 -3
- package/dist/until/predicates.d.ts +1 -1
- package/package.json +4 -4
- package/schema/noetic-workflow.schema.json +28 -0
- package/dist/builders/memory-builder.d.ts +0 -11
- package/dist/builders/memory-builder.js +0 -15
- package/dist/patterns/flow.d.ts +0 -2
- package/dist/patterns/flow.js +0 -1
- package/dist/portable/memory-layers.d.ts +0 -8
- package/dist/portable/memory-layers.js +0 -8
|
@@ -146,15 +146,22 @@ function hydrateToolNode(node, ctx) {
|
|
|
146
146
|
arguments: JSON.stringify(args),
|
|
147
147
|
};
|
|
148
148
|
execCtx.itemLog.append(callItem);
|
|
149
|
+
const layerState = {
|
|
150
|
+
get: (_layerId) => undefined,
|
|
151
|
+
set: (_layerId, _state) => { },
|
|
152
|
+
};
|
|
149
153
|
const toolCtx = {
|
|
150
154
|
ctx: execCtx,
|
|
151
155
|
harness: execCtx.harness,
|
|
152
156
|
fs: execCtx.fs,
|
|
153
157
|
shell: execCtx.shell,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
context: layerState,
|
|
159
|
+
// Deprecated alias — the same accessor object, as
|
|
160
|
+
// `buildToolExecutionContext` does. `Tool.execute` takes its second
|
|
161
|
+
// argument as `unknown`, so nothing here is structurally checked
|
|
162
|
+
// against `ToolExecutionContext`; the annotation above is what makes
|
|
163
|
+
// a missing field a compile error instead of a runtime `undefined`.
|
|
164
|
+
memory: layerState,
|
|
158
165
|
assembledView: execCtx.itemLog.items,
|
|
159
166
|
lastStepMeta: execCtx.lastStepMeta,
|
|
160
167
|
};
|
|
@@ -285,7 +292,7 @@ function resolveNamedLayers({ names, nodeKind, nodeId, ctx, }) {
|
|
|
285
292
|
if (!layer) {
|
|
286
293
|
throw new NoeticConfigError({
|
|
287
294
|
code: 'UNKNOWN_LAYER_REFERENCE',
|
|
288
|
-
message: `
|
|
295
|
+
message: `Context layer '${name}' referenced in ${nodeKind} node '${nodeId}' is not registered.`,
|
|
289
296
|
hint: `Available layers: ${[
|
|
290
297
|
...(ctx.layers?.keys() ?? []),
|
|
291
298
|
].join(', ') || '(none)'}. Pass named layers via HydrationContext.layers.`,
|
|
@@ -298,7 +305,7 @@ function hydrateSpawnNode(node, ctx) {
|
|
|
298
305
|
if (node.kind !== 'spawn') {
|
|
299
306
|
return frameworkCast(undefined);
|
|
300
307
|
}
|
|
301
|
-
// `
|
|
308
|
+
// `context` is left undefined when the node names no layers so the child
|
|
302
309
|
// inherits the parent's layers (spec 04). An explicit list replaces them.
|
|
303
310
|
const resolvedLayers = node.layers
|
|
304
311
|
? resolveNamedLayers({
|
|
@@ -312,7 +319,7 @@ function hydrateSpawnNode(node, ctx) {
|
|
|
312
319
|
id: node.id,
|
|
313
320
|
child: hydrateNode(node.child, ctx),
|
|
314
321
|
timeout: node.timeout,
|
|
315
|
-
|
|
322
|
+
context: resolvedLayers,
|
|
316
323
|
});
|
|
317
324
|
}
|
|
318
325
|
function hydrateProvideNode(node, ctx) {
|
|
@@ -322,7 +329,7 @@ function hydrateProvideNode(node, ctx) {
|
|
|
322
329
|
return provide({
|
|
323
330
|
id: node.id,
|
|
324
331
|
child: hydrateNode(node.child, ctx),
|
|
325
|
-
|
|
332
|
+
context: resolveNamedLayers({
|
|
326
333
|
names: node.layers,
|
|
327
334
|
nodeKind: 'provide',
|
|
328
335
|
nodeId: node.id,
|
|
@@ -401,6 +408,75 @@ function hydrateSubHarnessNode(node, ctx) {
|
|
|
401
408
|
session: node.session,
|
|
402
409
|
});
|
|
403
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Hydrates a `subflow` node lazily: the target document resolves and hydrates
|
|
413
|
+
* on first execution, memoized. Lazy because `HydrationContext.workflows` may
|
|
414
|
+
* be a live view that gains entries after hydration, and because it keeps
|
|
415
|
+
* cycle detection path-scoped — each ref chain threads its own ancestry set,
|
|
416
|
+
* so diamond reuse of a named workflow is legal while A→B→A throws.
|
|
417
|
+
*/
|
|
418
|
+
function hydrateSubflowNode(node, ctx) {
|
|
419
|
+
if (node.kind !== 'subflow') {
|
|
420
|
+
return frameworkCast(undefined);
|
|
421
|
+
}
|
|
422
|
+
let cached;
|
|
423
|
+
const resolve = () => {
|
|
424
|
+
if (!cached) {
|
|
425
|
+
const { doc, childCtx } = resolveSubflowDocument(node, ctx);
|
|
426
|
+
cached = hydrateNode(suffixNodeIds(doc.root, `-${node.id}`), childCtx);
|
|
427
|
+
}
|
|
428
|
+
return cached;
|
|
429
|
+
};
|
|
430
|
+
return frameworkCast(step.run({
|
|
431
|
+
id: node.id,
|
|
432
|
+
execute: async (input, execCtx) => {
|
|
433
|
+
const child = resolve();
|
|
434
|
+
return stringifyResult(await ctx.executeStep(child, node.input ?? input, execCtx));
|
|
435
|
+
},
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
function resolveSubflowDocument(node, ctx) {
|
|
439
|
+
if (node.document) {
|
|
440
|
+
// Inline documents are finite JSON trees — they cannot cycle, so they
|
|
441
|
+
// add nothing to the ancestry chain.
|
|
442
|
+
return {
|
|
443
|
+
doc: node.document,
|
|
444
|
+
childCtx: ctx,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const ref = node.ref ?? '';
|
|
448
|
+
const ancestry = ctx.subflowAncestry ?? new Set();
|
|
449
|
+
if (ancestry.has(ref)) {
|
|
450
|
+
throw new NoeticConfigError({
|
|
451
|
+
code: 'WORKFLOW_CYCLE',
|
|
452
|
+
message: `Sub-workflow '${ref}' referenced in subflow node '${node.id}' forms a reference cycle (${[
|
|
453
|
+
...ancestry,
|
|
454
|
+
ref,
|
|
455
|
+
].join(' -> ')}).`,
|
|
456
|
+
hint: 'A named sub-workflow may not reference itself, directly or transitively. Break the cycle or inline the document.',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
const doc = ctx.workflows?.get(ref);
|
|
460
|
+
if (!doc) {
|
|
461
|
+
throw new NoeticConfigError({
|
|
462
|
+
code: 'UNKNOWN_WORKFLOW_REFERENCE',
|
|
463
|
+
message: `Sub-workflow '${ref}' referenced in subflow node '${node.id}' is not registered.`,
|
|
464
|
+
hint: `Pass named workflows via HydrationContext.workflows. Available: ${[
|
|
465
|
+
...(ctx.workflows?.keys() ?? []),
|
|
466
|
+
].join(', ') || '(none)'}.`,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
doc,
|
|
471
|
+
childCtx: {
|
|
472
|
+
...ctx,
|
|
473
|
+
subflowAncestry: new Set([
|
|
474
|
+
...ancestry,
|
|
475
|
+
ref,
|
|
476
|
+
]),
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
404
480
|
//#endregion
|
|
405
481
|
//#region Handler Registry
|
|
406
482
|
const NODE_HYDRATORS = {
|
|
@@ -414,6 +490,7 @@ const NODE_HYDRATORS = {
|
|
|
414
490
|
loop: hydrateLoopNode,
|
|
415
491
|
sequence: hydrateSequenceNode,
|
|
416
492
|
every: hydrateEveryNode,
|
|
493
|
+
subflow: hydrateSubflowNode,
|
|
417
494
|
'claude-code': hydrateSubHarnessNode,
|
|
418
495
|
codex: hydrateSubHarnessNode,
|
|
419
496
|
opencode: hydrateSubHarnessNode,
|
|
@@ -611,6 +688,8 @@ async function runCodeViaSubprocess(opts) {
|
|
|
611
688
|
* @throws `NoeticConfigError` with code `UNKNOWN_NODE_KIND` if the node kind is unrecognised.
|
|
612
689
|
* @throws `NoeticConfigError` with code `UNKNOWN_TOOL_REFERENCE` if a tool name cannot be resolved.
|
|
613
690
|
* @throws `NoeticConfigError` with code `UNKNOWN_UNTIL_PREDICATE` if an until predicate kind is unrecognised.
|
|
691
|
+
* @throws `NoeticConfigError` with code `UNKNOWN_WORKFLOW_REFERENCE` (at execution time) if a subflow ref names no registered workflow.
|
|
692
|
+
* @throws `NoeticConfigError` with code `WORKFLOW_CYCLE` (at execution time) if named sub-workflow refs form a cycle.
|
|
614
693
|
*/
|
|
615
694
|
export function hydrateNode(node, ctx) {
|
|
616
695
|
const hydrator = NODE_HYDRATORS[node.kind];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `step.workflow` — runs a `WorkflowDocument` as a single composable step.
|
|
3
|
+
*
|
|
4
|
+
* Lives beside `step-builders.ts` (rather than inside it) because the hydrator
|
|
5
|
+
* imports the base `step` object; adding a hydrating builder there would create
|
|
6
|
+
* an import cycle. This module composes the public `step` namespace instead.
|
|
7
|
+
*/
|
|
8
|
+
import type { ContextData, ContextLayer } from '@noetic-tools/context';
|
|
9
|
+
import type { OutputCodec, StepRun, SubHarness, SubHarnessKind, SubprocessAdapter, Tool, WorkflowDocument } from '@noetic-tools/types';
|
|
10
|
+
import * as stepBuilders from './step-builders.js';
|
|
11
|
+
/** @public Options for `step.workflow`. */
|
|
12
|
+
export interface StepWorkflowOpts {
|
|
13
|
+
id: string;
|
|
14
|
+
/** Inline workflow document. Mutually exclusive with `ref`. */
|
|
15
|
+
document?: WorkflowDocument;
|
|
16
|
+
/** Named workflow resolved from `workflows`. Mutually exclusive with `document`. */
|
|
17
|
+
ref?: string;
|
|
18
|
+
/** Client tools the document's `llm`/`tool` nodes may reference by name. Default: none. */
|
|
19
|
+
tools?: Tool[];
|
|
20
|
+
/** Named context layers for `provide`/`spawn` nodes. */
|
|
21
|
+
layers?: ReadonlyMap<string, ContextLayer>;
|
|
22
|
+
/** Named sub-workflows for `subflow` nodes — and for resolving `ref` itself. */
|
|
23
|
+
workflows?: ReadonlyMap<string, WorkflowDocument>;
|
|
24
|
+
/** SubHarness adapters for `claude-code`/`codex`/`opencode`/`pi` nodes. */
|
|
25
|
+
subHarnesses?: ReadonlyMap<SubHarnessKind, SubHarness>;
|
|
26
|
+
/** Output codecs for `llm` nodes' `output` codec references. */
|
|
27
|
+
uiLibraries?: ReadonlyMap<string, OutputCodec>;
|
|
28
|
+
/** Resolver for named subprocess adapters on `run` nodes. */
|
|
29
|
+
resolveSubprocess?: (ref: string) => SubprocessAdapter | undefined;
|
|
30
|
+
/** `'inherit'` (default) runs in the caller's session; `'spawn'` isolates via `spawn()`. */
|
|
31
|
+
isolation?: 'inherit' | 'spawn';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates a step that hydrates and executes a JSON workflow document.
|
|
35
|
+
*
|
|
36
|
+
* The document resolves lazily on first execution (so a `ref` may target a
|
|
37
|
+
* workflow registered after the step is built) and the hydrated tree is
|
|
38
|
+
* memoized across executions. When the step itself is built from a `ref`,
|
|
39
|
+
* that name seeds the subflow ancestry chain, so a self-referencing named
|
|
40
|
+
* workflow fails with `WORKFLOW_CYCLE` instead of recursing forever.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
* @param opts - See `StepWorkflowOpts`.
|
|
44
|
+
* @returns A `StepRun` executing the workflow via the harness on the context.
|
|
45
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
46
|
+
* @throws `NoeticConfigError` with code `INVALID_WORKFLOW_SOURCE` unless exactly one of `document`/`ref` is set.
|
|
47
|
+
* @throws `NoeticConfigError` with code `MISSING_HARNESS_CONTEXT` (at execution) without `ctx.harness`.
|
|
48
|
+
* @throws `NoeticConfigError` with code `UNKNOWN_WORKFLOW_REFERENCE` (at execution) if `ref` names no registered workflow.
|
|
49
|
+
*/
|
|
50
|
+
export declare function stepWorkflow(opts: StepWorkflowOpts): StepRun<ContextData, string, string>;
|
|
51
|
+
/** @public The step builder namespace: the base builders plus `workflow`. */
|
|
52
|
+
export declare const step: {
|
|
53
|
+
readonly run: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepRunOpts<TContext, I, O>) => StepRun<TContext, I, O>;
|
|
54
|
+
readonly llm: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepLLMOpts<TContext, O>) => import("@noetic-tools/types").StepLLM<TContext, I, O>;
|
|
55
|
+
readonly tool: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepToolOpts<I, O>) => import("@noetic-tools/types").StepTool<TContext, I, O>;
|
|
56
|
+
readonly claudeCode: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
57
|
+
readonly codex: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
58
|
+
readonly opencode: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
59
|
+
readonly pi: <TContext = Readonly<Record<string, Record<string, unknown>>>, I = unknown, O = unknown>(opts: stepBuilders.StepSubHarnessOpts<TContext, O>) => import("@noetic-tools/types").StepSubHarness<TContext, I, O>;
|
|
60
|
+
readonly workflow: typeof stepWorkflow;
|
|
61
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `step.workflow` — runs a `WorkflowDocument` as a single composable step.
|
|
3
|
+
*
|
|
4
|
+
* Lives beside `step-builders.ts` (rather than inside it) because the hydrator
|
|
5
|
+
* imports the base `step` object; adding a hydrating builder there would create
|
|
6
|
+
* an import cycle. This module composes the public `step` namespace instead.
|
|
7
|
+
*/
|
|
8
|
+
import { frameworkCast, NoeticConfigError } from '@noetic-tools/types';
|
|
9
|
+
import { spawn } from './spawn-builder.js';
|
|
10
|
+
import * as stepBuilders from './step-builders.js';
|
|
11
|
+
import { hydrateWorkflow } from './workflow-hydrator.js';
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region Builder
|
|
14
|
+
/**
|
|
15
|
+
* Creates a step that hydrates and executes a JSON workflow document.
|
|
16
|
+
*
|
|
17
|
+
* The document resolves lazily on first execution (so a `ref` may target a
|
|
18
|
+
* workflow registered after the step is built) and the hydrated tree is
|
|
19
|
+
* memoized across executions. When the step itself is built from a `ref`,
|
|
20
|
+
* that name seeds the subflow ancestry chain, so a self-referencing named
|
|
21
|
+
* workflow fails with `WORKFLOW_CYCLE` instead of recursing forever.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
* @param opts - See `StepWorkflowOpts`.
|
|
25
|
+
* @returns A `StepRun` executing the workflow via the harness on the context.
|
|
26
|
+
* @throws `NoeticConfigError` with code `EMPTY_STEP_ID` if `id` is empty.
|
|
27
|
+
* @throws `NoeticConfigError` with code `INVALID_WORKFLOW_SOURCE` unless exactly one of `document`/`ref` is set.
|
|
28
|
+
* @throws `NoeticConfigError` with code `MISSING_HARNESS_CONTEXT` (at execution) without `ctx.harness`.
|
|
29
|
+
* @throws `NoeticConfigError` with code `UNKNOWN_WORKFLOW_REFERENCE` (at execution) if `ref` names no registered workflow.
|
|
30
|
+
*/
|
|
31
|
+
export function stepWorkflow(opts) {
|
|
32
|
+
if (!opts.id || opts.id.trim() === '') {
|
|
33
|
+
throw new NoeticConfigError({
|
|
34
|
+
code: 'EMPTY_STEP_ID',
|
|
35
|
+
message: 'step.workflow() requires a non-empty id.',
|
|
36
|
+
hint: 'Pass a unique string as the id field, e.g. step.workflow({ id: "verify", ... }).',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if ((opts.document === undefined) === (opts.ref === undefined)) {
|
|
40
|
+
throw new NoeticConfigError({
|
|
41
|
+
code: 'INVALID_WORKFLOW_SOURCE',
|
|
42
|
+
message: `step.workflow('${opts.id}') requires exactly one of 'document' (inline) or 'ref' (named).`,
|
|
43
|
+
hint: "Pass document: { version: 1, root: ... } or ref: '<name>' with a workflows registry.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// The hydrated tree bakes the harness's executeStep into every nested
|
|
47
|
+
// node's closure, so the cache is keyed on the harness: a step shared
|
|
48
|
+
// across sessions re-hydrates when it runs under a different harness
|
|
49
|
+
// instead of routing nested steps through the first one.
|
|
50
|
+
let cached;
|
|
51
|
+
return stepBuilders.step.run({
|
|
52
|
+
id: opts.id,
|
|
53
|
+
execute: async (input, ctx) => {
|
|
54
|
+
const harness = ctx.harness;
|
|
55
|
+
if (!harness) {
|
|
56
|
+
throw new NoeticConfigError({
|
|
57
|
+
code: 'MISSING_HARNESS_CONTEXT',
|
|
58
|
+
message: 'step.workflow requires a harness on the execution context.',
|
|
59
|
+
hint: 'Execute this step via AgentHarness.run() or ensure ctx.harness is set.',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const executeStep = frameworkCast(harness.run.bind(harness));
|
|
63
|
+
if (!cached || cached.harness !== harness) {
|
|
64
|
+
cached = {
|
|
65
|
+
harness,
|
|
66
|
+
step: hydrate(opts, executeStep),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return stringify(await executeStep(cached.step, input, ctx));
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** @public The step builder namespace: the base builders plus `workflow`. */
|
|
74
|
+
export const step = {
|
|
75
|
+
...stepBuilders.step,
|
|
76
|
+
workflow: stepWorkflow,
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region Helpers
|
|
80
|
+
function hydrate(opts, executeStep) {
|
|
81
|
+
const doc = opts.document ?? (opts.ref ? opts.workflows?.get(opts.ref) : undefined);
|
|
82
|
+
if (!doc) {
|
|
83
|
+
throw new NoeticConfigError({
|
|
84
|
+
code: 'UNKNOWN_WORKFLOW_REFERENCE',
|
|
85
|
+
message: `Workflow '${opts.ref}' referenced by step.workflow('${opts.id}') is not registered.`,
|
|
86
|
+
hint: `Pass named workflows via the workflows option. Available: ${[
|
|
87
|
+
...(opts.workflows?.keys() ?? []),
|
|
88
|
+
].join(', ') || '(none)'}.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const hydrationCtx = {
|
|
92
|
+
tools: new Map((opts.tools ?? []).map((t) => [
|
|
93
|
+
t.name,
|
|
94
|
+
t,
|
|
95
|
+
])),
|
|
96
|
+
executeStep,
|
|
97
|
+
layers: opts.layers,
|
|
98
|
+
workflows: opts.workflows,
|
|
99
|
+
subHarnesses: opts.subHarnesses,
|
|
100
|
+
uiLibraries: opts.uiLibraries,
|
|
101
|
+
resolveSubprocess: opts.resolveSubprocess,
|
|
102
|
+
subflowAncestry: opts.ref
|
|
103
|
+
? new Set([
|
|
104
|
+
opts.ref,
|
|
105
|
+
])
|
|
106
|
+
: undefined,
|
|
107
|
+
};
|
|
108
|
+
const hydrated = hydrateWorkflow(doc, hydrationCtx);
|
|
109
|
+
if (opts.isolation !== 'spawn') {
|
|
110
|
+
return hydrated;
|
|
111
|
+
}
|
|
112
|
+
return spawn({
|
|
113
|
+
id: `${opts.id}-spawn`,
|
|
114
|
+
child: hydrated,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function stringify(value) {
|
|
118
|
+
if (typeof value === 'string') {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
if (value === null || value === undefined) {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
return JSON.stringify(value);
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContextData, StorageAdapter } from '@noetic-tools/context';
|
|
2
2
|
import type { Context, Step } from '@noetic-tools/types';
|
|
3
3
|
import type { EmbedFn } from '../types/embed.js';
|
|
4
4
|
export type Condition<I> = (input: I, ctx: Context) => Promise<boolean>;
|
|
5
|
-
export interface WhenClause<
|
|
5
|
+
export interface WhenClause<TContext = ContextData, I = unknown, O = unknown> {
|
|
6
6
|
readonly kind: 'when';
|
|
7
7
|
readonly condition: Condition<I>;
|
|
8
|
-
readonly step: Step<
|
|
8
|
+
readonly step: Step<TContext, I, O>;
|
|
9
9
|
}
|
|
10
|
-
export interface OtherwiseClause<
|
|
10
|
+
export interface OtherwiseClause<TContext = ContextData, I = unknown, O = unknown> {
|
|
11
11
|
readonly kind: 'otherwise';
|
|
12
|
-
readonly step: Step<
|
|
12
|
+
readonly step: Step<TContext, I, O>;
|
|
13
13
|
}
|
|
14
|
-
type Clause<
|
|
14
|
+
type Clause<TContext, I, O> = WhenClause<TContext, I, O> | OtherwiseClause<TContext, I, O>;
|
|
15
15
|
/**
|
|
16
16
|
* Creates a conditional clause that routes to the given step when the condition is true.
|
|
17
17
|
*
|
|
@@ -20,7 +20,7 @@ type Clause<TMemory, I, O> = WhenClause<TMemory, I, O> | OtherwiseClause<TMemory
|
|
|
20
20
|
* @param step - Step to execute when the condition matches.
|
|
21
21
|
* @returns A `WhenClause` for use in `semanticRoute`.
|
|
22
22
|
*/
|
|
23
|
-
export declare function when<
|
|
23
|
+
export declare function when<TContext = ContextData, I = unknown, O = unknown>(condition: Condition<I>, step: Step<TContext, I, O>): WhenClause<TContext, I, O>;
|
|
24
24
|
/**
|
|
25
25
|
* Creates a fallback clause that always matches, used as the last clause in `semanticRoute`.
|
|
26
26
|
*
|
|
@@ -28,7 +28,7 @@ export declare function when<TMemory = ContextMemory, I = unknown, O = unknown>(
|
|
|
28
28
|
* @param step - Step to execute when no prior `when` clause matches.
|
|
29
29
|
* @returns An `OtherwiseClause` for use in `semanticRoute`.
|
|
30
30
|
*/
|
|
31
|
-
export declare function otherwise<
|
|
31
|
+
export declare function otherwise<TContext = ContextData, I = unknown, O = unknown>(step: Step<TContext, I, O>): OtherwiseClause<TContext, I, O>;
|
|
32
32
|
/**
|
|
33
33
|
* Builds a route function from an ordered list of `when`/`otherwise` clauses.
|
|
34
34
|
* Evaluates clauses sequentially, returning the first matching step or `null`.
|
|
@@ -37,21 +37,21 @@ export declare function otherwise<TMemory = ContextMemory, I = unknown, O = unkn
|
|
|
37
37
|
* @param clauses - Ordered `WhenClause` and optional trailing `OtherwiseClause`.
|
|
38
38
|
* @returns A route function suitable for `branch({ route })`.
|
|
39
39
|
*/
|
|
40
|
-
export declare function semanticRoute<
|
|
41
|
-
interface SemanticSwitchSimple<
|
|
40
|
+
export declare function semanticRoute<TContext = ContextData, I = unknown, O = unknown>(...clauses: Clause<TContext, I, O>[]): (input: I, ctx: Context) => Promise<Step<TContext, I, O> | null>;
|
|
41
|
+
interface SemanticSwitchSimple<TContext, I, O> {
|
|
42
42
|
embed: EmbedFn;
|
|
43
|
-
cases: Record<string, Step<
|
|
44
|
-
default?: Step<
|
|
43
|
+
cases: Record<string, Step<TContext, I, O>>;
|
|
44
|
+
default?: Step<TContext, I, O>;
|
|
45
45
|
threshold?: number;
|
|
46
46
|
cache?: StorageAdapter;
|
|
47
47
|
}
|
|
48
|
-
interface SemanticSwitchAdvanced<
|
|
48
|
+
interface SemanticSwitchAdvanced<TContext, I, O> {
|
|
49
49
|
embed: EmbedFn;
|
|
50
50
|
cases: {
|
|
51
51
|
labels: string | string[];
|
|
52
|
-
step: Step<
|
|
52
|
+
step: Step<TContext, I, O>;
|
|
53
53
|
}[];
|
|
54
|
-
default?: Step<
|
|
54
|
+
default?: Step<TContext, I, O>;
|
|
55
55
|
threshold?: number;
|
|
56
56
|
cache?: StorageAdapter;
|
|
57
57
|
}
|
|
@@ -62,9 +62,9 @@ interface SemanticSwitchAdvanced<TMemory, I, O> {
|
|
|
62
62
|
* @param opts - Simple form with `Record<string, Step>` cases, or advanced form with multi-label cases.
|
|
63
63
|
* @returns A route function suitable for `branch({ route })`.
|
|
64
64
|
*/
|
|
65
|
-
export declare function semanticSwitch<
|
|
65
|
+
export declare function semanticSwitch<TContext = ContextData, I = unknown, O = unknown>(opts: SemanticSwitchSimple<TContext, I, O>): (input: I, ctx: Context) => Promise<Step<TContext, I, O> | null>;
|
|
66
66
|
/** @public */
|
|
67
|
-
export declare function semanticSwitch<
|
|
67
|
+
export declare function semanticSwitch<TContext = ContextData, I = unknown, O = unknown>(opts: SemanticSwitchAdvanced<TContext, I, O>): (input: I, ctx: Context) => Promise<Step<TContext, I, O> | null>;
|
|
68
68
|
/**
|
|
69
69
|
* Creates a condition that matches when the input embedding is within a cosine similarity threshold of a label.
|
|
70
70
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { storageGetMany } from '@noetic-tools/
|
|
1
|
+
import { storageGetMany } from '@noetic-tools/context';
|
|
2
2
|
import { createMessage, extractAssistantText, trackUsage } from '@noetic-tools/types';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { cosineSimilarity } from './cosine-similarity.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated aliases from the "memory layer" era of the framework.
|
|
3
|
+
*
|
|
4
|
+
* The layer system assembles the model's *context window* — recall, budget
|
|
5
|
+
* allocation, history projection, the item-append pipeline — so it is named
|
|
6
|
+
* after context, not memory. Every name below still resolves to its
|
|
7
|
+
* replacement; nothing here changes behaviour.
|
|
8
|
+
*
|
|
9
|
+
* Most of these are plain pass-throughs: the alias and its replacement already
|
|
10
|
+
* live in `@noetic-tools/types` / `@noetic-tools/context`, and re-exporting
|
|
11
|
+
* carries each name's `@deprecated` pointer along with it. Only the renaming
|
|
12
|
+
* aliases — where core exports a symbol under a *different* name than its
|
|
13
|
+
* source package does — are declared here.
|
|
14
|
+
*
|
|
15
|
+
* The matching runtime aliases (the `memory:` config key on `provide`/`spawn`/
|
|
16
|
+
* `tool`/`react`/`AgentHarness`, and the `ctx.memory` accessor) are handled at
|
|
17
|
+
* their call sites — see `resolveContextOption`.
|
|
18
|
+
*
|
|
19
|
+
* Scheduled for removal in the next major of `@noetic-tools/core`.
|
|
20
|
+
*/
|
|
21
|
+
export type { ObservationalMemoryConfig, PlanMemoryConfig, TemporalMemoryConfig, WorkingMemoryConfig, WorkingMemoryState, } from '@noetic-tools/context';
|
|
22
|
+
export { observationalMemory, planMemory, temporalMemory, toolMemoryLayer, workingMemory, } from '@noetic-tools/context';
|
|
23
|
+
export type { ContextMemory, InferMemory, InferMemoryShape, MemoryCallModelRequest, MemoryConfig, MemoryHooks, MemoryLayer, MemoryScope, MemoryTraceSpan, ToolMemory, ToolMemoryDeclaration, } from '@noetic-tools/types';
|
|
24
|
+
export {
|
|
25
|
+
/**
|
|
26
|
+
* @public
|
|
27
|
+
* @deprecated Renamed to `context`. Declared here rather than re-exported
|
|
28
|
+
* because the builder itself lives in core under its new name.
|
|
29
|
+
*/
|
|
30
|
+
context as memory, } from './builders/context-builder.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated aliases from the "memory layer" era of the framework.
|
|
3
|
+
*
|
|
4
|
+
* The layer system assembles the model's *context window* — recall, budget
|
|
5
|
+
* allocation, history projection, the item-append pipeline — so it is named
|
|
6
|
+
* after context, not memory. Every name below still resolves to its
|
|
7
|
+
* replacement; nothing here changes behaviour.
|
|
8
|
+
*
|
|
9
|
+
* Most of these are plain pass-throughs: the alias and its replacement already
|
|
10
|
+
* live in `@noetic-tools/types` / `@noetic-tools/context`, and re-exporting
|
|
11
|
+
* carries each name's `@deprecated` pointer along with it. Only the renaming
|
|
12
|
+
* aliases — where core exports a symbol under a *different* name than its
|
|
13
|
+
* source package does — are declared here.
|
|
14
|
+
*
|
|
15
|
+
* The matching runtime aliases (the `memory:` config key on `provide`/`spawn`/
|
|
16
|
+
* `tool`/`react`/`AgentHarness`, and the `ctx.memory` accessor) are handled at
|
|
17
|
+
* their call sites — see `resolveContextOption`.
|
|
18
|
+
*
|
|
19
|
+
* Scheduled for removal in the next major of `@noetic-tools/core`.
|
|
20
|
+
*/
|
|
21
|
+
export { observationalMemory, planMemory, temporalMemory, toolMemoryLayer, workingMemory, } from '@noetic-tools/context';
|
|
22
|
+
export {
|
|
23
|
+
/**
|
|
24
|
+
* @public
|
|
25
|
+
* @deprecated Renamed to `context`. Declared here rather than re-exported
|
|
26
|
+
* because the builder itself lives in core under its new name.
|
|
27
|
+
*/
|
|
28
|
+
context as memory, } from './builders/context-builder.js';
|