@noetic-tools/core 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +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 +16 -7
- package/dist/adapters/openrouter.js +68 -7
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +3 -3
- package/dist/builders/control-flow-builders.js +9 -4
- package/dist/builders/every.d.ts +3 -3
- package/dist/builders/every.js +5 -2
- package/dist/builders/layer-provides-builders.d.ts +1 -32
- package/dist/builders/layer-provides-builders.js +1 -33
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +5 -2
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +5 -2
- package/dist/builders/spawn-builder.d.ts +22 -9
- package/dist/builders/spawn-builder.js +17 -3
- package/dist/builders/step-builders.d.ts +51 -30
- package/dist/builders/step-builders.js +31 -12
- package/dist/builders/tool-builder.d.ts +7 -3
- package/dist/builders/tool-builder.js +3 -1
- package/dist/builders/workflow-hydrator.d.ts +38 -0
- package/dist/builders/workflow-hydrator.js +336 -0
- package/dist/conditions/conditions.d.ts +4 -4
- package/dist/conditions/conditions.js +2 -2
- package/dist/errors/noetic-error.d.ts +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 +114 -76
- package/dist/index.js +74 -50
- package/dist/internal-test.d.ts +4 -4
- package/dist/internal-test.js +4 -4
- 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 +2 -2
- 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} +8 -2
- package/dist/interpreter/execute-control.js +530 -0
- package/dist/interpreter/execute.d.ts +16 -3
- package/dist/interpreter/execute.js +154 -51
- 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 +8 -15
- package/dist/interpreter/typeguards.js +4 -21
- package/dist/memory/budget.d.ts +2 -2
- package/dist/memory/budget.js +1 -1
- package/dist/{runtime → memory}/exec-context-factory.d.ts +2 -2
- 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.d.ts +1 -1
- package/dist/memory/function-call-utils.js +4 -2
- package/dist/memory/layer-api.d.ts +4 -4
- package/dist/memory/layer-api.js +2 -2
- package/dist/memory/layer-lifecycle.d.ts +6 -6
- package/dist/memory/layer-lifecycle.js +6 -6
- package/dist/memory/layer-provides.d.ts +32 -0
- package/dist/memory/layer-provides.js +33 -0
- package/dist/memory/layer-usage.d.ts +4 -3
- package/dist/memory/layer-usage.js +9 -3
- package/dist/memory/layers/durable-task-state.d.ts +8 -8
- package/dist/memory/layers/durable-task-state.js +2 -2
- package/dist/memory/layers/file-reference.d.ts +1 -1
- package/dist/memory/layers/file-reference.js +303 -247
- package/dist/memory/layers/history-window.d.ts +1 -1
- package/dist/memory/layers/history-window.js +4 -4
- package/dist/memory/layers/observational-memory.d.ts +5 -5
- package/dist/memory/layers/observational-memory.js +2 -3
- package/dist/memory/layers/plan.d.ts +2 -2
- package/dist/memory/layers/plan.js +15 -10
- package/dist/memory/layers/static-content.d.ts +2 -2
- package/dist/memory/layers/static-content.js +1 -1
- package/dist/memory/layers/steering.d.ts +7 -7
- package/dist/memory/layers/steering.js +5 -5
- package/dist/memory/layers/tool-memory-layer.d.ts +2 -2
- package/dist/memory/layers/tool-memory-layer.js +1 -1
- package/dist/memory/layers/working-memory.d.ts +7 -7
- package/dist/memory/layers/working-memory.js +4 -4
- package/dist/memory/projector.d.ts +2 -2
- package/dist/memory/scope.d.ts +1 -1
- package/dist/memory/strip-unresolved.d.ts +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +2 -2
- package/dist/observability/trace-exporter.js +1 -1
- 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.d.ts +2 -2
- package/dist/patterns/interview.js +5 -5
- package/dist/patterns/plans.d.ts +6 -6
- package/dist/patterns/plans.js +4 -4
- package/dist/patterns/ralph-wiggum.d.ts +4 -4
- package/dist/patterns/ralph-wiggum.js +5 -5
- package/dist/patterns/react.d.ts +3 -3
- package/dist/patterns/react.js +5 -5
- 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 +7 -3
- package/dist/runtime/broadcaster-utils.js +11 -6
- package/dist/runtime/channel-store.d.ts +1 -1
- package/dist/runtime/channel-store.js +2 -2
- 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 +52 -5
- package/dist/runtime/context-type-deps.d.ts +14 -0
- package/dist/runtime/context-type-deps.js +1 -0
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +52 -4
- package/dist/runtime/detached-handle.js +218 -14
- 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/event-broadcaster.d.ts +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +3 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +2 -2
- package/dist/runtime/session-runner.d.ts +7 -7
- package/dist/runtime/session-runner.js +3 -3
- package/dist/runtime/session-streams.d.ts +3 -3
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +42 -0
- package/dist/runtime/step-registry.js +77 -0
- package/dist/runtime/tool-memory.d.ts +3 -3
- package/dist/schemas/item.d.ts +1 -1
- 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 +140 -31
- package/dist/types/error.d.ts +15 -0
- package/dist/types/harness-result.d.ts +2 -2
- 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 +38 -68
- package/dist/types/mutable-context.d.ts +3 -3
- package/dist/types/runtime.d.ts +14 -13
- package/dist/types/steering.d.ts +2 -2
- package/dist/types/step-registrar.d.ts +46 -0
- package/dist/types/step-registrar.js +56 -0
- package/dist/types/step.d.ts +34 -9
- package/dist/types/subprocess-adapter.d.ts +119 -3
- package/dist/types/subprocess-adapter.js +2 -0
- package/dist/types/tool-context.d.ts +6 -6
- package/dist/types/tool.d.ts +64 -0
- package/dist/types/tool.js +1 -0
- package/dist/unstable.d.ts +9 -9
- package/dist/unstable.js +7 -7
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +13 -4
- package/dist/until/predicates.js +16 -2
- package/dist/util/message-helpers.d.ts +17 -0
- package/dist/util/message-helpers.js +64 -0
- package/package.json +6 -4
- 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/conditions/index.d.ts +0 -3
- package/dist/conditions/index.js +0 -2
- 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/{interpreter → util}/framework-cast.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.js +0 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-flow step handlers: branch, fork, loop, every.
|
|
3
|
+
*/
|
|
4
|
+
import { isNoeticError, NoeticErrorImpl } from '../errors/noetic-error.js';
|
|
5
|
+
import { ContextImpl } from '../runtime/context-impl.js';
|
|
6
|
+
import { snapshotCwdState } from '../runtime/cwd-helpers.js';
|
|
7
|
+
import { frameworkCast } from '../util/framework-cast.js';
|
|
8
|
+
import { createMessage } from '../util/message-helpers.js';
|
|
9
|
+
import { cloneWithGuard } from './clone-guard.js';
|
|
10
|
+
import { getContextChannelStore, isContextImpl, isMutableContext } from './typeguards.js';
|
|
11
|
+
//#region branch
|
|
12
|
+
export async function executeBranch(step, input, ctx, executeStep) {
|
|
13
|
+
const selected = await step.route(input, ctx);
|
|
14
|
+
if (selected === null) {
|
|
15
|
+
// Requires I assignable to O for null route — when no branch is selected,
|
|
16
|
+
// the input passes through. Callers must ensure I is compatible with O.
|
|
17
|
+
return frameworkCast(input);
|
|
18
|
+
}
|
|
19
|
+
return executeStep(selected, input, ctx);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region fork
|
|
23
|
+
function createChildContexts(ctx, count, stepId) {
|
|
24
|
+
const threadId = isContextImpl(ctx) ? ctx.threadId : crypto.randomUUID();
|
|
25
|
+
const resourceId = isContextImpl(ctx) ? ctx.resourceId : undefined;
|
|
26
|
+
const channelStore = getContextChannelStore(ctx);
|
|
27
|
+
return Array.from({
|
|
28
|
+
length: count,
|
|
29
|
+
}, () => new ContextImpl({
|
|
30
|
+
harness: ctx.harness,
|
|
31
|
+
parent: ctx,
|
|
32
|
+
items: [
|
|
33
|
+
...ctx.itemLog.items,
|
|
34
|
+
],
|
|
35
|
+
state: cloneWithGuard(ctx.state, `Fork '${stepId}'`),
|
|
36
|
+
threadId,
|
|
37
|
+
resourceId,
|
|
38
|
+
channelStore,
|
|
39
|
+
cwdState: snapshotCwdState(ctx),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
export async function executeFork(step, input, ctx, executeStep) {
|
|
43
|
+
const baseCtx = frameworkCast(ctx);
|
|
44
|
+
const paths = step.paths(input, ctx);
|
|
45
|
+
if (paths.length === 0) {
|
|
46
|
+
if (step.mode === 'all') {
|
|
47
|
+
return step.merge([], ctx);
|
|
48
|
+
}
|
|
49
|
+
if (step.mode === 'settle') {
|
|
50
|
+
return step.merge([], ctx);
|
|
51
|
+
}
|
|
52
|
+
throw new NoeticErrorImpl({
|
|
53
|
+
kind: 'fork_partial',
|
|
54
|
+
stepId: step.id,
|
|
55
|
+
succeeded: [],
|
|
56
|
+
failed: [],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const childContexts = createChildContexts(baseCtx, paths.length, step.id);
|
|
60
|
+
const concurrency = step.concurrency ?? paths.length;
|
|
61
|
+
switch (step.mode) {
|
|
62
|
+
case 'all':
|
|
63
|
+
return executeAll(step, paths, input, ctx, childContexts, executeStep, concurrency);
|
|
64
|
+
case 'race':
|
|
65
|
+
return executeRace(step, paths, input, ctx, childContexts, executeStep, concurrency);
|
|
66
|
+
case 'settle':
|
|
67
|
+
return executeSettle(step, paths, input, ctx, childContexts, executeStep, concurrency);
|
|
68
|
+
default: {
|
|
69
|
+
const _exhaustive = step;
|
|
70
|
+
throw new NoeticErrorImpl({
|
|
71
|
+
kind: 'step_failed',
|
|
72
|
+
stepId: 'unknown',
|
|
73
|
+
cause: new Error('Unknown fork mode'),
|
|
74
|
+
retriesExhausted: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function runWithConcurrency(tasks, concurrency) {
|
|
80
|
+
const results = new Array(tasks.length);
|
|
81
|
+
let nextIndex = 0;
|
|
82
|
+
async function runNext() {
|
|
83
|
+
while (nextIndex < tasks.length) {
|
|
84
|
+
const index = nextIndex++;
|
|
85
|
+
try {
|
|
86
|
+
const value = await tasks[index]();
|
|
87
|
+
results[index] = {
|
|
88
|
+
status: 'fulfilled',
|
|
89
|
+
value,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (reason) {
|
|
93
|
+
results[index] = {
|
|
94
|
+
status: 'rejected',
|
|
95
|
+
reason,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const workers = Array.from({
|
|
101
|
+
length: Math.min(concurrency, tasks.length),
|
|
102
|
+
}, () => runNext());
|
|
103
|
+
await Promise.all(workers);
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
function toNoeticError(err, stepId) {
|
|
107
|
+
if (isNoeticError(err)) {
|
|
108
|
+
return err.noeticError;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
kind: 'step_failed',
|
|
112
|
+
stepId,
|
|
113
|
+
cause: err instanceof Error ? err : new Error(String(err)),
|
|
114
|
+
retriesExhausted: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function classifyResults(settled, paths) {
|
|
118
|
+
const succeeded = [];
|
|
119
|
+
const failed = [];
|
|
120
|
+
settled.forEach((result, i) => {
|
|
121
|
+
if (result.status === 'fulfilled') {
|
|
122
|
+
succeeded.push({
|
|
123
|
+
stepId: paths[i].id,
|
|
124
|
+
value: result.value,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
failed.push({
|
|
129
|
+
stepId: paths[i].id,
|
|
130
|
+
error: toNoeticError(result.reason, paths[i].id),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
succeeded,
|
|
136
|
+
failed,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function executeAll(step, paths, _input, ctx, childContexts, executeStep, concurrency) {
|
|
140
|
+
const tasks = paths.map((path, i) => () => executeStep(path, _input, frameworkCast(childContexts[i])));
|
|
141
|
+
const settled = await runWithConcurrency(tasks, concurrency);
|
|
142
|
+
const { succeeded, failed } = classifyResults(settled, paths);
|
|
143
|
+
if (failed.length > 0) {
|
|
144
|
+
throw new NoeticErrorImpl({
|
|
145
|
+
kind: 'fork_partial',
|
|
146
|
+
stepId: step.id,
|
|
147
|
+
succeeded,
|
|
148
|
+
failed,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const results = succeeded.map((s) => s.value);
|
|
152
|
+
return step.merge(results, ctx);
|
|
153
|
+
}
|
|
154
|
+
async function executeRace(step, paths, input, ctx, childContexts, executeStep, concurrency) {
|
|
155
|
+
const raceBaseCtx = frameworkCast(ctx);
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
let settled = false;
|
|
158
|
+
let failedCount = 0;
|
|
159
|
+
let nextIndex = 0;
|
|
160
|
+
const errors = [];
|
|
161
|
+
const totalPaths = paths.length;
|
|
162
|
+
function startNext() {
|
|
163
|
+
if (settled || nextIndex >= totalPaths) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const i = nextIndex++;
|
|
167
|
+
executeStep(paths[i], input, frameworkCast(childContexts[i]))
|
|
168
|
+
.then((result) => {
|
|
169
|
+
if (settled) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
settled = true;
|
|
173
|
+
// Winner's state replaces parent's
|
|
174
|
+
if (isContextImpl(raceBaseCtx)) {
|
|
175
|
+
raceBaseCtx.state = childContexts[i].state;
|
|
176
|
+
}
|
|
177
|
+
// Resolve first, then abort losers (non-critical path)
|
|
178
|
+
resolve(result);
|
|
179
|
+
for (let j = 0; j < childContexts.length; j++) {
|
|
180
|
+
if (j !== i) {
|
|
181
|
+
childContexts[j].abort('race lost');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
.catch((err) => {
|
|
186
|
+
failedCount++;
|
|
187
|
+
errors.push({
|
|
188
|
+
stepId: paths[i].id,
|
|
189
|
+
error: toNoeticError(err, paths[i].id),
|
|
190
|
+
});
|
|
191
|
+
// Start next task if available
|
|
192
|
+
if (!settled) {
|
|
193
|
+
startNext();
|
|
194
|
+
}
|
|
195
|
+
if (failedCount === totalPaths && !settled) {
|
|
196
|
+
settled = true;
|
|
197
|
+
reject(new NoeticErrorImpl({
|
|
198
|
+
kind: 'fork_partial',
|
|
199
|
+
stepId: step.id,
|
|
200
|
+
succeeded: [],
|
|
201
|
+
failed: errors,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Launch initial batch respecting concurrency
|
|
207
|
+
const initialBatch = Math.min(concurrency, totalPaths);
|
|
208
|
+
for (let i = 0; i < initialBatch; i++) {
|
|
209
|
+
startNext();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function executeSettle(step, paths, input, _ctx, childContexts, executeStep, concurrency) {
|
|
214
|
+
const tasks = paths.map((path, i) => () => executeStep(path, input, frameworkCast(childContexts[i])));
|
|
215
|
+
const settled = await runWithConcurrency(tasks, concurrency);
|
|
216
|
+
const results = settled.map((result, i) => {
|
|
217
|
+
if (result.status === 'fulfilled') {
|
|
218
|
+
return {
|
|
219
|
+
stepId: paths[i].id,
|
|
220
|
+
status: 'fulfilled',
|
|
221
|
+
value: result.value,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
stepId: paths[i].id,
|
|
226
|
+
status: 'rejected',
|
|
227
|
+
error: toNoeticError(result.reason, paths[i].id),
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
return step.merge(results, _ctx);
|
|
231
|
+
}
|
|
232
|
+
function hasTextField(value) {
|
|
233
|
+
return typeof value === 'object' && value !== null && 'text' in value;
|
|
234
|
+
}
|
|
235
|
+
async function recvInboxWithTimeout(ctx, step) {
|
|
236
|
+
if (!step.inbox) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
if ((step.parkTimeout ?? 0) <= 0) {
|
|
240
|
+
return ctx.tryRecv(step.inbox);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return await ctx.recv(step.inbox, {
|
|
244
|
+
timeout: step.parkTimeout,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Expected: channel_timeout error when parkTimeout expires with no message.
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function prepareNextInput(step, lastOutput, verdict, ctx) {
|
|
253
|
+
if (step.prepareNext) {
|
|
254
|
+
return step.prepareNext(lastOutput, verdict, ctx);
|
|
255
|
+
}
|
|
256
|
+
// Requires I === O when prepareNext is omitted — the loop feeds output
|
|
257
|
+
// back as input. Callers must ensure I and O are compatible types.
|
|
258
|
+
return frameworkCast(lastOutput);
|
|
259
|
+
}
|
|
260
|
+
export async function executeLoop(step, input, ctx, executeStep) {
|
|
261
|
+
const baseCtx = frameworkCast(ctx);
|
|
262
|
+
let currentInput = input;
|
|
263
|
+
let lastOutput;
|
|
264
|
+
let lastText = '';
|
|
265
|
+
const history = [];
|
|
266
|
+
const startTime = Date.now();
|
|
267
|
+
let stepCount = 0;
|
|
268
|
+
const maxIterations = step.maxIterations ?? 1e3;
|
|
269
|
+
const maxHistory = step.maxHistorySize ?? 100;
|
|
270
|
+
let totalIterations = 0;
|
|
271
|
+
// Validate maxIterations
|
|
272
|
+
if (!Number.isFinite(maxIterations) || maxIterations < 1) {
|
|
273
|
+
throw new NoeticErrorImpl({
|
|
274
|
+
kind: 'step_failed',
|
|
275
|
+
stepId: step.id,
|
|
276
|
+
cause: new Error(`Invalid maxIterations: ${step.maxIterations}`),
|
|
277
|
+
retriesExhausted: false,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
while (true) {
|
|
281
|
+
// Abort check at top of each iteration
|
|
282
|
+
if (ctx.aborted) {
|
|
283
|
+
throw new NoeticErrorImpl({
|
|
284
|
+
kind: 'cancelled',
|
|
285
|
+
reason: ctx.abortReason ?? 'context aborted',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// Enforce hard iteration ceiling (includes retries)
|
|
289
|
+
totalIterations++;
|
|
290
|
+
if (totalIterations > maxIterations) {
|
|
291
|
+
throw new NoeticErrorImpl({
|
|
292
|
+
kind: 'step_failed',
|
|
293
|
+
stepId: step.id,
|
|
294
|
+
cause: new Error(`Loop exceeded maximum iterations (${maxIterations})`),
|
|
295
|
+
retriesExhausted: false,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
// Execute body steps sequentially
|
|
299
|
+
let output;
|
|
300
|
+
try {
|
|
301
|
+
let stepOutput = currentInput;
|
|
302
|
+
for (const bodyStep of step.steps) {
|
|
303
|
+
stepOutput = await executeStep(bodyStep, frameworkCast(stepOutput), ctx);
|
|
304
|
+
}
|
|
305
|
+
output = frameworkCast(stepOutput);
|
|
306
|
+
stepCount++;
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
if (!step.onError || !isNoeticError(e)) {
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
const action = step.onError(e.noeticError, ctx);
|
|
313
|
+
if (action === 'retry') {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (action !== 'skip') {
|
|
317
|
+
throw e;
|
|
318
|
+
}
|
|
319
|
+
// Skip does not increment stepCount — it is not a successful execution.
|
|
320
|
+
if (lastOutput === undefined) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
output = lastOutput;
|
|
324
|
+
}
|
|
325
|
+
lastOutput = output;
|
|
326
|
+
history.push(output);
|
|
327
|
+
// Trim history if it exceeds maxHistorySize
|
|
328
|
+
if (history.length > maxHistory) {
|
|
329
|
+
history.splice(0, history.length - maxHistory);
|
|
330
|
+
}
|
|
331
|
+
// Extract text from output for snapshot
|
|
332
|
+
if (typeof output === 'string') {
|
|
333
|
+
lastText = output;
|
|
334
|
+
}
|
|
335
|
+
else if (hasTextField(output)) {
|
|
336
|
+
lastText = String(output.text);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
lastText = output === undefined ? '' : JSON.stringify(output);
|
|
340
|
+
}
|
|
341
|
+
// Build snapshot
|
|
342
|
+
const snapshot = {
|
|
343
|
+
stepCount,
|
|
344
|
+
tokens: {
|
|
345
|
+
...ctx.tokens,
|
|
346
|
+
},
|
|
347
|
+
elapsed: Date.now() - startTime,
|
|
348
|
+
cost: ctx.cost,
|
|
349
|
+
lastOutput: output,
|
|
350
|
+
lastText,
|
|
351
|
+
history: [
|
|
352
|
+
...history,
|
|
353
|
+
],
|
|
354
|
+
depth: ctx.depth,
|
|
355
|
+
lastStepMeta: isMutableContext(baseCtx) ? baseCtx.lastStepMeta : null,
|
|
356
|
+
};
|
|
357
|
+
// Evaluate until predicate
|
|
358
|
+
let verdict;
|
|
359
|
+
try {
|
|
360
|
+
verdict = await step.until(snapshot);
|
|
361
|
+
}
|
|
362
|
+
catch (predicateError) {
|
|
363
|
+
// Per spec: if until predicate throws, treat as stop
|
|
364
|
+
verdict = {
|
|
365
|
+
stop: true,
|
|
366
|
+
reason: `Predicate error: ${predicateError instanceof Error ? predicateError.message : String(predicateError)}`,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (verdict.stop) {
|
|
370
|
+
if (lastOutput === undefined) {
|
|
371
|
+
throw new NoeticErrorImpl({
|
|
372
|
+
kind: 'step_failed',
|
|
373
|
+
stepId: step.id,
|
|
374
|
+
cause: new Error('Loop completed with no successful output'),
|
|
375
|
+
retriesExhausted: false,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
// Check inbox before truly stopping
|
|
379
|
+
if (step.inbox) {
|
|
380
|
+
const inboxMessage = await recvInboxWithTimeout(baseCtx, step);
|
|
381
|
+
if (inboxMessage !== null) {
|
|
382
|
+
baseCtx.itemLog.append(createMessage(inboxMessage, 'developer'));
|
|
383
|
+
// Continue the loop — don't stop
|
|
384
|
+
currentInput = prepareNextInput(step, lastOutput, verdict, ctx);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return lastOutput;
|
|
389
|
+
}
|
|
390
|
+
// Prepare input for next iteration
|
|
391
|
+
currentInput = prepareNextInput(step, output, verdict, ctx);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** Returns the next park duration in ms, applying random jitter clamped to `[ms - jitter, ms + jitter]`. */
|
|
395
|
+
function nextParkMs(ms, jitter) {
|
|
396
|
+
if (jitter <= 0) {
|
|
397
|
+
return ms;
|
|
398
|
+
}
|
|
399
|
+
// Math.random() returns [0, 1); shift to [-1, 1) so jitter is symmetric.
|
|
400
|
+
const offset = (Math.random() * 2 - 1) * jitter;
|
|
401
|
+
const value = ms + offset;
|
|
402
|
+
if (value < 0) {
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
return value;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Parks for `ms` ms, returning early if `wakeOn` receives a message or the
|
|
409
|
+
* context is aborted. Always resolves; never throws (cancellation surfaces on
|
|
410
|
+
* the next iteration's abort check).
|
|
411
|
+
*/
|
|
412
|
+
function park(parkCtx) {
|
|
413
|
+
const duration = nextParkMs(parkCtx.ms, parkCtx.jitter);
|
|
414
|
+
return new Promise((resolve) => {
|
|
415
|
+
let settled = false;
|
|
416
|
+
let timer = null;
|
|
417
|
+
let abortPoll = null;
|
|
418
|
+
let wakeUnsub = null;
|
|
419
|
+
const settle = () => {
|
|
420
|
+
if (settled) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
settled = true;
|
|
424
|
+
if (timer !== null) {
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
}
|
|
427
|
+
if (abortPoll !== null) {
|
|
428
|
+
clearInterval(abortPoll);
|
|
429
|
+
}
|
|
430
|
+
if (wakeUnsub !== null) {
|
|
431
|
+
wakeUnsub();
|
|
432
|
+
}
|
|
433
|
+
resolve();
|
|
434
|
+
};
|
|
435
|
+
timer = setTimeout(settle, duration);
|
|
436
|
+
// Poll the abort flag at a coarse interval to wake parking when the
|
|
437
|
+
// context is aborted. The Context interface exposes only `aborted` as a
|
|
438
|
+
// boolean, so this is the simplest interruption strategy that respects
|
|
439
|
+
// the public contract.
|
|
440
|
+
abortPoll = setInterval(() => {
|
|
441
|
+
if (parkCtx.ctx.aborted) {
|
|
442
|
+
settle();
|
|
443
|
+
}
|
|
444
|
+
}, 5);
|
|
445
|
+
if (!parkCtx.wakeOn) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const { channelStore } = parkCtx;
|
|
449
|
+
if (channelStore) {
|
|
450
|
+
// Non-consuming subscription so the body still observes the message that
|
|
451
|
+
// woke us — `recv()` would dequeue queue-mode messages and leave the
|
|
452
|
+
// body's drain loop empty.
|
|
453
|
+
wakeUnsub = channelStore.subscribeWake(parkCtx.wakeOn, settle);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// Contexts without a ContextImpl/channel store have no body draining the
|
|
457
|
+
// channel anyway; the destructive `recv` is acceptable in that edge case.
|
|
458
|
+
parkCtx.ctx
|
|
459
|
+
.recv(parkCtx.wakeOn, {
|
|
460
|
+
timeout: Math.max(duration, 1) + 1e2,
|
|
461
|
+
})
|
|
462
|
+
.then(settle)
|
|
463
|
+
.catch(() => {
|
|
464
|
+
// Channel timeout / store error — another path settles us.
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
function recordIterationError(ctx, error) {
|
|
469
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
470
|
+
const stack = error instanceof Error && error.stack ? error.stack : '';
|
|
471
|
+
ctx.span.addEvent('every.iteration.error', {
|
|
472
|
+
message,
|
|
473
|
+
stack,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function throwCancelled(reason) {
|
|
477
|
+
throw new NoeticErrorImpl({
|
|
478
|
+
kind: 'cancelled',
|
|
479
|
+
reason: reason ?? 'context aborted',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Executes an `every` step: runs the body step forever, paced by `ms ± jitter`,
|
|
484
|
+
* woken early by `wakeOn`. Throws `cancelled` when the context is aborted.
|
|
485
|
+
*
|
|
486
|
+
* On body throw, behavior depends on `onError`:
|
|
487
|
+
* - `'continue'` (default): emit `every.iteration.error` span event and proceed
|
|
488
|
+
* to the park step as if no error occurred.
|
|
489
|
+
* - `'fail'`: re-throw, terminating the operator.
|
|
490
|
+
*
|
|
491
|
+
* The body's output is discarded — `every` runs forever and does not accumulate
|
|
492
|
+
* iteration outputs. Only ever returns by throwing on cancellation or `fail`.
|
|
493
|
+
*/
|
|
494
|
+
export async function executeEvery(step, input, ctx, executeStep) {
|
|
495
|
+
const onError = step.onError ?? 'continue';
|
|
496
|
+
const jitter = step.jitter ?? 0;
|
|
497
|
+
// Resolve once — the channel store reference is stable for the lifetime
|
|
498
|
+
// of the every loop, no need to re-derive it per park.
|
|
499
|
+
const channelStore = getContextChannelStore(ctx);
|
|
500
|
+
while (true) {
|
|
501
|
+
if (ctx.aborted) {
|
|
502
|
+
throwCancelled(ctx.abortReason);
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await executeStep(step.step, input, ctx);
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
if (onError === 'fail') {
|
|
509
|
+
throw e;
|
|
510
|
+
}
|
|
511
|
+
// 'continue' policy — but a cancellation should still terminate the
|
|
512
|
+
// operator promptly rather than be swallowed and re-parked.
|
|
513
|
+
if (isNoeticError(e) && e.noeticError.kind === 'cancelled') {
|
|
514
|
+
throw e;
|
|
515
|
+
}
|
|
516
|
+
recordIterationError(ctx, e);
|
|
517
|
+
}
|
|
518
|
+
if (ctx.aborted) {
|
|
519
|
+
throwCancelled(ctx.abortReason);
|
|
520
|
+
}
|
|
521
|
+
await park({
|
|
522
|
+
ms: step.ms,
|
|
523
|
+
jitter,
|
|
524
|
+
wakeOn: step.wakeOn,
|
|
525
|
+
ctx,
|
|
526
|
+
channelStore,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
//#endregion
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import type { Context } from '../types/context';
|
|
2
|
-
import type { ContextMemory } from '../types/memory';
|
|
3
|
-
import type { Step } from '../types/step';
|
|
1
|
+
import type { Context } from '../types/context.js';
|
|
2
|
+
import type { ContextMemory } from '../types/memory.js';
|
|
3
|
+
import type { Step } from '../types/step.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
* Dispatch a step at the per-kind handler level without adapter routing.
|
|
7
|
+
*
|
|
8
|
+
* Used as the top-level `_localExecutor` for `StepRun` / `StepSpawn`
|
|
9
|
+
* dispatches: the outer adapter call already recorded the request, so
|
|
10
|
+
* running the handler directly avoids a re-entrant adapter round-trip
|
|
11
|
+
* inside the same logical step. Nested step dispatches (e.g. `executeSpawn`
|
|
12
|
+
* descending into `step.child`) still go through `execute()` and route
|
|
13
|
+
* through the adapter as normal, so per-step overrides on descendants
|
|
14
|
+
* continue to work.
|
|
15
|
+
*/
|
|
16
|
+
export declare function executeNoAdapter<TMemory, I, O>(step: Step<TMemory, I, O>, input: I, ctx: Context<TMemory>): Promise<O>;
|
|
4
17
|
/**
|
|
5
18
|
* Executes a step within the interpreter, dispatching to the appropriate handler by step kind.
|
|
6
19
|
*
|