@oni.bot/core 1.0.2 → 1.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/CHANGELOG.md +146 -146
- package/dist/agents/define-agent.d.ts.map +1 -1
- package/dist/agents/define-agent.js +7 -2
- package/dist/agents/define-agent.js.map +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +7 -2
- package/dist/checkpoint.js.map +1 -1
- package/dist/checkpointers/postgres.d.ts.map +1 -1
- package/dist/checkpointers/postgres.js +45 -28
- package/dist/checkpointers/postgres.js.map +1 -1
- package/dist/circuit-breaker.d.ts +1 -0
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +13 -0
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +0 -1
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +4 -2
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +0 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +12 -4
- package/dist/config/loader.js.map +1 -1
- package/dist/coordination/pubsub.d.ts +1 -0
- package/dist/coordination/pubsub.d.ts.map +1 -1
- package/dist/coordination/pubsub.js +31 -16
- package/dist/coordination/pubsub.js.map +1 -1
- package/dist/coordination/request-reply.d.ts +17 -2
- package/dist/coordination/request-reply.d.ts.map +1 -1
- package/dist/coordination/request-reply.js +56 -14
- package/dist/coordination/request-reply.js.map +1 -1
- package/dist/events/bus.d.ts +1 -0
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +17 -10
- package/dist/events/bus.js.map +1 -1
- package/dist/functional.d.ts.map +1 -1
- package/dist/functional.js +3 -0
- package/dist/functional.js.map +1 -1
- package/dist/graph.d.ts +11 -1
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +9 -4
- package/dist/graph.js.map +1 -1
- package/dist/guardrails/audit.d.ts +4 -1
- package/dist/guardrails/audit.d.ts.map +1 -1
- package/dist/guardrails/audit.js +18 -1
- package/dist/guardrails/audit.js.map +1 -1
- package/dist/harness/agent-loop.d.ts +1 -7
- package/dist/harness/agent-loop.d.ts.map +1 -1
- package/dist/harness/agent-loop.js +2 -523
- package/dist/harness/agent-loop.js.map +1 -1
- package/dist/harness/context-compactor.d.ts +1 -0
- package/dist/harness/context-compactor.d.ts.map +1 -1
- package/dist/harness/context-compactor.js +43 -1
- package/dist/harness/context-compactor.js.map +1 -1
- package/dist/harness/harness.d.ts +6 -0
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +32 -5
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/hooks-engine.d.ts.map +1 -1
- package/dist/harness/hooks-engine.js +12 -10
- package/dist/harness/hooks-engine.js.map +1 -1
- package/dist/harness/index.d.ts +3 -1
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +2 -0
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/loop/hooks.d.ts +7 -0
- package/dist/harness/loop/hooks.d.ts.map +1 -0
- package/dist/harness/loop/hooks.js +46 -0
- package/dist/harness/loop/hooks.js.map +1 -0
- package/dist/harness/loop/index.d.ts +8 -0
- package/dist/harness/loop/index.d.ts.map +1 -0
- package/dist/harness/loop/index.js +257 -0
- package/dist/harness/loop/index.js.map +1 -0
- package/dist/harness/loop/inference.d.ts +19 -0
- package/dist/harness/loop/inference.d.ts.map +1 -0
- package/dist/harness/loop/inference.js +121 -0
- package/dist/harness/loop/inference.js.map +1 -0
- package/dist/harness/loop/memory.d.ts +22 -0
- package/dist/harness/loop/memory.d.ts.map +1 -0
- package/dist/harness/loop/memory.js +73 -0
- package/dist/harness/loop/memory.js.map +1 -0
- package/dist/harness/loop/safety.d.ts +8 -0
- package/dist/harness/loop/safety.d.ts.map +1 -0
- package/dist/harness/loop/safety.js +21 -0
- package/dist/harness/loop/safety.js.map +1 -0
- package/dist/harness/loop/tools.d.ts +24 -0
- package/dist/harness/loop/tools.d.ts.map +1 -0
- package/dist/harness/loop/tools.js +184 -0
- package/dist/harness/loop/tools.js.map +1 -0
- package/dist/harness/loop/types.d.ts +7 -0
- package/dist/harness/loop/types.d.ts.map +1 -0
- package/dist/harness/loop/types.js +9 -0
- package/dist/harness/loop/types.js.map +1 -0
- package/dist/harness/memory/fs-compat.d.ts +3 -0
- package/dist/harness/memory/fs-compat.d.ts.map +1 -0
- package/dist/harness/memory/fs-compat.js +26 -0
- package/dist/harness/memory/fs-compat.js.map +1 -0
- package/dist/harness/memory/index.d.ts +105 -0
- package/dist/harness/memory/index.d.ts.map +1 -0
- package/dist/harness/memory/index.js +491 -0
- package/dist/harness/memory/index.js.map +1 -0
- package/dist/harness/memory/prompter.d.ts +7 -0
- package/dist/harness/memory/prompter.d.ts.map +1 -0
- package/dist/harness/memory/prompter.js +24 -0
- package/dist/harness/memory/prompter.js.map +1 -0
- package/dist/harness/memory/ranker.d.ts +15 -0
- package/dist/harness/memory/ranker.d.ts.map +1 -0
- package/dist/harness/memory/ranker.js +72 -0
- package/dist/harness/memory/ranker.js.map +1 -0
- package/dist/harness/memory/scanner.d.ts +26 -0
- package/dist/harness/memory/scanner.d.ts.map +1 -0
- package/dist/harness/memory/scanner.js +187 -0
- package/dist/harness/memory/scanner.js.map +1 -0
- package/dist/harness/memory/types.d.ts +50 -0
- package/dist/harness/memory/types.d.ts.map +1 -0
- package/dist/harness/memory/types.js +7 -0
- package/dist/harness/memory/types.js.map +1 -0
- package/dist/harness/memory-loader.d.ts +3 -0
- package/dist/harness/memory-loader.d.ts.map +1 -0
- package/dist/harness/memory-loader.js +2 -0
- package/dist/harness/memory-loader.js.map +1 -0
- package/dist/harness/safety-gate.d.ts.map +1 -1
- package/dist/harness/safety-gate.js +47 -26
- package/dist/harness/safety-gate.js.map +1 -1
- package/dist/harness/skill-loader.d.ts +7 -0
- package/dist/harness/skill-loader.d.ts.map +1 -1
- package/dist/harness/skill-loader.js +24 -8
- package/dist/harness/skill-loader.js.map +1 -1
- package/dist/harness/todo-module.d.ts.map +1 -1
- package/dist/harness/todo-module.js +13 -6
- package/dist/harness/todo-module.js.map +1 -1
- package/dist/harness/types.d.ts +7 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/validate-args.js +18 -3
- package/dist/harness/validate-args.js.map +1 -1
- package/dist/hitl/interrupt.d.ts +2 -2
- package/dist/hitl/interrupt.d.ts.map +1 -1
- package/dist/hitl/interrupt.js +8 -5
- package/dist/hitl/interrupt.js.map +1 -1
- package/dist/hitl/resume.d.ts +10 -0
- package/dist/hitl/resume.d.ts.map +1 -1
- package/dist/hitl/resume.js +31 -0
- package/dist/hitl/resume.js.map +1 -1
- package/dist/injected.d.ts.map +1 -1
- package/dist/injected.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +28 -8
- package/dist/inspect.js.map +1 -1
- package/dist/lsp/client.d.ts +2 -0
- package/dist/lsp/client.d.ts.map +1 -1
- package/dist/lsp/client.js +62 -17
- package/dist/lsp/client.js.map +1 -1
- package/dist/lsp/index.d.ts.map +1 -1
- package/dist/lsp/index.js.map +1 -1
- package/dist/mcp/client.d.ts +2 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +44 -13
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/convert.js +1 -1
- package/dist/mcp/convert.js.map +1 -1
- package/dist/mcp/transport.d.ts +2 -0
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +33 -8
- package/dist/mcp/transport.js.map +1 -1
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/index.js +7 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +25 -15
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/google.d.ts.map +1 -1
- package/dist/models/google.js +23 -7
- package/dist/models/google.js.map +1 -1
- package/dist/models/ollama.d.ts.map +1 -1
- package/dist/models/ollama.js +11 -1
- package/dist/models/ollama.js.map +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +15 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/openrouter.d.ts.map +1 -1
- package/dist/models/openrouter.js +14 -3
- package/dist/models/openrouter.js.map +1 -1
- package/dist/prebuilt/react-agent.d.ts.map +1 -1
- package/dist/prebuilt/react-agent.js +7 -2
- package/dist/prebuilt/react-agent.js.map +1 -1
- package/dist/pregel/checkpointing.d.ts +12 -0
- package/dist/pregel/checkpointing.d.ts.map +1 -0
- package/dist/pregel/checkpointing.js +60 -0
- package/dist/pregel/checkpointing.js.map +1 -0
- package/dist/pregel/execution.d.ts +7 -0
- package/dist/pregel/execution.d.ts.map +1 -0
- package/dist/pregel/execution.js +178 -0
- package/dist/pregel/execution.js.map +1 -0
- package/dist/pregel/index.d.ts +61 -0
- package/dist/pregel/index.d.ts.map +1 -0
- package/dist/pregel/index.js +154 -0
- package/dist/pregel/index.js.map +1 -0
- package/dist/pregel/interrupts.d.ts +3 -0
- package/dist/pregel/interrupts.d.ts.map +1 -0
- package/dist/pregel/interrupts.js +7 -0
- package/dist/pregel/interrupts.js.map +1 -0
- package/dist/pregel/state-helpers.d.ts +12 -0
- package/dist/pregel/state-helpers.d.ts.map +1 -0
- package/dist/pregel/state-helpers.js +71 -0
- package/dist/pregel/state-helpers.js.map +1 -0
- package/dist/pregel/streaming.d.ts +5 -0
- package/dist/pregel/streaming.d.ts.map +1 -0
- package/dist/pregel/streaming.js +462 -0
- package/dist/pregel/streaming.js.map +1 -0
- package/dist/pregel/types.d.ts +48 -0
- package/dist/pregel/types.d.ts.map +1 -0
- package/dist/pregel/types.js +5 -0
- package/dist/pregel/types.js.map +1 -0
- package/dist/pregel.d.ts +1 -63
- package/dist/pregel.d.ts.map +1 -1
- package/dist/pregel.js +2 -804
- package/dist/pregel.js.map +1 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +7 -6
- package/dist/retry.js.map +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +36 -9
- package/dist/store/index.js.map +1 -1
- package/dist/stream-events.d.ts.map +1 -1
- package/dist/stream-events.js +3 -9
- package/dist/stream-events.js.map +1 -1
- package/dist/swarm/agent-node.d.ts +11 -0
- package/dist/swarm/agent-node.d.ts.map +1 -0
- package/dist/swarm/agent-node.js +156 -0
- package/dist/swarm/agent-node.js.map +1 -0
- package/dist/swarm/compile-ext.d.ts +5 -0
- package/dist/swarm/compile-ext.d.ts.map +1 -0
- package/dist/swarm/compile-ext.js +126 -0
- package/dist/swarm/compile-ext.js.map +1 -0
- package/dist/swarm/config.d.ts +147 -0
- package/dist/swarm/config.d.ts.map +1 -0
- package/dist/swarm/config.js +17 -0
- package/dist/swarm/config.js.map +1 -0
- package/dist/swarm/factories.d.ts +37 -0
- package/dist/swarm/factories.d.ts.map +1 -0
- package/dist/swarm/factories.js +703 -0
- package/dist/swarm/factories.js.map +1 -0
- package/dist/swarm/graph.d.ts +16 -136
- package/dist/swarm/graph.d.ts.map +1 -1
- package/dist/swarm/graph.js +39 -897
- package/dist/swarm/graph.js.map +1 -1
- package/dist/swarm/index.d.ts +2 -1
- package/dist/swarm/index.d.ts.map +1 -1
- package/dist/swarm/index.js.map +1 -1
- package/dist/swarm/mailbox.d.ts.map +1 -1
- package/dist/swarm/mailbox.js +3 -1
- package/dist/swarm/mailbox.js.map +1 -1
- package/dist/swarm/mermaid.d.ts +2 -1
- package/dist/swarm/mermaid.d.ts.map +1 -1
- package/dist/swarm/mermaid.js +6 -3
- package/dist/swarm/mermaid.js.map +1 -1
- package/dist/swarm/pool.d.ts.map +1 -1
- package/dist/swarm/pool.js +18 -1
- package/dist/swarm/pool.js.map +1 -1
- package/dist/swarm/registry.d.ts.map +1 -1
- package/dist/swarm/registry.js +7 -0
- package/dist/swarm/registry.js.map +1 -1
- package/dist/swarm/scaling.d.ts +10 -1
- package/dist/swarm/scaling.d.ts.map +1 -1
- package/dist/swarm/scaling.js +85 -14
- package/dist/swarm/scaling.js.map +1 -1
- package/dist/swarm/snapshot.d.ts.map +1 -1
- package/dist/swarm/snapshot.js +10 -1
- package/dist/swarm/snapshot.js.map +1 -1
- package/dist/swarm/supervisor.js +20 -12
- package/dist/swarm/supervisor.js.map +1 -1
- package/dist/swarm/tracer.d.ts +3 -1
- package/dist/swarm/tracer.d.ts.map +1 -1
- package/dist/swarm/tracer.js +66 -15
- package/dist/swarm/tracer.js.map +1 -1
- package/dist/swarm/types.d.ts +1 -6
- package/dist/swarm/types.d.ts.map +1 -1
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tools/define.d.ts +2 -1
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +3 -1
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/pregel.js
CHANGED
|
@@ -1,805 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// New: interrupt() context management, HITL resume, token streaming
|
|
4
|
-
// ============================================================
|
|
5
|
-
import { START, END, Send, Command, } from "./types.js";
|
|
6
|
-
import { RecursionLimitError, NodeNotFoundError, ONIInterrupt, NodeTimeoutError, ONIError, NodeExecutionError, CircuitBreakerOpenError } from "./errors.js";
|
|
7
|
-
import { CircuitBreaker } from "./circuit-breaker.js";
|
|
8
|
-
import { withRetry } from "./retry.js";
|
|
9
|
-
import { NamespacedCheckpointer } from "./checkpointers/namespaced.js";
|
|
10
|
-
import { _runWithContext } from "./context.js";
|
|
11
|
-
import { StreamWriterImpl, _withTokenHandler } from "./streaming.js";
|
|
12
|
-
import { NodeInterruptSignal, HITLInterruptException, HITLSessionStore, _installInterruptContext, _clearInterruptContext, } from "./hitl/index.js";
|
|
13
|
-
import { EventBus } from "./events/bus.js";
|
|
14
|
-
import { AuditLog } from "./guardrails/audit.js";
|
|
15
|
-
import { BudgetTracker } from "./guardrails/budget.js";
|
|
16
|
-
import { runFilters } from "./guardrails/filters.js";
|
|
17
|
-
import { ONITracer } from "./telemetry.js";
|
|
18
|
-
const DEFAULT_RECURSION_LIMIT = 25;
|
|
19
|
-
export class ONIPregelRunner {
|
|
20
|
-
nodes;
|
|
21
|
-
edges;
|
|
22
|
-
channels;
|
|
23
|
-
interruptConfig;
|
|
24
|
-
checkpointer;
|
|
25
|
-
store;
|
|
26
|
-
defaults;
|
|
27
|
-
dlq;
|
|
28
|
-
hitlStore = new HITLSessionStore();
|
|
29
|
-
nodeCache = new Map();
|
|
30
|
-
circuitBreakers = new Map();
|
|
31
|
-
/** Set to true when this runner is being invoked as a subgraph */
|
|
32
|
-
_isSubgraph = false;
|
|
33
|
-
/** Accumulated parent updates from Command.PARENT during subgraph execution */
|
|
34
|
-
_parentUpdates = [];
|
|
35
|
-
eventBus;
|
|
36
|
-
auditLog;
|
|
37
|
-
budgetTracker;
|
|
38
|
-
contentFilters;
|
|
39
|
-
toolPermissions;
|
|
40
|
-
tracer;
|
|
41
|
-
/** Pre-indexed edges by source node — O(1) lookup instead of O(n) filter */
|
|
42
|
-
_edgesBySource;
|
|
43
|
-
/** Pre-computed ephemeral channel keys — avoids iterating all channels */
|
|
44
|
-
_ephemeralKeys;
|
|
45
|
-
constructor(nodes, edges, channels, interruptConfig = {}, checkpointer = null, store = null, guardrails, listeners, defaults, dlq = null, tracer) {
|
|
46
|
-
this.nodes = nodes;
|
|
47
|
-
this.edges = edges;
|
|
48
|
-
this.channels = channels;
|
|
49
|
-
this.interruptConfig = interruptConfig;
|
|
50
|
-
this.checkpointer = checkpointer;
|
|
51
|
-
this.store = store;
|
|
52
|
-
this.defaults = defaults;
|
|
53
|
-
this.dlq = dlq;
|
|
54
|
-
this.eventBus = new EventBus(listeners);
|
|
55
|
-
this.auditLog = guardrails?.audit ? new AuditLog() : null;
|
|
56
|
-
this.budgetTracker = guardrails?.budget ? new BudgetTracker(guardrails.budget) : null;
|
|
57
|
-
this.contentFilters = guardrails?.filters ?? [];
|
|
58
|
-
this.toolPermissions = guardrails?.toolPermissions;
|
|
59
|
-
this.tracer = new ONITracer(tracer ?? null);
|
|
60
|
-
// Pre-index edges by source for O(1) lookups in getNextNodes
|
|
61
|
-
this._edgesBySource = new Map();
|
|
62
|
-
for (const edge of edges) {
|
|
63
|
-
const from = edge.from;
|
|
64
|
-
let list = this._edgesBySource.get(from);
|
|
65
|
-
if (!list) {
|
|
66
|
-
list = [];
|
|
67
|
-
this._edgesBySource.set(from, list);
|
|
68
|
-
}
|
|
69
|
-
list.push(edge);
|
|
70
|
-
}
|
|
71
|
-
// Pre-compute ephemeral keys to avoid scanning all channels per superstep
|
|
72
|
-
this._ephemeralKeys = Object.keys(channels).filter((k) => channels[k].ephemeral);
|
|
73
|
-
}
|
|
74
|
-
// ----------------------------------------------------------------
|
|
75
|
-
// State helpers
|
|
76
|
-
// ----------------------------------------------------------------
|
|
77
|
-
buildInitialState() {
|
|
78
|
-
const state = {};
|
|
79
|
-
for (const key of Object.keys(this.channels)) {
|
|
80
|
-
state[key] = this.channels[key].default();
|
|
81
|
-
}
|
|
82
|
-
return state;
|
|
83
|
-
}
|
|
84
|
-
applyUpdate(current, update) {
|
|
85
|
-
const keys = Object.keys(update);
|
|
86
|
-
if (keys.length === 0)
|
|
87
|
-
return current;
|
|
88
|
-
const next = { ...current };
|
|
89
|
-
for (const key of keys) {
|
|
90
|
-
if (update[key] !== undefined) {
|
|
91
|
-
const ch = this.channels[key];
|
|
92
|
-
next[key] = ch
|
|
93
|
-
? ch.reducer(current[key], update[key])
|
|
94
|
-
: update[key];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return next;
|
|
98
|
-
}
|
|
99
|
-
resetEphemeral(state) {
|
|
100
|
-
if (this._ephemeralKeys.length === 0)
|
|
101
|
-
return state;
|
|
102
|
-
const next = { ...state };
|
|
103
|
-
for (const key of this._ephemeralKeys) {
|
|
104
|
-
next[key] = this.channels[key].default();
|
|
105
|
-
}
|
|
106
|
-
return next;
|
|
107
|
-
}
|
|
108
|
-
// ----------------------------------------------------------------
|
|
109
|
-
// Edge resolution
|
|
110
|
-
// ----------------------------------------------------------------
|
|
111
|
-
getNextNodes(fromNode, state, config) {
|
|
112
|
-
const outgoing = this._edgesBySource.get(fromNode) ?? [];
|
|
113
|
-
const nodes = [];
|
|
114
|
-
const sends = [];
|
|
115
|
-
for (const edge of outgoing) {
|
|
116
|
-
if (edge.type === "static") {
|
|
117
|
-
nodes.push(edge.to);
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
const result = edge.condition(state, config);
|
|
121
|
-
const resolved = Array.isArray(result) ? result : [result];
|
|
122
|
-
for (const r of resolved) {
|
|
123
|
-
if (r instanceof Send)
|
|
124
|
-
sends.push({ node: r.node, args: r.args });
|
|
125
|
-
else
|
|
126
|
-
nodes.push(edge.pathMap?.[r] ?? r);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { nodes, sends };
|
|
131
|
-
}
|
|
132
|
-
// ----------------------------------------------------------------
|
|
133
|
-
// Execute a node with interrupt() context installed
|
|
134
|
-
// ----------------------------------------------------------------
|
|
135
|
-
async executeNode(nodeDef, state, config, resumeValue, hasResume, writer, step, recursionLimit) {
|
|
136
|
-
// Check cache (compute key once, reuse for both lookup and store)
|
|
137
|
-
let cacheKey;
|
|
138
|
-
if (nodeDef.cache) {
|
|
139
|
-
const policy = typeof nodeDef.cache === "object" ? nodeDef.cache : {};
|
|
140
|
-
const keyFn = policy.key ?? ((s) => JSON.stringify(s));
|
|
141
|
-
cacheKey = `${nodeDef.name}::${keyFn(state)}`;
|
|
142
|
-
const cached = this.nodeCache.get(cacheKey);
|
|
143
|
-
if (cached) {
|
|
144
|
-
const ttl = policy.ttl ?? Infinity;
|
|
145
|
-
if (Date.now() - cached.timestamp < ttl) {
|
|
146
|
-
return cached.result;
|
|
147
|
-
}
|
|
148
|
-
this.nodeCache.delete(cacheKey);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
const _tid = config?.threadId ?? "unknown";
|
|
152
|
-
const ctx = {
|
|
153
|
-
config: config ?? {},
|
|
154
|
-
store: this.store,
|
|
155
|
-
writer: writer ?? null,
|
|
156
|
-
state: state,
|
|
157
|
-
parentGraph: null,
|
|
158
|
-
parentUpdates: [],
|
|
159
|
-
step: step ?? 0,
|
|
160
|
-
recursionLimit: recursionLimit ?? DEFAULT_RECURSION_LIMIT,
|
|
161
|
-
toolPermissions: this.toolPermissions,
|
|
162
|
-
_recordUsage: (agentName, modelId, usage) => {
|
|
163
|
-
if (!this.budgetTracker)
|
|
164
|
-
return;
|
|
165
|
-
const entries = this.budgetTracker.record(agentName, modelId, usage);
|
|
166
|
-
for (const e of entries)
|
|
167
|
-
this.auditLog?.record(_tid, e);
|
|
168
|
-
},
|
|
169
|
-
_emitEvent: (event) => this.eventBus.emit(event),
|
|
170
|
-
_auditRecord: (entry) => this.auditLog?.record(_tid, entry),
|
|
171
|
-
};
|
|
172
|
-
return _runWithContext(ctx, async () => {
|
|
173
|
-
_installInterruptContext({
|
|
174
|
-
nodeName: nodeDef.name,
|
|
175
|
-
resumeValue: resumeValue,
|
|
176
|
-
hasResume: hasResume ?? false,
|
|
177
|
-
});
|
|
178
|
-
try {
|
|
179
|
-
// Content filter — input direction
|
|
180
|
-
if (this.contentFilters.length > 0) {
|
|
181
|
-
const inputStr = JSON.stringify(state);
|
|
182
|
-
const inputCheck = runFilters(this.contentFilters, inputStr, "input");
|
|
183
|
-
if (!inputCheck.passed) {
|
|
184
|
-
const threadId = config?.threadId ?? "unknown";
|
|
185
|
-
this.eventBus.emit({ type: "filter.blocked", filter: inputCheck.blockedBy, agent: nodeDef.name, direction: "input", reason: inputCheck.reason, timestamp: Date.now() });
|
|
186
|
-
this.auditLog?.record(threadId, { timestamp: Date.now(), agent: nodeDef.name, action: "filter.blocked", data: { filter: inputCheck.blockedBy, direction: "input", reason: inputCheck.reason } });
|
|
187
|
-
throw new Error(`Content blocked by filter "${inputCheck.blockedBy}" on input to node "${nodeDef.name}": ${inputCheck.reason}`);
|
|
188
|
-
}
|
|
189
|
-
// Apply redaction if content was rewritten by a redacting filter
|
|
190
|
-
if (inputCheck.content !== inputStr) {
|
|
191
|
-
try {
|
|
192
|
-
state = JSON.parse(inputCheck.content);
|
|
193
|
-
}
|
|
194
|
-
catch { /* leave state unchanged on parse failure */ }
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
const run = () => Promise.resolve(nodeDef.fn(state, config));
|
|
198
|
-
// Core execute call: retry-aware
|
|
199
|
-
const executeCall = async () => {
|
|
200
|
-
if (nodeDef.retry)
|
|
201
|
-
return withRetry(run, nodeDef.name, nodeDef.retry);
|
|
202
|
-
return run();
|
|
203
|
-
};
|
|
204
|
-
// Wrap with timeout if configured (per-node > global default > none)
|
|
205
|
-
const timeoutMs = nodeDef.timeout ?? this.defaults?.nodeTimeout;
|
|
206
|
-
const executeWithTimeout = async () => {
|
|
207
|
-
if (timeoutMs != null && timeoutMs > 0) {
|
|
208
|
-
const ac = new AbortController();
|
|
209
|
-
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
210
|
-
try {
|
|
211
|
-
return await Promise.race([
|
|
212
|
-
executeCall(),
|
|
213
|
-
new Promise((_, reject) => {
|
|
214
|
-
ac.signal.addEventListener("abort", () => {
|
|
215
|
-
reject(new NodeTimeoutError(nodeDef.name, timeoutMs));
|
|
216
|
-
});
|
|
217
|
-
}),
|
|
218
|
-
]);
|
|
219
|
-
}
|
|
220
|
-
finally {
|
|
221
|
-
clearTimeout(timer);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return executeCall();
|
|
225
|
-
};
|
|
226
|
-
// Wrap with circuit breaker if configured
|
|
227
|
-
const cb = this.getCircuitBreaker(nodeDef);
|
|
228
|
-
let result;
|
|
229
|
-
try {
|
|
230
|
-
if (cb) {
|
|
231
|
-
result = await cb.execute(executeWithTimeout);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
result = await executeWithTimeout();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
// Pass through interrupt signals (thrown by interrupt() inside nodes)
|
|
239
|
-
if (err instanceof NodeInterruptSignal)
|
|
240
|
-
throw err;
|
|
241
|
-
// Circuit breaker open — invoke user fallback with real state + error
|
|
242
|
-
if (err instanceof CircuitBreakerOpenError && nodeDef.circuitBreaker?.fallback) {
|
|
243
|
-
result = nodeDef.circuitBreaker.fallback(state, err);
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
// Pass through structured ONI errors (NodeExecutionError from retry, NodeTimeoutError, etc.)
|
|
247
|
-
if (err instanceof ONIError)
|
|
248
|
-
throw err;
|
|
249
|
-
// Wrap raw errors and non-Error throws in NodeExecutionError
|
|
250
|
-
const cause = err instanceof Error ? err : new Error(String(err));
|
|
251
|
-
throw new NodeExecutionError(nodeDef.name, cause);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// Content filter — output direction
|
|
255
|
-
if (this.contentFilters.length > 0 && result != null) {
|
|
256
|
-
const outputStr = JSON.stringify(result);
|
|
257
|
-
const outputCheck = runFilters(this.contentFilters, outputStr, "output");
|
|
258
|
-
if (!outputCheck.passed) {
|
|
259
|
-
const threadId = config?.threadId ?? "unknown";
|
|
260
|
-
this.eventBus.emit({ type: "filter.blocked", filter: outputCheck.blockedBy, agent: nodeDef.name, direction: "output", reason: outputCheck.reason, timestamp: Date.now() });
|
|
261
|
-
this.auditLog?.record(threadId, { timestamp: Date.now(), agent: nodeDef.name, action: "filter.blocked", data: { filter: outputCheck.blockedBy, direction: "output", reason: outputCheck.reason } });
|
|
262
|
-
throw new Error(`Content blocked by filter "${outputCheck.blockedBy}" on output of node "${nodeDef.name}": ${outputCheck.reason}`);
|
|
263
|
-
}
|
|
264
|
-
// Apply redaction if content was rewritten by a redacting filter
|
|
265
|
-
if (outputCheck.content !== outputStr) {
|
|
266
|
-
try {
|
|
267
|
-
result = JSON.parse(outputCheck.content);
|
|
268
|
-
}
|
|
269
|
-
catch { /* leave result unchanged on parse failure */ }
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// Store in cache (reuse key computed above)
|
|
273
|
-
if (nodeDef.cache && cacheKey) {
|
|
274
|
-
this.nodeCache.set(cacheKey, { result, timestamp: Date.now() });
|
|
275
|
-
}
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
finally {
|
|
279
|
-
_clearInterruptContext();
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
// ----------------------------------------------------------------
|
|
284
|
-
// Dynamic interrupt check
|
|
285
|
-
// ----------------------------------------------------------------
|
|
286
|
-
checkDynamicInterrupt(node, timing, state, config) {
|
|
287
|
-
const dynamics = config?.dynamicInterrupts;
|
|
288
|
-
if (!dynamics)
|
|
289
|
-
return;
|
|
290
|
-
for (const di of dynamics) {
|
|
291
|
-
if (di.node === node && di.timing === timing && di.condition(state)) {
|
|
292
|
-
throw new ONIInterrupt(node, timing, state);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// ----------------------------------------------------------------
|
|
297
|
-
// Core stream generator
|
|
298
|
-
// ----------------------------------------------------------------
|
|
299
|
-
async *_stream(input, config, streamMode = "updates") {
|
|
300
|
-
const threadId = config?.threadId ?? `oni-${Date.now()}`;
|
|
301
|
-
const recursionLimit = config?.recursionLimit ?? DEFAULT_RECURSION_LIMIT;
|
|
302
|
-
const agentId = config?.agentId;
|
|
303
|
-
const modes = new Set(Array.isArray(streamMode) ? streamMode : [streamMode]);
|
|
304
|
-
const isMultiMode = Array.isArray(streamMode);
|
|
305
|
-
const tag = (evt, mode) => isMultiMode ? { ...evt, mode } : evt;
|
|
306
|
-
// Pre-compute mode flags — eliminates ~20 Set.has() lookups per superstep
|
|
307
|
-
const modeDebug = modes.has("debug");
|
|
308
|
-
const modeUpdates = modes.has("updates");
|
|
309
|
-
const modeValues = modes.has("values");
|
|
310
|
-
const modeCustom = modes.has("custom");
|
|
311
|
-
const modeMessages = modes.has("messages");
|
|
312
|
-
// Telemetry: graph-level span
|
|
313
|
-
const graphSpan = this.tracer.startGraphSpan("invoke", { threadId, agentId });
|
|
314
|
-
let step = 0; // declared before try so finally can read it for setAttribute
|
|
315
|
-
try {
|
|
316
|
-
// Load resume values from config (set by resume() call)
|
|
317
|
-
const resumeMap = config?.__resumeValues ?? {};
|
|
318
|
-
// Load or init state
|
|
319
|
-
let state;
|
|
320
|
-
let pendingNodes = [];
|
|
321
|
-
let pendingSends = [];
|
|
322
|
-
if (this.checkpointer && config?.threadId) {
|
|
323
|
-
const cp = await this.checkpointer.get(threadId);
|
|
324
|
-
if (cp) {
|
|
325
|
-
state = this.applyUpdate(cp.state, input);
|
|
326
|
-
step = cp.step;
|
|
327
|
-
pendingNodes = cp.nextNodes;
|
|
328
|
-
pendingSends = cp.pendingSends ?? [];
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
state = this.applyUpdate(this.buildInitialState(), input);
|
|
332
|
-
const init = this.getNextNodes(START, state, config);
|
|
333
|
-
pendingNodes = init.nodes;
|
|
334
|
-
pendingSends = init.sends;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
state = this.applyUpdate(this.buildInitialState(), input);
|
|
339
|
-
const init = this.getNextNodes(START, state, config);
|
|
340
|
-
pendingNodes = init.nodes;
|
|
341
|
-
pendingSends = init.sends;
|
|
342
|
-
}
|
|
343
|
-
if (modeValues)
|
|
344
|
-
yield tag(this.evt("state_update", state, step, agentId), "values");
|
|
345
|
-
// ---- Main superstep loop ----
|
|
346
|
-
while (true) {
|
|
347
|
-
const nextNodes = [];
|
|
348
|
-
const nextSends = [];
|
|
349
|
-
state = this.resetEphemeral(state);
|
|
350
|
-
// Drain sends — group by target node for parallel fan-out execution
|
|
351
|
-
const sendGroups = new Map();
|
|
352
|
-
for (const send of pendingSends) {
|
|
353
|
-
if (!sendGroups.has(send.node))
|
|
354
|
-
sendGroups.set(send.node, []);
|
|
355
|
-
sendGroups.get(send.node).push(send);
|
|
356
|
-
if (modeDebug)
|
|
357
|
-
yield tag(this.evt("send", send, step, agentId, send.node), "debug");
|
|
358
|
-
}
|
|
359
|
-
// Execute fan-out sends (each Send → separate node execution with its own state)
|
|
360
|
-
if (sendGroups.size > 0) {
|
|
361
|
-
// Build promises directly — avoids spread+flatMap intermediate arrays
|
|
362
|
-
const sendPromises = [];
|
|
363
|
-
for (const [node, sends] of sendGroups) {
|
|
364
|
-
const nodeDef = this.nodes.get(node);
|
|
365
|
-
if (!nodeDef)
|
|
366
|
-
throw new NodeNotFoundError(node);
|
|
367
|
-
for (const send of sends) {
|
|
368
|
-
sendPromises.push((async () => {
|
|
369
|
-
const sendState = this.applyUpdate(state, send.args);
|
|
370
|
-
const result = await this.executeNode(nodeDef, sendState, config, undefined, undefined, undefined, step, recursionLimit);
|
|
371
|
-
return { name: node, result };
|
|
372
|
-
})());
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const sendResults = await Promise.all(sendPromises);
|
|
376
|
-
// Reduce all send results through channels
|
|
377
|
-
for (const { name, result } of sendResults) {
|
|
378
|
-
if (result instanceof Command) {
|
|
379
|
-
if (result.update)
|
|
380
|
-
state = this.applyUpdate(state, result.update);
|
|
381
|
-
const gotos = result.goto
|
|
382
|
-
? (Array.isArray(result.goto) ? result.goto : [result.goto])
|
|
383
|
-
: this.getNextNodes(name, state, config).nodes;
|
|
384
|
-
nextNodes.push(...gotos);
|
|
385
|
-
}
|
|
386
|
-
else if (result && typeof result === "object") {
|
|
387
|
-
state = this.applyUpdate(state, result);
|
|
388
|
-
const { nodes, sends } = this.getNextNodes(name, state, config);
|
|
389
|
-
nextNodes.push(...nodes);
|
|
390
|
-
nextSends.push(...sends);
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
const { nodes, sends } = this.getNextNodes(name, state, config);
|
|
394
|
-
nextNodes.push(...nodes);
|
|
395
|
-
nextSends.push(...sends);
|
|
396
|
-
}
|
|
397
|
-
if (modeUpdates || modeDebug) {
|
|
398
|
-
const delta = result instanceof Command ? (result.update ?? {}) : (result ?? {});
|
|
399
|
-
if (modeUpdates)
|
|
400
|
-
yield tag(this.evt("node_end", delta, step, agentId, name), "updates");
|
|
401
|
-
if (modeDebug)
|
|
402
|
-
yield tag(this.evt("node_end", delta, step, agentId, name), "debug");
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
pendingSends = [];
|
|
407
|
-
// Filter executable nodes (non-END), excluding nodes already handled by sends
|
|
408
|
-
const executableNodes = pendingNodes.filter((n) => n !== END && !sendGroups.has(n));
|
|
409
|
-
if (executableNodes.length === 0 && sendGroups.size === 0)
|
|
410
|
-
break;
|
|
411
|
-
if (step >= recursionLimit)
|
|
412
|
-
throw new RecursionLimitError(recursionLimit);
|
|
413
|
-
// Emit debug node_start events before parallel execution
|
|
414
|
-
if (modeDebug) {
|
|
415
|
-
for (const nodeName of executableNodes) {
|
|
416
|
-
const name = nodeName;
|
|
417
|
-
if (!this.nodes.has(name))
|
|
418
|
-
throw new NodeNotFoundError(name);
|
|
419
|
-
// Static interrupt BEFORE (check before emitting start)
|
|
420
|
-
if (this.interruptConfig.interruptBefore?.includes(name))
|
|
421
|
-
throw new ONIInterrupt(name, "before", state);
|
|
422
|
-
this.checkDynamicInterrupt(name, "before", state, config);
|
|
423
|
-
yield tag(this.evt("node_start", {}, step, agentId, name), "debug");
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
// Execute all active nodes in parallel
|
|
427
|
-
const allCustomEvents = [];
|
|
428
|
-
const allMessageEvents = [];
|
|
429
|
-
const allSubgraphEvents = [];
|
|
430
|
-
const nodeWriters = new Map();
|
|
431
|
-
const nodeResults = await Promise.all(executableNodes.map(async (nodeName) => {
|
|
432
|
-
const name = nodeName;
|
|
433
|
-
const nodeDef = this.nodes.get(name);
|
|
434
|
-
if (!nodeDef)
|
|
435
|
-
throw new NodeNotFoundError(name);
|
|
436
|
-
// Static interrupt BEFORE (non-debug mode)
|
|
437
|
-
if (!modeDebug) {
|
|
438
|
-
if (this.interruptConfig.interruptBefore?.includes(name))
|
|
439
|
-
throw new ONIInterrupt(name, "before", state);
|
|
440
|
-
this.checkDynamicInterrupt(name, "before", state, config);
|
|
441
|
-
}
|
|
442
|
-
// Create a StreamWriter for this node
|
|
443
|
-
const messageId = `msg-${threadId}-${step}-${name}`;
|
|
444
|
-
const customEvents = [];
|
|
445
|
-
const messageEvents = [];
|
|
446
|
-
const writerImpl = new StreamWriterImpl((evt) => customEvents.push(evt), (token) => { }, (evt) => messageEvents.push(evt), name, step, messageId, agentId);
|
|
447
|
-
nodeWriters.set(name, writerImpl);
|
|
448
|
-
// Check if this node has a pending resume value
|
|
449
|
-
const resumeValue = resumeMap[name];
|
|
450
|
-
const hasResume = name in resumeMap;
|
|
451
|
-
// Emit agent.start lifecycle event
|
|
452
|
-
const nodeStartTime = Date.now();
|
|
453
|
-
this.eventBus.emit({ type: "agent.start", agent: name, timestamp: nodeStartTime, step });
|
|
454
|
-
// Telemetry: node-level span
|
|
455
|
-
const nodeSpan = this.tracer.startNodeSpan(name, { threadId, step, agentId });
|
|
456
|
-
let result;
|
|
457
|
-
let subParentUpdates = [];
|
|
458
|
-
try {
|
|
459
|
-
// Scope emitToken to this node's async context via ALS — parallel nodes each
|
|
460
|
-
// get their own handler so tokens are never dropped or misrouted.
|
|
461
|
-
result = await _withTokenHandler((token) => writerImpl.token(token), async () => {
|
|
462
|
-
if (nodeDef.subgraph) {
|
|
463
|
-
// Mark child runner as a subgraph so Command.PARENT works
|
|
464
|
-
const childRunner = nodeDef.subgraph._runner;
|
|
465
|
-
if (childRunner) {
|
|
466
|
-
childRunner._isSubgraph = true;
|
|
467
|
-
childRunner._parentUpdates = [];
|
|
468
|
-
}
|
|
469
|
-
// Save original checkpointer before potentially overwriting
|
|
470
|
-
const savedChildCheckpointer = childRunner ? childRunner.checkpointer : undefined;
|
|
471
|
-
// Namespace the subgraph's checkpointer for isolation
|
|
472
|
-
if (this.checkpointer && childRunner) {
|
|
473
|
-
childRunner.checkpointer = new NamespacedCheckpointer(this.checkpointer, name);
|
|
474
|
-
}
|
|
475
|
-
// Stream the subgraph — always restore child runner state, even on throw/interrupt
|
|
476
|
-
let subFinalState;
|
|
477
|
-
try {
|
|
478
|
-
const childStreamMode = ["debug", "values"];
|
|
479
|
-
for await (const evt of nodeDef.subgraph.stream(state, {
|
|
480
|
-
...config,
|
|
481
|
-
parentRunId: config?.threadId,
|
|
482
|
-
streamMode: childStreamMode,
|
|
483
|
-
})) {
|
|
484
|
-
// Namespace-prefix the node name
|
|
485
|
-
allSubgraphEvents.push({
|
|
486
|
-
...evt,
|
|
487
|
-
node: evt.node ? `${name}:${evt.node}` : name,
|
|
488
|
-
});
|
|
489
|
-
// Track the last state_update as the final subgraph state
|
|
490
|
-
if (evt.event === "state_update") {
|
|
491
|
-
subFinalState = evt.data;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (childRunner) {
|
|
495
|
-
subParentUpdates = childRunner._parentUpdates;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
finally {
|
|
499
|
-
// Restore regardless of success, throw, or interrupt
|
|
500
|
-
if (childRunner) {
|
|
501
|
-
childRunner._isSubgraph = false;
|
|
502
|
-
childRunner._parentUpdates = [];
|
|
503
|
-
childRunner.checkpointer = savedChildCheckpointer;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return subFinalState ?? {};
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
return this.executeNode(nodeDef, state, config, resumeValue, hasResume, writerImpl, step, recursionLimit);
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
catch (err) {
|
|
514
|
-
// Catch interrupt() signals thrown from inside nodes
|
|
515
|
-
if (err instanceof NodeInterruptSignal) {
|
|
516
|
-
const iv = {
|
|
517
|
-
value: err.value,
|
|
518
|
-
node: name,
|
|
519
|
-
resumeId: err.resumeId,
|
|
520
|
-
timestamp: Date.now(),
|
|
521
|
-
};
|
|
522
|
-
// Save checkpoint before surfacing interrupt
|
|
523
|
-
await this.saveCheckpoint(threadId, step, state, [name], pendingSends, agentId, config?.metadata);
|
|
524
|
-
// Record HITL session if checkpointer exists
|
|
525
|
-
if (this.checkpointer) {
|
|
526
|
-
const cp = await this.checkpointer.get(threadId);
|
|
527
|
-
if (cp)
|
|
528
|
-
this.hitlStore.record(threadId, iv, cp);
|
|
529
|
-
}
|
|
530
|
-
throw new HITLInterruptException(threadId, iv, state);
|
|
531
|
-
}
|
|
532
|
-
// Record to DLQ before re-throwing — use original cause if wrapped
|
|
533
|
-
if (this.dlq && err instanceof Error) {
|
|
534
|
-
const dlqErr = (err instanceof NodeExecutionError && err.cause instanceof Error) ? err.cause : err;
|
|
535
|
-
this.dlq.record(threadId, name, state, dlqErr, nodeDef.retry?.maxAttempts ?? 1);
|
|
536
|
-
}
|
|
537
|
-
// Telemetry: record error on node span — use original cause if wrapped
|
|
538
|
-
if (err instanceof Error) {
|
|
539
|
-
const telErr = (err instanceof NodeExecutionError && err.cause instanceof Error) ? err.cause : err;
|
|
540
|
-
this.tracer.recordError(nodeSpan, telErr);
|
|
541
|
-
}
|
|
542
|
-
this.tracer.endSpan(nodeSpan);
|
|
543
|
-
// Lifecycle event: emit error for non-interrupt failures
|
|
544
|
-
if (err instanceof Error) {
|
|
545
|
-
this.eventBus.emit({ type: "error", agent: name, error: err, timestamp: Date.now() });
|
|
546
|
-
}
|
|
547
|
-
throw err;
|
|
548
|
-
}
|
|
549
|
-
// Telemetry: end node span
|
|
550
|
-
this.tracer.endSpan(nodeSpan);
|
|
551
|
-
// Emit agent.end lifecycle event
|
|
552
|
-
this.eventBus.emit({ type: "agent.end", agent: name, timestamp: Date.now(), step, duration: Date.now() - nodeStartTime });
|
|
553
|
-
// Collect events for yielding after parallel execution
|
|
554
|
-
allCustomEvents.push(...customEvents);
|
|
555
|
-
allMessageEvents.push(...messageEvents);
|
|
556
|
-
return { name, result, subParentUpdates };
|
|
557
|
-
}));
|
|
558
|
-
// Yield buffered subgraph events — filtered by parent's active modes
|
|
559
|
-
for (const evt of allSubgraphEvents) {
|
|
560
|
-
const e = evt;
|
|
561
|
-
if (modeDebug) {
|
|
562
|
-
yield tag(e, "debug");
|
|
563
|
-
}
|
|
564
|
-
else if (modeUpdates && (e.event === "node_end")) {
|
|
565
|
-
yield tag(e, "updates");
|
|
566
|
-
}
|
|
567
|
-
else if (modeValues && e.event === "state_update") {
|
|
568
|
-
yield tag(e, "values");
|
|
569
|
-
}
|
|
570
|
-
// Custom and message events from subgraphs are forwarded if those modes are active
|
|
571
|
-
if (modeCustom && e.event === "custom") {
|
|
572
|
-
yield tag(e, "custom");
|
|
573
|
-
}
|
|
574
|
-
if (modeMessages && (e.event === "messages" || e.event === "messages/complete")) {
|
|
575
|
-
yield tag(e, "messages");
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
// Apply results
|
|
579
|
-
const stepWrites = [];
|
|
580
|
-
for (const { name, result, subParentUpdates: parentUpdates } of nodeResults) {
|
|
581
|
-
if (result instanceof Command) {
|
|
582
|
-
if (result.graph === Command.PARENT) {
|
|
583
|
-
// Push update to parent — do NOT apply locally
|
|
584
|
-
if (!this._isSubgraph) {
|
|
585
|
-
throw new Error("Command.PARENT used but graph is not running as a subgraph");
|
|
586
|
-
}
|
|
587
|
-
if (result.update)
|
|
588
|
-
this._parentUpdates.push(result.update);
|
|
589
|
-
// Still resolve next nodes normally
|
|
590
|
-
const { nodes, sends } = this.getNextNodes(name, state, config);
|
|
591
|
-
nextNodes.push(...nodes);
|
|
592
|
-
nextSends.push(...sends);
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
if (result.update) {
|
|
596
|
-
state = this.applyUpdate(state, result.update);
|
|
597
|
-
if (Object.keys(result.update).length > 0) {
|
|
598
|
-
stepWrites.push({ nodeId: name, writes: result.update });
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
const gotos = result.goto
|
|
602
|
-
? (Array.isArray(result.goto) ? result.goto : [result.goto])
|
|
603
|
-
: this.getNextNodes(name, state, config).nodes;
|
|
604
|
-
nextNodes.push(...gotos);
|
|
605
|
-
if (result.send)
|
|
606
|
-
nextSends.push(...result.send.map((s) => ({ node: s.node, args: s.args })));
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
else if (result && typeof result === "object") {
|
|
610
|
-
state = this.applyUpdate(state, result);
|
|
611
|
-
const writes = result;
|
|
612
|
-
if (Object.keys(writes).length > 0) {
|
|
613
|
-
stepWrites.push({ nodeId: name, writes });
|
|
614
|
-
}
|
|
615
|
-
const { nodes, sends } = this.getNextNodes(name, state, config);
|
|
616
|
-
nextNodes.push(...nodes);
|
|
617
|
-
nextSends.push(...sends);
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
const { nodes, sends } = this.getNextNodes(name, state, config);
|
|
621
|
-
nextNodes.push(...nodes);
|
|
622
|
-
nextSends.push(...sends);
|
|
623
|
-
}
|
|
624
|
-
// Apply parent updates from subgraph Command.PARENT (after normal result)
|
|
625
|
-
for (const pu of parentUpdates) {
|
|
626
|
-
state = this.applyUpdate(state, pu);
|
|
627
|
-
}
|
|
628
|
-
if (modeUpdates || modeDebug) {
|
|
629
|
-
const delta = result instanceof Command ? (result.update ?? {}) : (result ?? {});
|
|
630
|
-
if (modeUpdates)
|
|
631
|
-
yield tag(this.evt("node_end", delta, step, agentId, name), "updates");
|
|
632
|
-
if (modeDebug)
|
|
633
|
-
yield tag(this.evt("node_end", delta, step, agentId, name), "debug");
|
|
634
|
-
}
|
|
635
|
-
// Static interrupt AFTER
|
|
636
|
-
if (this.interruptConfig.interruptAfter?.includes(name)) {
|
|
637
|
-
await this.saveCheckpoint(threadId, step, state, nextNodes, nextSends, agentId, config?.metadata);
|
|
638
|
-
throw new ONIInterrupt(name, "after", state);
|
|
639
|
-
}
|
|
640
|
-
this.checkDynamicInterrupt(name, "after", state, config);
|
|
641
|
-
}
|
|
642
|
-
// Yield buffered custom/message events based on stream mode
|
|
643
|
-
if (modeCustom || modeDebug) {
|
|
644
|
-
for (const evt of allCustomEvents) {
|
|
645
|
-
if (modeCustom)
|
|
646
|
-
yield tag(evt, "custom");
|
|
647
|
-
if (modeDebug)
|
|
648
|
-
yield tag(evt, "debug");
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (modeMessages || modeDebug) {
|
|
652
|
-
for (const evt of allMessageEvents) {
|
|
653
|
-
if (modeMessages)
|
|
654
|
-
yield tag(evt, "messages");
|
|
655
|
-
if (modeDebug)
|
|
656
|
-
yield tag(evt, "debug");
|
|
657
|
-
}
|
|
658
|
-
// Emit messages/complete for each node that produced tokens
|
|
659
|
-
for (const [, writer] of nodeWriters) {
|
|
660
|
-
const complete = writer._complete();
|
|
661
|
-
if (complete) {
|
|
662
|
-
if (modeMessages)
|
|
663
|
-
yield tag(complete, "messages");
|
|
664
|
-
if (modeDebug)
|
|
665
|
-
yield tag(complete, "debug");
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (modeValues)
|
|
670
|
-
yield tag(this.evt("state_update", state, step, agentId), "values");
|
|
671
|
-
// Deduplicate nextNodes — avoid Set+spread when no dupes (common case)
|
|
672
|
-
if (nextNodes.length <= 1) {
|
|
673
|
-
pendingNodes = nextNodes;
|
|
674
|
-
}
|
|
675
|
-
else {
|
|
676
|
-
const seen = new Set();
|
|
677
|
-
pendingNodes = [];
|
|
678
|
-
for (const n of nextNodes) {
|
|
679
|
-
const key = n;
|
|
680
|
-
if (!seen.has(key)) {
|
|
681
|
-
seen.add(key);
|
|
682
|
-
pendingNodes.push(n);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
pendingSends = nextSends;
|
|
687
|
-
step++;
|
|
688
|
-
await this.saveCheckpoint(threadId, step, state, pendingNodes, pendingSends, agentId, config?.metadata, stepWrites);
|
|
689
|
-
}
|
|
690
|
-
if (modeValues)
|
|
691
|
-
yield tag(this.evt("state_update", state, step, agentId), "values");
|
|
692
|
-
}
|
|
693
|
-
finally {
|
|
694
|
-
// Telemetry: end graph span — always runs, even on error or interrupt
|
|
695
|
-
graphSpan.setAttribute("oni.steps", step);
|
|
696
|
-
this.tracer.endSpan(graphSpan);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
// ----------------------------------------------------------------
|
|
700
|
-
// Public API
|
|
701
|
-
// ----------------------------------------------------------------
|
|
702
|
-
async invoke(input, config) {
|
|
703
|
-
let finalState;
|
|
704
|
-
for await (const evt of this._stream(input, config, "values")) {
|
|
705
|
-
if (evt.event === "state_update")
|
|
706
|
-
finalState = evt.data;
|
|
707
|
-
}
|
|
708
|
-
return finalState;
|
|
709
|
-
}
|
|
710
|
-
async *stream(input, config) {
|
|
711
|
-
yield* this._stream(input, config, config?.streamMode ?? "updates");
|
|
712
|
-
}
|
|
713
|
-
async batch(inputs, config) {
|
|
714
|
-
return Promise.all(inputs.map((inp, i) => this.invoke(inp, {
|
|
715
|
-
...config,
|
|
716
|
-
threadId: config?.threadId ? `${config.threadId}-${i}` : undefined,
|
|
717
|
-
})));
|
|
718
|
-
}
|
|
719
|
-
// ---- State ----
|
|
720
|
-
async getState(threadId) {
|
|
721
|
-
if (!this.checkpointer)
|
|
722
|
-
return null;
|
|
723
|
-
return (await this.checkpointer.get(threadId))?.state ?? null;
|
|
724
|
-
}
|
|
725
|
-
async updateState(threadId, update) {
|
|
726
|
-
if (!this.checkpointer)
|
|
727
|
-
return;
|
|
728
|
-
const cp = await this.checkpointer.get(threadId);
|
|
729
|
-
if (!cp)
|
|
730
|
-
return;
|
|
731
|
-
await this.checkpointer.put({ ...cp, state: this.applyUpdate(cp.state, update), timestamp: Date.now() });
|
|
732
|
-
}
|
|
733
|
-
// ---- Time-travel ----
|
|
734
|
-
async getStateAt(threadId, step) {
|
|
735
|
-
if (!this.checkpointer)
|
|
736
|
-
return null;
|
|
737
|
-
const history = await this.checkpointer.list(threadId);
|
|
738
|
-
return history.find((c) => c.step === step)?.state ?? null;
|
|
739
|
-
}
|
|
740
|
-
async getHistory(threadId) {
|
|
741
|
-
if (!this.checkpointer)
|
|
742
|
-
return [];
|
|
743
|
-
return this.checkpointer.list(threadId);
|
|
744
|
-
}
|
|
745
|
-
async forkFrom(threadId, step, newThreadId) {
|
|
746
|
-
if (!this.checkpointer)
|
|
747
|
-
return;
|
|
748
|
-
const cp = this.checkpointer;
|
|
749
|
-
if (typeof cp.fork === "function") {
|
|
750
|
-
await cp.fork(threadId, step, newThreadId);
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
const history = await this.checkpointer.list(threadId);
|
|
754
|
-
for (const c of history.filter((x) => x.step <= step)) {
|
|
755
|
-
await this.checkpointer.put({ ...c, threadId: newThreadId });
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
// ---- HITL ----
|
|
760
|
-
getPendingInterrupts(threadId) {
|
|
761
|
-
return this.hitlStore.getByThread(threadId);
|
|
762
|
-
}
|
|
763
|
-
hitlSessionStore() {
|
|
764
|
-
return this.hitlStore;
|
|
765
|
-
}
|
|
766
|
-
// ---- Circuit Breaker ----
|
|
767
|
-
getCircuitBreaker(nodeDef) {
|
|
768
|
-
if (!nodeDef.circuitBreaker)
|
|
769
|
-
return null;
|
|
770
|
-
let cb = this.circuitBreakers.get(nodeDef.name);
|
|
771
|
-
if (!cb) {
|
|
772
|
-
cb = new CircuitBreaker({
|
|
773
|
-
threshold: nodeDef.circuitBreaker.threshold,
|
|
774
|
-
resetAfter: nodeDef.circuitBreaker.resetAfter,
|
|
775
|
-
}, nodeDef.name);
|
|
776
|
-
this.circuitBreakers.set(nodeDef.name, cb);
|
|
777
|
-
}
|
|
778
|
-
return cb;
|
|
779
|
-
}
|
|
780
|
-
// ---- Dead Letter Queue ----
|
|
781
|
-
getDeadLetters(threadId) {
|
|
782
|
-
return this.dlq?.getAll(threadId) ?? [];
|
|
783
|
-
}
|
|
784
|
-
// ---- Helpers ----
|
|
785
|
-
async saveCheckpoint(threadId, step, state, nextNodes, pendingSends, agentId, metadata, pendingWrites) {
|
|
786
|
-
if (!this.checkpointer)
|
|
787
|
-
return;
|
|
788
|
-
const cpSpan = this.tracer.startCheckpointSpan("put", { threadId });
|
|
789
|
-
try {
|
|
790
|
-
await this.checkpointer.put({
|
|
791
|
-
threadId, step, state, agentId, metadata, pendingWrites,
|
|
792
|
-
nextNodes: nextNodes.map(String),
|
|
793
|
-
pendingSends: pendingSends,
|
|
794
|
-
timestamp: Date.now(),
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
finally {
|
|
798
|
-
this.tracer.endSpan(cpSpan);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
evt(event, data, step, agentId, node) {
|
|
802
|
-
return { event, data, step, timestamp: Date.now(), agentId, node };
|
|
803
|
-
}
|
|
804
|
-
}
|
|
1
|
+
// src/pregel.ts — internals moved to src/pregel/
|
|
2
|
+
export { ONIPregelRunner } from './pregel/index.js';
|
|
805
3
|
//# sourceMappingURL=pregel.js.map
|