@arronqzy/blueprint-dsl 1.0.8 → 1.0.9
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/index.cjs +2711 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +739 -0
- package/dist/index.d.ts +739 -0
- package/dist/index.js +2585 -0
- package/dist/index.js.map +1 -0
- package/package.json +14 -5
- package/src/behaviors/default.ts +0 -64
- package/src/blueprint-signal.ts +0 -14
- package/src/clock-config.ts +0 -232
- package/src/core/behavior-registry.ts +0 -32
- package/src/core/behavior.ts +0 -13
- package/src/core/executor.ts +0 -51
- package/src/core/scheduler-loop.ts +0 -13
- package/src/event-config.ts +0 -211
- package/src/fetch-config.ts +0 -281
- package/src/incoming-node-scope.ts +0 -411
- package/src/index.ts +0 -33
- package/src/json-config.ts +0 -37
- package/src/lifecycle.ts +0 -45
- package/src/logic-config.ts +0 -53
- package/src/node-signal.ts +0 -83
- package/src/nodes/definitions.ts +0 -105
- package/src/runtime/blueprint-cycle.ts +0 -136
- package/src/runtime/clock-active-registry.ts +0 -30
- package/src/runtime/clock-scheduler.ts +0 -49
- package/src/runtime/execution-trace.ts +0 -55
- package/src/runtime/graph-runner.ts +0 -1523
- package/src/scope-autocomplete.ts +0 -114
- package/src/scope-template.ts +0 -93
- package/src/swagger.ts +0 -192
- package/src/type.ts +0 -34
|
@@ -1,1523 +0,0 @@
|
|
|
1
|
-
import type { FetchRequestConfig } from "../fetch-config.js";
|
|
2
|
-
import {
|
|
3
|
-
executeFetch,
|
|
4
|
-
normalizeFetchRequestConfig,
|
|
5
|
-
resolveFetchRequestUrl,
|
|
6
|
-
} from "../fetch-config.js";
|
|
7
|
-
import {
|
|
8
|
-
applyFetchConfigScope,
|
|
9
|
-
getFetchBodyValidationError,
|
|
10
|
-
getFetchHeadersValidationError,
|
|
11
|
-
resolveFetchIncomingScope,
|
|
12
|
-
} from "../incoming-node-scope.js";
|
|
13
|
-
import type { JsonNodeConfig } from "../json-config.js";
|
|
14
|
-
import {
|
|
15
|
-
DEFAULT_JSON_NODE_CONFIG,
|
|
16
|
-
parseJsonConfig,
|
|
17
|
-
} from "../json-config.js";
|
|
18
|
-
import type { LogicNodeConfig } from "../logic-config.js";
|
|
19
|
-
import {
|
|
20
|
-
DEFAULT_LOGIC_NODE_CONFIG,
|
|
21
|
-
executeLogicConfig,
|
|
22
|
-
} from "../logic-config.js";
|
|
23
|
-
import type { ClockNodeConfig } from "../clock-config.js";
|
|
24
|
-
import {
|
|
25
|
-
buildClockSignalValue,
|
|
26
|
-
DEFAULT_CLOCK_NODE_CONFIG,
|
|
27
|
-
getClockTickWaitMs,
|
|
28
|
-
normalizeClockConfig,
|
|
29
|
-
validateClockScheduleConfig,
|
|
30
|
-
} from "../clock-config.js";
|
|
31
|
-
import type { ViewEventNodeConfig, ViewEventSignal } from "../event-config.js";
|
|
32
|
-
import { normalizeViewEventConfig } from "../event-config.js";
|
|
33
|
-
import {
|
|
34
|
-
buildClockSessionKey,
|
|
35
|
-
scheduleClockOutputs,
|
|
36
|
-
stopAllClockSchedules,
|
|
37
|
-
abortClockNode as abortClockNodeSession,
|
|
38
|
-
isClockSessionAborted,
|
|
39
|
-
clearClockSessionAbort,
|
|
40
|
-
} from "./clock-scheduler.js";
|
|
41
|
-
import {
|
|
42
|
-
markClockNodeActive,
|
|
43
|
-
markClockNodeInactive,
|
|
44
|
-
} from "./clock-active-registry.js";
|
|
45
|
-
import {
|
|
46
|
-
registerDefaultBehaviors,
|
|
47
|
-
BLUEPRINT_ACTIVATION_INPUT_KEY,
|
|
48
|
-
LIFECYCLE_SIGNAL_KEY,
|
|
49
|
-
UI_EVENT_PAYLOAD_KEY,
|
|
50
|
-
VIEW_EVENT_SIGNAL_KEY,
|
|
51
|
-
} from "../behaviors/default.js";
|
|
52
|
-
import { BehaviorRegistry } from "../core/behavior-registry.js";
|
|
53
|
-
import { Executor } from "../core/executor.js";
|
|
54
|
-
import {
|
|
55
|
-
createLifecycleSignal,
|
|
56
|
-
type LifecycleSignal,
|
|
57
|
-
type PageLifecyclePhase,
|
|
58
|
-
} from "../lifecycle.js";
|
|
59
|
-
import {
|
|
60
|
-
createFalseSignal,
|
|
61
|
-
createTrueSignal,
|
|
62
|
-
combinePortSignalsOr,
|
|
63
|
-
isFalseSignal,
|
|
64
|
-
isTrueSignal,
|
|
65
|
-
} from "../node-signal.js";
|
|
66
|
-
import {
|
|
67
|
-
BLUEPRINT_NODE_TYPE,
|
|
68
|
-
CLOCK_NODE_TYPE,
|
|
69
|
-
EVENT_NODE_TYPE,
|
|
70
|
-
getNodeDefinition,
|
|
71
|
-
LIFECYCLE_NODE_TYPE,
|
|
72
|
-
VIEW_NODE_TYPE,
|
|
73
|
-
} from "../nodes/definitions.js";
|
|
74
|
-
import type { BlueprintRefOutput, BlueprintNodeOutputs } from "../blueprint-signal.js";
|
|
75
|
-
import {
|
|
76
|
-
createTraceEntryId,
|
|
77
|
-
serializeTraceValue,
|
|
78
|
-
type ExecutionTraceEntry,
|
|
79
|
-
} from "./execution-trace.js";
|
|
80
|
-
import type { ExecutionToken, Scope, Value } from "../type.js";
|
|
81
|
-
import {
|
|
82
|
-
assertNoBlueprintReferenceCycle,
|
|
83
|
-
BLUEPRINT_CALL_STACK_KEY,
|
|
84
|
-
detectRuntimeBlueprintCycle,
|
|
85
|
-
resolveBlueprintCallStack,
|
|
86
|
-
} from "./blueprint-cycle.js";
|
|
87
|
-
|
|
88
|
-
export type RunnableGraphNode = {
|
|
89
|
-
id: string;
|
|
90
|
-
nodeType: string;
|
|
91
|
-
label?: string;
|
|
92
|
-
role?: string;
|
|
93
|
-
lifecyclePhase?: PageLifecyclePhase;
|
|
94
|
-
/** 蓝图库记录 id,Blueprint 节点引用嵌套蓝图时使用 */
|
|
95
|
-
libraryBlueprintId?: string;
|
|
96
|
-
blueprintName?: string;
|
|
97
|
-
/** 视图绑定节点关联的视图画布元素 id */
|
|
98
|
-
viewElementIds?: string[];
|
|
99
|
-
fetchConfig?: FetchRequestConfig;
|
|
100
|
-
jsonConfig?: JsonNodeConfig;
|
|
101
|
-
logicConfig?: LogicNodeConfig;
|
|
102
|
-
clockConfig?: ClockNodeConfig;
|
|
103
|
-
eventConfig?: ViewEventNodeConfig;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export type RunnableGraphEdge = {
|
|
107
|
-
source: string;
|
|
108
|
-
target: string;
|
|
109
|
-
sourceHandle?: string;
|
|
110
|
-
targetHandle?: string;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export type RunnableGraph = {
|
|
114
|
-
nodes: RunnableGraphNode[];
|
|
115
|
-
edges: RunnableGraphEdge[];
|
|
116
|
-
/** 为 true 时调试/执行不因假信号阻塞队列 */
|
|
117
|
-
allowFalseSignalPropagation?: boolean;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export type LibraryBlueprintResolver = (
|
|
121
|
-
libraryBlueprintId: string
|
|
122
|
-
) => Promise<RunnableGraph | null>;
|
|
123
|
-
|
|
124
|
-
export type ViewScopeUpdateCallback = (
|
|
125
|
-
viewElementIds: string[],
|
|
126
|
-
scope: unknown
|
|
127
|
-
) => void;
|
|
128
|
-
|
|
129
|
-
type NodeOutputStore = Map<string, Map<string, Value>>;
|
|
130
|
-
|
|
131
|
-
type DebugSessionSnapshot = {
|
|
132
|
-
outputs: NodeOutputStore;
|
|
133
|
-
queue: ExecutionToken[];
|
|
134
|
-
traceEntries: ExecutionTraceEntry[];
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
function createTokenId() {
|
|
138
|
-
return `token_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function cloneScope(scope?: Scope): Scope {
|
|
142
|
-
return {
|
|
143
|
-
parent: scope?.parent,
|
|
144
|
-
vars: new Map(scope?.vars ?? []),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function cloneOutputs(outputs: NodeOutputStore): NodeOutputStore {
|
|
149
|
-
const next = new Map<string, Map<string, Value>>();
|
|
150
|
-
for (const [nodeId, ports] of outputs.entries()) {
|
|
151
|
-
next.set(nodeId, new Map(ports));
|
|
152
|
-
}
|
|
153
|
-
return next;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function cloneToken(token: ExecutionToken): ExecutionToken {
|
|
157
|
-
return {
|
|
158
|
-
tokenId: token.tokenId,
|
|
159
|
-
nodeId: token.nodeId,
|
|
160
|
-
nodeType: token.nodeType,
|
|
161
|
-
inPort: token.inPort,
|
|
162
|
-
scope: cloneScope(token.scope),
|
|
163
|
-
correlationId: token.correlationId,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function cloneQueue(queue: ExecutionToken[]): ExecutionToken[] {
|
|
168
|
-
return queue.map(cloneToken);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
type DebugProgressCallback = (entries: ExecutionTraceEntry[]) => void;
|
|
172
|
-
|
|
173
|
-
export class BlueprintGraphRunner {
|
|
174
|
-
private behaviors: BehaviorRegistry;
|
|
175
|
-
private outputs: NodeOutputStore = new Map();
|
|
176
|
-
private resolveLibraryBlueprint?: LibraryBlueprintResolver;
|
|
177
|
-
private debugQueue: ExecutionToken[] | null = null;
|
|
178
|
-
private debugExecutor: Executor | null = null;
|
|
179
|
-
private traceEntries: ExecutionTraceEntry[] = [];
|
|
180
|
-
private debugHistory: DebugSessionSnapshot[] = [];
|
|
181
|
-
private rootLibraryBlueprintId?: string | null;
|
|
182
|
-
private resolveBlueprintName?: (libraryBlueprintId: string) => string;
|
|
183
|
-
private cycleValidated = false;
|
|
184
|
-
private debugProgressCallback: DebugProgressCallback | null = null;
|
|
185
|
-
private onViewScopeUpdate?: ViewScopeUpdateCallback;
|
|
186
|
-
|
|
187
|
-
constructor(
|
|
188
|
-
private graph: RunnableGraph,
|
|
189
|
-
options?: {
|
|
190
|
-
resolveLibraryBlueprint?: LibraryBlueprintResolver;
|
|
191
|
-
rootLibraryBlueprintId?: string | null;
|
|
192
|
-
resolveBlueprintName?: (libraryBlueprintId: string) => string;
|
|
193
|
-
onViewScopeUpdate?: ViewScopeUpdateCallback;
|
|
194
|
-
}
|
|
195
|
-
) {
|
|
196
|
-
this.resolveLibraryBlueprint = options?.resolveLibraryBlueprint;
|
|
197
|
-
this.rootLibraryBlueprintId = options?.rootLibraryBlueprintId ?? null;
|
|
198
|
-
this.resolveBlueprintName = options?.resolveBlueprintName;
|
|
199
|
-
this.onViewScopeUpdate = options?.onViewScopeUpdate;
|
|
200
|
-
this.behaviors = new BehaviorRegistry();
|
|
201
|
-
registerDefaultBehaviors(this.behaviors);
|
|
202
|
-
this.registerBlueprintRefBehavior();
|
|
203
|
-
this.registerFetchBehavior();
|
|
204
|
-
this.registerJsonBehavior();
|
|
205
|
-
this.registerLogicBehavior();
|
|
206
|
-
this.registerClockBehavior();
|
|
207
|
-
this.registerAndBehavior();
|
|
208
|
-
this.registerViewBindBehavior();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async ensureNoBlueprintCycle(): Promise<void> {
|
|
212
|
-
if (this.cycleValidated || !this.resolveLibraryBlueprint) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
await assertNoBlueprintReferenceCycle({
|
|
216
|
-
rootGraph: this.graph,
|
|
217
|
-
rootLibraryBlueprintId: this.rootLibraryBlueprintId,
|
|
218
|
-
resolveLibraryBlueprint: this.resolveLibraryBlueprint,
|
|
219
|
-
resolveBlueprintName: this.resolveBlueprintName,
|
|
220
|
-
});
|
|
221
|
-
this.cycleValidated = true;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private injectedInputs = new Map<string, Value>();
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* 从视图交互触发指定蓝图节点:向该节点 in 口注入真信号并执行下游。
|
|
228
|
-
* payload 写入 scope(UI_EVENT_PAYLOAD_KEY)供 Logic 等节点读取。
|
|
229
|
-
*/
|
|
230
|
-
async triggerNode(nodeId: string, inputValue?: unknown): Promise<void> {
|
|
231
|
-
await this.ensureNoBlueprintCycle();
|
|
232
|
-
this.requireNode(nodeId);
|
|
233
|
-
const key = `${nodeId}:in`;
|
|
234
|
-
this.injectedInputs.set(key, createTrueSignal(inputValue ?? true));
|
|
235
|
-
try {
|
|
236
|
-
await this.runFromNodeWithUiPayload(nodeId, inputValue);
|
|
237
|
-
} finally {
|
|
238
|
-
this.injectedInputs.delete(key);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private async runFromNodeWithUiPayload(
|
|
243
|
-
nodeId: string,
|
|
244
|
-
inputValue?: unknown
|
|
245
|
-
) {
|
|
246
|
-
const queue: ExecutionToken[] = [
|
|
247
|
-
{
|
|
248
|
-
tokenId: createTokenId(),
|
|
249
|
-
nodeId,
|
|
250
|
-
nodeType: this.requireNode(nodeId).nodeType,
|
|
251
|
-
inPort: "in",
|
|
252
|
-
scope: cloneScope(),
|
|
253
|
-
},
|
|
254
|
-
];
|
|
255
|
-
queue[0]!.scope.vars.set(UI_EVENT_PAYLOAD_KEY, inputValue);
|
|
256
|
-
|
|
257
|
-
const executor = this.createExecutor(queue);
|
|
258
|
-
|
|
259
|
-
while (queue.length > 0) {
|
|
260
|
-
const token = queue.shift()!;
|
|
261
|
-
if (!this.syncTokenFromGraph(token)) continue;
|
|
262
|
-
try {
|
|
263
|
-
await executor.executeToken(token);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
this.setNodeOutput(
|
|
266
|
-
token.nodeId,
|
|
267
|
-
"out",
|
|
268
|
-
createFalseSignal(
|
|
269
|
-
error instanceof Error ? error.message : String(error)
|
|
270
|
-
)
|
|
271
|
-
);
|
|
272
|
-
const nextNodes = this.findSignalTargets(token.nodeId, "out");
|
|
273
|
-
for (const target of nextNodes) {
|
|
274
|
-
queue.push({
|
|
275
|
-
tokenId: createTokenId(),
|
|
276
|
-
nodeId: target.nodeId,
|
|
277
|
-
nodeType: target.nodeType,
|
|
278
|
-
inPort: target.inPort,
|
|
279
|
-
scope: token.scope,
|
|
280
|
-
correlationId: token.correlationId ?? token.tokenId,
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async emitLifecycle(phase: PageLifecyclePhase): Promise<void> {
|
|
288
|
-
await this.ensureNoBlueprintCycle();
|
|
289
|
-
const signal = createLifecycleSignal(phase);
|
|
290
|
-
const targets = this.graph.nodes.filter(
|
|
291
|
-
(node) =>
|
|
292
|
-
node.nodeType === LIFECYCLE_NODE_TYPE && node.lifecyclePhase === phase
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
for (const node of targets) {
|
|
296
|
-
await this.runFromNode(node.id, signal);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* 嵌套蓝图被引用且父级传入真信号时,触发 blueprintActivated 生命周期节点,
|
|
302
|
-
* 并将父级输入值写入 scope 供节点输出。
|
|
303
|
-
*/
|
|
304
|
-
async emitBlueprintActivated(
|
|
305
|
-
inputValue: unknown,
|
|
306
|
-
parentScope?: Scope
|
|
307
|
-
): Promise<void> {
|
|
308
|
-
const signal = createLifecycleSignal("blueprintActivated");
|
|
309
|
-
const targets = this.graph.nodes.filter(
|
|
310
|
-
(node) =>
|
|
311
|
-
node.nodeType === LIFECYCLE_NODE_TYPE &&
|
|
312
|
-
node.lifecyclePhase === "blueprintActivated"
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
for (const node of targets) {
|
|
316
|
-
await this.runFromNode(node.id, signal, parentScope, inputValue);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* 视图节点触发事件时,匹配已注册(上游生命周期已触发)且绑定了该元素、事件类型的事件节点,发出真信号。
|
|
322
|
-
*/
|
|
323
|
-
async emitViewEvent(
|
|
324
|
-
payload: ViewEventSignal,
|
|
325
|
-
armedNodeIds?: ReadonlySet<string>
|
|
326
|
-
): Promise<void> {
|
|
327
|
-
await this.ensureNoBlueprintCycle();
|
|
328
|
-
const targets = this.graph.nodes.filter((node) => {
|
|
329
|
-
if (node.nodeType !== EVENT_NODE_TYPE) return false;
|
|
330
|
-
if (armedNodeIds && !armedNodeIds.has(node.id)) return false;
|
|
331
|
-
if (!(node.viewElementIds ?? []).includes(payload.node.id)) return false;
|
|
332
|
-
return normalizeViewEventConfig(node.eventConfig).eventTypes.includes(
|
|
333
|
-
payload.eventType
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
for (const node of targets) {
|
|
338
|
-
await this.runFromNode(node.id, undefined, undefined, undefined, payload);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** 时钟节点单次输出:向下游传播含当前时间的真信号 */
|
|
343
|
-
async propagateClockOutput(
|
|
344
|
-
nodeId: string,
|
|
345
|
-
parentScope?: Scope,
|
|
346
|
-
sessionKey?: string
|
|
347
|
-
): Promise<void> {
|
|
348
|
-
const resolvedSessionKey =
|
|
349
|
-
sessionKey ?? buildClockSessionKey(this.clockScopeId(), nodeId);
|
|
350
|
-
if (isClockSessionAborted(resolvedSessionKey)) return;
|
|
351
|
-
|
|
352
|
-
await this.ensureNoBlueprintCycle();
|
|
353
|
-
const node = this.requireNode(nodeId);
|
|
354
|
-
if (node.nodeType !== CLOCK_NODE_TYPE) return;
|
|
355
|
-
|
|
356
|
-
const config = normalizeClockConfig({
|
|
357
|
-
...DEFAULT_CLOCK_NODE_CONFIG,
|
|
358
|
-
...node.clockConfig,
|
|
359
|
-
});
|
|
360
|
-
const value = buildClockSignalValue(config);
|
|
361
|
-
this.setNodeOutput(nodeId, "out", createTrueSignal(value));
|
|
362
|
-
|
|
363
|
-
const queue: ExecutionToken[] = [];
|
|
364
|
-
for (const target of this.findSignalTargets(nodeId, "out")) {
|
|
365
|
-
queue.push({
|
|
366
|
-
tokenId: createTokenId(),
|
|
367
|
-
nodeId: target.nodeId,
|
|
368
|
-
nodeType: target.nodeType,
|
|
369
|
-
inPort: target.inPort,
|
|
370
|
-
scope: cloneScope(parentScope),
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const executor = this.createExecutor(queue);
|
|
375
|
-
while (queue.length > 0) {
|
|
376
|
-
if (isClockSessionAborted(resolvedSessionKey)) {
|
|
377
|
-
queue.length = 0;
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const token = queue.shift()!;
|
|
381
|
-
if (!this.syncTokenFromGraph(token)) continue;
|
|
382
|
-
try {
|
|
383
|
-
await executor.executeToken(token);
|
|
384
|
-
} catch (error) {
|
|
385
|
-
this.setNodeOutput(
|
|
386
|
-
token.nodeId,
|
|
387
|
-
"out",
|
|
388
|
-
createFalseSignal(
|
|
389
|
-
error instanceof Error ? error.message : String(error)
|
|
390
|
-
)
|
|
391
|
-
);
|
|
392
|
-
const nextNodes = this.findSignalTargets(token.nodeId, "out");
|
|
393
|
-
for (const target of nextNodes) {
|
|
394
|
-
queue.push({
|
|
395
|
-
tokenId: createTokenId(),
|
|
396
|
-
nodeId: target.nodeId,
|
|
397
|
-
nodeType: target.nodeType,
|
|
398
|
-
inPort: target.inPort,
|
|
399
|
-
scope: token.scope,
|
|
400
|
-
correlationId: token.correlationId ?? token.tokenId,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
private clockScopeId(): string {
|
|
408
|
-
return this.rootLibraryBlueprintId ?? "local";
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private startClockSchedule(nodeId: string, config: ClockNodeConfig, scope?: Scope) {
|
|
412
|
-
const sessionKey = buildClockSessionKey(this.clockScopeId(), nodeId);
|
|
413
|
-
markClockNodeActive(nodeId);
|
|
414
|
-
scheduleClockOutputs({
|
|
415
|
-
sessionKey,
|
|
416
|
-
config,
|
|
417
|
-
onTick: async () => {
|
|
418
|
-
if (isClockSessionAborted(sessionKey)) return;
|
|
419
|
-
await this.propagateClockOutput(nodeId, scope, sessionKey);
|
|
420
|
-
},
|
|
421
|
-
onSettled: () => markClockNodeInactive(nodeId),
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** 中止指定时钟节点:停止剩余 tick 与正在执行的下游任务 */
|
|
426
|
-
abortClockNode(nodeId: string): void {
|
|
427
|
-
abortClockNodeSession(this.clockScopeId(), nodeId);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/** 从所有无入流 signal 的节点开始执行(嵌套蓝图调用) */
|
|
431
|
-
async runFromFlowEntries(
|
|
432
|
-
parentScope?: Scope,
|
|
433
|
-
options?: { preserveOutputs?: boolean }
|
|
434
|
-
): Promise<BlueprintNodeOutputs> {
|
|
435
|
-
if (!options?.preserveOutputs) {
|
|
436
|
-
this.outputs.clear();
|
|
437
|
-
}
|
|
438
|
-
const entries = this.findSignalEntryNodes();
|
|
439
|
-
for (const node of entries) {
|
|
440
|
-
await this.runFromNode(node.id, undefined, parentScope);
|
|
441
|
-
}
|
|
442
|
-
return this.collectOutputs();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
getNodeOutputs(): BlueprintNodeOutputs {
|
|
446
|
-
return this.collectOutputs();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
getTraceEntries(): ExecutionTraceEntry[] {
|
|
450
|
-
return [...this.traceEntries];
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
isDebugSessionActive(): boolean {
|
|
454
|
-
return this.debugQueue !== null;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
isDebugQueueEmpty(): boolean {
|
|
458
|
-
return !this.debugQueue || this.debugQueue.length === 0;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/** 调试过程中同步最新蓝图拓扑与节点配置 */
|
|
462
|
-
updateGraph(graph: RunnableGraph): void {
|
|
463
|
-
this.graph = graph;
|
|
464
|
-
this.syncDebugQueueWithLatestGraph();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* 按当前图结构重建待执行队列。
|
|
469
|
-
* 已有 trace 时,从最后执行的节点按最新出边重新入队;便于编辑连线后继续单步调试。
|
|
470
|
-
*/
|
|
471
|
-
refreshPendingQueueFromLatestGraph(): void {
|
|
472
|
-
if (!this.debugQueue) return;
|
|
473
|
-
|
|
474
|
-
if (this.traceEntries.length === 0) {
|
|
475
|
-
this.syncDebugQueueWithLatestGraph();
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const lastEntry = this.traceEntries[this.traceEntries.length - 1]!;
|
|
480
|
-
const lastOutput = this.getNodeOutput(lastEntry.nodeId, "out");
|
|
481
|
-
if (
|
|
482
|
-
lastOutput !== undefined &&
|
|
483
|
-
isFalseSignal(lastOutput) &&
|
|
484
|
-
!this.graph.allowFalseSignalPropagation
|
|
485
|
-
) {
|
|
486
|
-
this.debugQueue = [];
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const scope =
|
|
491
|
-
this.debugQueue[0]?.scope ??
|
|
492
|
-
this.debugHistory[0]?.queue[0]?.scope ??
|
|
493
|
-
cloneScope();
|
|
494
|
-
|
|
495
|
-
const node = this.graph.nodes.find((n) => n.id === lastEntry.nodeId);
|
|
496
|
-
if (!node) {
|
|
497
|
-
this.debugQueue = [];
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
let def;
|
|
502
|
-
try {
|
|
503
|
-
def = getNodeDefinition(node.nodeType);
|
|
504
|
-
} catch {
|
|
505
|
-
this.debugQueue = [];
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const newQueue: ExecutionToken[] = [];
|
|
510
|
-
for (const port of def.outputs) {
|
|
511
|
-
if (this.getNodeOutput(lastEntry.nodeId, port.name) === undefined) {
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
for (const target of this.findSignalTargets(lastEntry.nodeId, port.name)) {
|
|
515
|
-
newQueue.push({
|
|
516
|
-
tokenId: createTokenId(),
|
|
517
|
-
nodeId: target.nodeId,
|
|
518
|
-
nodeType: target.nodeType,
|
|
519
|
-
inPort: target.inPort,
|
|
520
|
-
scope: cloneScope(scope),
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (newQueue.length > 0 || this.debugQueue.length === 0) {
|
|
525
|
-
this.debugQueue = newQueue;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
canDebugStepBack(): boolean {
|
|
530
|
-
return this.debugHistory.length > 1;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
debugStepBack(): boolean {
|
|
534
|
-
if (!this.debugQueue || this.debugHistory.length <= 1) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
this.debugHistory.pop();
|
|
538
|
-
const snapshot = this.debugHistory[this.debugHistory.length - 1]!;
|
|
539
|
-
this.restoreDebugSnapshot(snapshot);
|
|
540
|
-
return true;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
private captureDebugSnapshot(): DebugSessionSnapshot {
|
|
544
|
-
return {
|
|
545
|
-
outputs: cloneOutputs(this.outputs),
|
|
546
|
-
queue: cloneQueue(this.debugQueue ?? []),
|
|
547
|
-
traceEntries: [...this.traceEntries],
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
private restoreDebugSnapshot(snapshot: DebugSessionSnapshot): void {
|
|
552
|
-
this.outputs = cloneOutputs(snapshot.outputs);
|
|
553
|
-
this.debugQueue = cloneQueue(snapshot.queue);
|
|
554
|
-
this.traceEntries = [...snapshot.traceEntries];
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
private pushDebugSnapshot(): void {
|
|
558
|
-
this.debugHistory.push(this.captureDebugSnapshot());
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
beginDebugSession(lifecycleNodeId: string): void {
|
|
562
|
-
stopAllClockSchedules();
|
|
563
|
-
const node = this.requireNode(lifecycleNodeId);
|
|
564
|
-
if (node.nodeType !== LIFECYCLE_NODE_TYPE) {
|
|
565
|
-
throw new Error("调试会话只能从生命周期节点开始");
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
this.outputs.clear();
|
|
569
|
-
this.traceEntries = [];
|
|
570
|
-
this.debugQueue = [
|
|
571
|
-
{
|
|
572
|
-
tokenId: createTokenId(),
|
|
573
|
-
nodeId: lifecycleNodeId,
|
|
574
|
-
nodeType: node.nodeType,
|
|
575
|
-
inPort: "in",
|
|
576
|
-
scope: cloneScope(),
|
|
577
|
-
},
|
|
578
|
-
];
|
|
579
|
-
|
|
580
|
-
this.applyLifecycleScopeForNode(lifecycleNodeId);
|
|
581
|
-
|
|
582
|
-
this.debugExecutor = this.createExecutor(this.debugQueue, {
|
|
583
|
-
emitToActiveDebugQueue: true,
|
|
584
|
-
});
|
|
585
|
-
this.debugHistory = [this.captureDebugSnapshot()];
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** 调试中修改生命周期阶段后,同步队列头部的 scope 信号 */
|
|
589
|
-
refreshDebugLifecycleScope(lifecycleNodeId: string): void {
|
|
590
|
-
const head = this.debugQueue?.[0];
|
|
591
|
-
if (!head || head.nodeId !== lifecycleNodeId) return;
|
|
592
|
-
this.applyLifecycleScopeForNode(lifecycleNodeId);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
private applyLifecycleScopeForNode(lifecycleNodeId: string): void {
|
|
596
|
-
const head = this.debugQueue?.[0];
|
|
597
|
-
if (!head || head.nodeId !== lifecycleNodeId) return;
|
|
598
|
-
|
|
599
|
-
const node = this.graph.nodes.find((n) => n.id === lifecycleNodeId);
|
|
600
|
-
if (!node?.lifecyclePhase) {
|
|
601
|
-
head.scope.vars.delete(LIFECYCLE_SIGNAL_KEY);
|
|
602
|
-
head.scope.vars.delete(BLUEPRINT_ACTIVATION_INPUT_KEY);
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
head.scope.vars.set(
|
|
607
|
-
LIFECYCLE_SIGNAL_KEY,
|
|
608
|
-
createLifecycleSignal(node.lifecyclePhase)
|
|
609
|
-
);
|
|
610
|
-
if (node.lifecyclePhase === "blueprintActivated") {
|
|
611
|
-
head.scope.vars.set(BLUEPRINT_ACTIVATION_INPUT_KEY, undefined);
|
|
612
|
-
} else {
|
|
613
|
-
head.scope.vars.delete(BLUEPRINT_ACTIVATION_INPUT_KEY);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
clearDebugSession(): void {
|
|
618
|
-
stopAllClockSchedules();
|
|
619
|
-
this.debugQueue = null;
|
|
620
|
-
this.debugExecutor = null;
|
|
621
|
-
this.traceEntries = [];
|
|
622
|
-
this.outputs.clear();
|
|
623
|
-
this.debugHistory = [];
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
async debugStep(
|
|
627
|
-
nodeLabelById?: Record<string, string>,
|
|
628
|
-
onProgress?: DebugProgressCallback
|
|
629
|
-
): Promise<{
|
|
630
|
-
done: boolean;
|
|
631
|
-
entry?: ExecutionTraceEntry;
|
|
632
|
-
haltedByFalseSignal?: boolean;
|
|
633
|
-
}> {
|
|
634
|
-
if (!this.debugQueue || !this.debugExecutor) {
|
|
635
|
-
return { done: true };
|
|
636
|
-
}
|
|
637
|
-
if (this.debugQueue.length === 0) {
|
|
638
|
-
return { done: true };
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const prevCallback = this.debugProgressCallback;
|
|
642
|
-
this.debugProgressCallback = onProgress ?? null;
|
|
643
|
-
|
|
644
|
-
try {
|
|
645
|
-
this.pushDebugSnapshot();
|
|
646
|
-
const token = this.debugQueue.shift()!;
|
|
647
|
-
if (!this.syncTokenFromGraph(token)) {
|
|
648
|
-
this.pushDebugSnapshot();
|
|
649
|
-
return {
|
|
650
|
-
done: this.debugQueue.length === 0,
|
|
651
|
-
haltedByFalseSignal: false,
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (token.nodeType === CLOCK_NODE_TYPE) {
|
|
656
|
-
const node = this.requireNode(token.nodeId);
|
|
657
|
-
const config = normalizeClockConfig({
|
|
658
|
-
...DEFAULT_CLOCK_NODE_CONFIG,
|
|
659
|
-
...node.clockConfig,
|
|
660
|
-
});
|
|
661
|
-
const halted = await this.executeClockScheduleForDebug(
|
|
662
|
-
token,
|
|
663
|
-
config,
|
|
664
|
-
nodeLabelById
|
|
665
|
-
);
|
|
666
|
-
this.haltDebugQueueOnFalseOutput(token.nodeId);
|
|
667
|
-
this.pushDebugSnapshot();
|
|
668
|
-
return {
|
|
669
|
-
done: this.debugQueue.length === 0,
|
|
670
|
-
entry: this.traceEntries[this.traceEntries.length - 1],
|
|
671
|
-
haltedByFalseSignal:
|
|
672
|
-
halted || this.isDebugHaltedByFalseOutput(token.nodeId),
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const entry = await this.executeDebugToken(token, nodeLabelById);
|
|
677
|
-
this.haltDebugQueueOnFalseOutput(token.nodeId);
|
|
678
|
-
this.pushDebugSnapshot();
|
|
679
|
-
|
|
680
|
-
return {
|
|
681
|
-
done: this.debugQueue.length === 0,
|
|
682
|
-
entry,
|
|
683
|
-
haltedByFalseSignal: this.isDebugHaltedByFalseOutput(token.nodeId),
|
|
684
|
-
};
|
|
685
|
-
} finally {
|
|
686
|
-
this.debugProgressCallback = prevCallback;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
private notifyDebugProgress(): void {
|
|
691
|
-
this.debugProgressCallback?.([...this.traceEntries]);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
private async awaitClockDelay(ms: number, sessionKey?: string): Promise<void> {
|
|
695
|
-
if (ms <= 0) return;
|
|
696
|
-
const step = 50;
|
|
697
|
-
let elapsed = 0;
|
|
698
|
-
while (elapsed < ms) {
|
|
699
|
-
if (sessionKey && isClockSessionAborted(sessionKey)) return;
|
|
700
|
-
const chunk = Math.min(step, ms - elapsed);
|
|
701
|
-
await new Promise<void>((resolve) => setTimeout(resolve, chunk));
|
|
702
|
-
elapsed += chunk;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
private buildTraceInputsForNode(
|
|
707
|
-
nodeId: string,
|
|
708
|
-
nodeType: string
|
|
709
|
-
): Record<string, unknown> {
|
|
710
|
-
const def = getNodeDefinition(nodeType);
|
|
711
|
-
const inputs: Record<string, unknown> = {};
|
|
712
|
-
for (const port of def.inputs) {
|
|
713
|
-
inputs[port.name] = serializeTraceValue(
|
|
714
|
-
this.resolvePortInputValue(nodeId, port.name)
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
return inputs;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
private appendDebugTraceEntry(
|
|
721
|
-
token: ExecutionToken,
|
|
722
|
-
nodeLabelById: Record<string, string> | undefined,
|
|
723
|
-
startedAt: number,
|
|
724
|
-
options?: {
|
|
725
|
-
tickLabel?: string;
|
|
726
|
-
clockTickIndex?: number;
|
|
727
|
-
clockTickTotal?: number;
|
|
728
|
-
inputs?: Record<string, unknown>;
|
|
729
|
-
error?: string;
|
|
730
|
-
}
|
|
731
|
-
): ExecutionTraceEntry {
|
|
732
|
-
const def = getNodeDefinition(token.nodeType);
|
|
733
|
-
const outputs: Record<string, unknown> = {};
|
|
734
|
-
for (const port of def.outputs) {
|
|
735
|
-
outputs[port.name] = serializeTraceValue(
|
|
736
|
-
this.getNodeOutput(token.nodeId, port.name)
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const entry: ExecutionTraceEntry = {
|
|
741
|
-
id: createTraceEntryId(),
|
|
742
|
-
nodeId: token.nodeId,
|
|
743
|
-
nodeLabel: options?.tickLabel ?? nodeLabelById?.[token.nodeId],
|
|
744
|
-
nodeType: token.nodeType,
|
|
745
|
-
startedAt,
|
|
746
|
-
finishedAt: Date.now(),
|
|
747
|
-
isoTime: new Date(startedAt).toISOString(),
|
|
748
|
-
inputs:
|
|
749
|
-
options?.inputs ??
|
|
750
|
-
this.buildTraceInputsForNode(token.nodeId, token.nodeType),
|
|
751
|
-
outputs,
|
|
752
|
-
error: options?.error,
|
|
753
|
-
clockTickIndex: options?.clockTickIndex,
|
|
754
|
-
clockTickTotal: options?.clockTickTotal,
|
|
755
|
-
};
|
|
756
|
-
this.traceEntries.push(entry);
|
|
757
|
-
this.notifyDebugProgress();
|
|
758
|
-
return entry;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private enqueueDebugTargets(
|
|
762
|
-
sourceNodeId: string,
|
|
763
|
-
outPort: string,
|
|
764
|
-
scope: Scope
|
|
765
|
-
): ExecutionToken[] {
|
|
766
|
-
return this.findSignalTargets(sourceNodeId, outPort).map((target) => ({
|
|
767
|
-
tokenId: createTokenId(),
|
|
768
|
-
nodeId: target.nodeId,
|
|
769
|
-
nodeType: target.nodeType,
|
|
770
|
-
inPort: target.inPort,
|
|
771
|
-
scope: cloneScope(scope),
|
|
772
|
-
}));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
private async runDebugTokenSubqueue(
|
|
776
|
-
queue: ExecutionToken[],
|
|
777
|
-
nodeLabelById?: Record<string, string>,
|
|
778
|
-
abortSessionKey?: string
|
|
779
|
-
): Promise<boolean> {
|
|
780
|
-
const executor = this.createExecutor(queue);
|
|
781
|
-
|
|
782
|
-
while (queue.length > 0) {
|
|
783
|
-
if (abortSessionKey && isClockSessionAborted(abortSessionKey)) {
|
|
784
|
-
queue.length = 0;
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const token = queue.shift()!;
|
|
789
|
-
|
|
790
|
-
if (token.nodeType === CLOCK_NODE_TYPE) {
|
|
791
|
-
const node = this.requireNode(token.nodeId);
|
|
792
|
-
const config = normalizeClockConfig({
|
|
793
|
-
...DEFAULT_CLOCK_NODE_CONFIG,
|
|
794
|
-
...node.clockConfig,
|
|
795
|
-
});
|
|
796
|
-
const halted = await this.executeClockScheduleForDebug(
|
|
797
|
-
token,
|
|
798
|
-
config,
|
|
799
|
-
nodeLabelById
|
|
800
|
-
);
|
|
801
|
-
if (halted) return true;
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
await this.executeDebugToken(token, nodeLabelById, executor, queue);
|
|
806
|
-
if (abortSessionKey && isClockSessionAborted(abortSessionKey)) {
|
|
807
|
-
queue.length = 0;
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
if (this.isDebugHaltedByFalseOutput(token.nodeId)) {
|
|
811
|
-
return true;
|
|
812
|
-
}
|
|
813
|
-
this.pushDebugSnapshot();
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
return false;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
private async executeDebugToken(
|
|
820
|
-
token: ExecutionToken,
|
|
821
|
-
nodeLabelById?: Record<string, string>,
|
|
822
|
-
executor?: Executor,
|
|
823
|
-
emitQueue?: ExecutionToken[]
|
|
824
|
-
): Promise<ExecutionTraceEntry> {
|
|
825
|
-
const startedAt = Date.now();
|
|
826
|
-
let error: string | undefined;
|
|
827
|
-
|
|
828
|
-
const runExecutor =
|
|
829
|
-
executor ??
|
|
830
|
-
this.debugExecutor ??
|
|
831
|
-
this.createExecutor(this.debugQueue ?? []);
|
|
832
|
-
|
|
833
|
-
if (!this.syncTokenFromGraph(token)) {
|
|
834
|
-
return this.appendDebugTraceEntry(token, nodeLabelById, startedAt, {
|
|
835
|
-
error: "节点已从蓝图中移除",
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
try {
|
|
840
|
-
await runExecutor.executeToken(token);
|
|
841
|
-
} catch (err) {
|
|
842
|
-
error = err instanceof Error ? err.message : String(err);
|
|
843
|
-
this.setNodeOutput(
|
|
844
|
-
token.nodeId,
|
|
845
|
-
"out",
|
|
846
|
-
createFalseSignal(error)
|
|
847
|
-
);
|
|
848
|
-
const targets = emitQueue ?? this.debugQueue;
|
|
849
|
-
if (targets) {
|
|
850
|
-
for (const next of this.enqueueDebugTargets(
|
|
851
|
-
token.nodeId,
|
|
852
|
-
"out",
|
|
853
|
-
token.scope
|
|
854
|
-
)) {
|
|
855
|
-
targets.push(next);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
return this.appendDebugTraceEntry(token, nodeLabelById, startedAt, {
|
|
861
|
-
error,
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* 调试模式下执行完整时钟序列:等待 n 次输出(含间隔),
|
|
867
|
-
* 每次更新 trace / 画布高亮,并同步跑完当次下游链。
|
|
868
|
-
*/
|
|
869
|
-
private async executeClockScheduleForDebug(
|
|
870
|
-
token: ExecutionToken,
|
|
871
|
-
config: ClockNodeConfig,
|
|
872
|
-
nodeLabelById?: Record<string, string>
|
|
873
|
-
): Promise<boolean> {
|
|
874
|
-
const sessionKey = buildClockSessionKey(this.clockScopeId(), token.nodeId);
|
|
875
|
-
clearClockSessionAbort(sessionKey);
|
|
876
|
-
markClockNodeActive(token.nodeId);
|
|
877
|
-
try {
|
|
878
|
-
const normalized = normalizeClockConfig(config);
|
|
879
|
-
const baseInputs = this.buildTraceInputsForNode(
|
|
880
|
-
token.nodeId,
|
|
881
|
-
token.nodeType
|
|
882
|
-
);
|
|
883
|
-
const input = this.resolvePortInputValue(token.nodeId, "in");
|
|
884
|
-
|
|
885
|
-
const finishWithOutput = async (
|
|
886
|
-
output: Value,
|
|
887
|
-
tickLabel?: string
|
|
888
|
-
): Promise<boolean> => {
|
|
889
|
-
this.setNodeOutput(token.nodeId, "out", output);
|
|
890
|
-
this.appendDebugTraceEntry(token, nodeLabelById, Date.now(), {
|
|
891
|
-
tickLabel,
|
|
892
|
-
inputs: baseInputs,
|
|
893
|
-
});
|
|
894
|
-
this.pushDebugSnapshot();
|
|
895
|
-
const subQueue = this.enqueueDebugTargets(
|
|
896
|
-
token.nodeId,
|
|
897
|
-
"out",
|
|
898
|
-
token.scope
|
|
899
|
-
);
|
|
900
|
-
return this.runDebugTokenSubqueue(subQueue, nodeLabelById, sessionKey);
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
if (isFalseSignal(input)) {
|
|
904
|
-
return finishWithOutput(input);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (!isTrueSignal(input)) {
|
|
908
|
-
return finishWithOutput(
|
|
909
|
-
createFalseSignal("时钟节点需要收到真信号后才会启动")
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const validation = validateClockScheduleConfig(normalized);
|
|
914
|
-
if (!validation.ok) {
|
|
915
|
-
return finishWithOutput(createFalseSignal(validation.error));
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const baseLabel = nodeLabelById?.[token.nodeId] ?? token.nodeId;
|
|
919
|
-
const scheduleStartMs = Date.now();
|
|
920
|
-
|
|
921
|
-
for (let i = 0; i < normalized.outputCount; i++) {
|
|
922
|
-
if (isClockSessionAborted(sessionKey)) return false;
|
|
923
|
-
|
|
924
|
-
const waitMs = getClockTickWaitMs(normalized, i, scheduleStartMs);
|
|
925
|
-
if (waitMs > 0) {
|
|
926
|
-
await this.awaitClockDelay(waitMs, sessionKey);
|
|
927
|
-
}
|
|
928
|
-
if (isClockSessionAborted(sessionKey)) return false;
|
|
929
|
-
|
|
930
|
-
const tickStarted = Date.now();
|
|
931
|
-
const value = buildClockSignalValue(normalized);
|
|
932
|
-
this.setNodeOutput(token.nodeId, "out", createTrueSignal(value));
|
|
933
|
-
|
|
934
|
-
const tickLabel =
|
|
935
|
-
normalized.outputCount > 1
|
|
936
|
-
? `${baseLabel} (${i + 1}/${normalized.outputCount})`
|
|
937
|
-
: baseLabel;
|
|
938
|
-
|
|
939
|
-
this.appendDebugTraceEntry(token, nodeLabelById, tickStarted, {
|
|
940
|
-
tickLabel,
|
|
941
|
-
inputs: baseInputs,
|
|
942
|
-
clockTickIndex: i + 1,
|
|
943
|
-
clockTickTotal: normalized.outputCount,
|
|
944
|
-
});
|
|
945
|
-
this.pushDebugSnapshot();
|
|
946
|
-
|
|
947
|
-
const subQueue = this.enqueueDebugTargets(
|
|
948
|
-
token.nodeId,
|
|
949
|
-
"out",
|
|
950
|
-
token.scope
|
|
951
|
-
);
|
|
952
|
-
const halted = await this.runDebugTokenSubqueue(
|
|
953
|
-
subQueue,
|
|
954
|
-
nodeLabelById,
|
|
955
|
-
sessionKey
|
|
956
|
-
);
|
|
957
|
-
if (halted || isClockSessionAborted(sessionKey)) return halted;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
return false;
|
|
961
|
-
} finally {
|
|
962
|
-
markClockNodeInactive(token.nodeId);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
isDebugHaltedByFalseOutput(nodeId?: string): boolean {
|
|
967
|
-
if (this.graph.allowFalseSignalPropagation) return false;
|
|
968
|
-
const targetId = nodeId ?? this.traceEntries[this.traceEntries.length - 1]?.nodeId;
|
|
969
|
-
if (!targetId) return false;
|
|
970
|
-
return this.isFalseOutputNode(targetId);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
private isFalseOutputNode(nodeId: string): boolean {
|
|
974
|
-
const out = this.getNodeOutput(nodeId, "out");
|
|
975
|
-
return out !== undefined && isFalseSignal(out);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
private haltDebugQueueOnFalseOutput(nodeId: string): void {
|
|
979
|
-
if (!this.debugQueue || this.graph.allowFalseSignalPropagation) return;
|
|
980
|
-
if (this.isFalseOutputNode(nodeId)) {
|
|
981
|
-
this.debugQueue = [];
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
async debugRunAll(
|
|
986
|
-
nodeLabelById?: Record<string, string>,
|
|
987
|
-
onProgress?: DebugProgressCallback
|
|
988
|
-
): Promise<ExecutionTraceEntry[]> {
|
|
989
|
-
while (this.debugQueue && this.debugQueue.length > 0) {
|
|
990
|
-
await this.debugStep(nodeLabelById, onProgress);
|
|
991
|
-
}
|
|
992
|
-
return this.getTraceEntries();
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
private registerBlueprintRefBehavior() {
|
|
996
|
-
const runner = this;
|
|
997
|
-
this.behaviors.registerJS("blueprint-ref-run", async ({ token, io }) => {
|
|
998
|
-
const node = runner.requireNode(token.nodeId);
|
|
999
|
-
|
|
1000
|
-
try {
|
|
1001
|
-
const input = await io.getInput("in");
|
|
1002
|
-
if (!isTrueSignal(input)) {
|
|
1003
|
-
io.setOutput(
|
|
1004
|
-
"out",
|
|
1005
|
-
createFalseSignal("蓝图节点需要收到真信号后才会执行")
|
|
1006
|
-
);
|
|
1007
|
-
io.emitFlow("out");
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const libraryId = node.libraryBlueprintId;
|
|
1012
|
-
if (!libraryId) {
|
|
1013
|
-
io.setOutput("out", createFalseSignal("未选择蓝图库中的蓝图"));
|
|
1014
|
-
io.emitFlow("out");
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
if (!runner.resolveLibraryBlueprint) {
|
|
1019
|
-
io.setOutput("out", createFalseSignal("蓝图库解析器未配置"));
|
|
1020
|
-
io.emitFlow("out");
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
const scopeStack = token.scope.vars.get(BLUEPRINT_CALL_STACK_KEY) as
|
|
1025
|
-
| string[]
|
|
1026
|
-
| undefined;
|
|
1027
|
-
const callStack = resolveBlueprintCallStack(
|
|
1028
|
-
scopeStack,
|
|
1029
|
-
runner.rootLibraryBlueprintId
|
|
1030
|
-
);
|
|
1031
|
-
const runtimeCycle = detectRuntimeBlueprintCycle(
|
|
1032
|
-
callStack,
|
|
1033
|
-
libraryId,
|
|
1034
|
-
runner.resolveBlueprintName
|
|
1035
|
-
);
|
|
1036
|
-
if (!runtimeCycle.ok) {
|
|
1037
|
-
io.setOutput("out", createFalseSignal(runtimeCycle.message));
|
|
1038
|
-
io.emitFlow("out");
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const nestedGraph = await runner.resolveLibraryBlueprint(libraryId);
|
|
1043
|
-
if (!nestedGraph) {
|
|
1044
|
-
io.setOutput(
|
|
1045
|
-
"out",
|
|
1046
|
-
createFalseSignal(`蓝图库记录不存在: ${libraryId}`)
|
|
1047
|
-
);
|
|
1048
|
-
io.emitFlow("out");
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const nestedScope = cloneScope(token.scope);
|
|
1053
|
-
nestedScope.vars.set(BLUEPRINT_CALL_STACK_KEY, [...callStack, libraryId]);
|
|
1054
|
-
|
|
1055
|
-
const subRunner = new BlueprintGraphRunner(nestedGraph, {
|
|
1056
|
-
resolveLibraryBlueprint: runner.resolveLibraryBlueprint,
|
|
1057
|
-
rootLibraryBlueprintId: libraryId,
|
|
1058
|
-
resolveBlueprintName: runner.resolveBlueprintName,
|
|
1059
|
-
onViewScopeUpdate: runner.onViewScopeUpdate,
|
|
1060
|
-
});
|
|
1061
|
-
await subRunner.emitBlueprintActivated(input.value, nestedScope);
|
|
1062
|
-
const nestedOutputs = await subRunner.runFromFlowEntries(nestedScope, {
|
|
1063
|
-
preserveOutputs: true,
|
|
1064
|
-
});
|
|
1065
|
-
|
|
1066
|
-
const payload: BlueprintRefOutput = {
|
|
1067
|
-
nodeId: token.nodeId,
|
|
1068
|
-
nodeType: node.nodeType,
|
|
1069
|
-
libraryBlueprintId: libraryId,
|
|
1070
|
-
blueprintName:
|
|
1071
|
-
node.blueprintName ?? nestedGraph.nodes[0]?.blueprintName,
|
|
1072
|
-
nestedOutputs,
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
io.setOutput("out", createTrueSignal(payload));
|
|
1076
|
-
} catch (error) {
|
|
1077
|
-
io.setOutput(
|
|
1078
|
-
"out",
|
|
1079
|
-
createFalseSignal(
|
|
1080
|
-
error instanceof Error ? error.message : String(error)
|
|
1081
|
-
)
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
io.emitFlow("out");
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
private registerFetchBehavior() {
|
|
1089
|
-
const runner = this;
|
|
1090
|
-
this.behaviors.registerJS("fetch-run", async ({ token, io }) => {
|
|
1091
|
-
const node = runner.requireNode(token.nodeId);
|
|
1092
|
-
|
|
1093
|
-
try {
|
|
1094
|
-
const inputPort = token.inPort || "in";
|
|
1095
|
-
const input = await io.getInput(inputPort);
|
|
1096
|
-
if (isFalseSignal(input)) {
|
|
1097
|
-
io.setOutput("out", input);
|
|
1098
|
-
io.emitFlow("out");
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
if (!isTrueSignal(input)) {
|
|
1102
|
-
io.setOutput(
|
|
1103
|
-
"out",
|
|
1104
|
-
createFalseSignal(
|
|
1105
|
-
input === undefined
|
|
1106
|
-
? "数据源节点未收到上游输出,请确认连线与上游节点已执行"
|
|
1107
|
-
: "数据源节点需要收到真信号后才会发起请求"
|
|
1108
|
-
)
|
|
1109
|
-
);
|
|
1110
|
-
io.emitFlow("out");
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
const incomingScope = runner.buildFetchIncomingScope(
|
|
1115
|
-
token.nodeId,
|
|
1116
|
-
inputPort,
|
|
1117
|
-
input
|
|
1118
|
-
);
|
|
1119
|
-
const config = applyFetchConfigScope(
|
|
1120
|
-
normalizeFetchRequestConfig(node.fetchConfig),
|
|
1121
|
-
incomingScope
|
|
1122
|
-
);
|
|
1123
|
-
const headersError = getFetchHeadersValidationError(config, incomingScope);
|
|
1124
|
-
if (headersError) {
|
|
1125
|
-
throw new Error(headersError);
|
|
1126
|
-
}
|
|
1127
|
-
const bodyError = getFetchBodyValidationError(config.body, config.body);
|
|
1128
|
-
if (bodyError) {
|
|
1129
|
-
throw new Error(`请求体不是有效 JSON:${bodyError}`);
|
|
1130
|
-
}
|
|
1131
|
-
resolveFetchRequestUrl(config);
|
|
1132
|
-
|
|
1133
|
-
const result = await executeFetch(config, {
|
|
1134
|
-
allowHttpError: runner.isDebugSessionActive(),
|
|
1135
|
-
});
|
|
1136
|
-
io.setOutput("out", createTrueSignal(result));
|
|
1137
|
-
} catch (error) {
|
|
1138
|
-
io.setOutput(
|
|
1139
|
-
"out",
|
|
1140
|
-
createFalseSignal(
|
|
1141
|
-
error instanceof Error ? error.message : String(error)
|
|
1142
|
-
)
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
io.emitFlow("out");
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
private registerLogicBehavior() {
|
|
1150
|
-
const runner = this;
|
|
1151
|
-
this.behaviors.registerJS("logic-run", async ({ token, io }) => {
|
|
1152
|
-
const node = runner.requireNode(token.nodeId);
|
|
1153
|
-
|
|
1154
|
-
try {
|
|
1155
|
-
const input = await io.getInput("in");
|
|
1156
|
-
if (isFalseSignal(input)) {
|
|
1157
|
-
io.setOutput("out", input);
|
|
1158
|
-
io.emitFlow("out");
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
const inputValue = isTrueSignal(input) ? input.value : undefined;
|
|
1163
|
-
const config: LogicNodeConfig = {
|
|
1164
|
-
...DEFAULT_LOGIC_NODE_CONFIG,
|
|
1165
|
-
...node.logicConfig,
|
|
1166
|
-
};
|
|
1167
|
-
const result = executeLogicConfig(config, inputValue);
|
|
1168
|
-
io.setOutput("out", createTrueSignal(result));
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
io.setOutput(
|
|
1171
|
-
"out",
|
|
1172
|
-
createFalseSignal(
|
|
1173
|
-
error instanceof Error ? error.message : String(error)
|
|
1174
|
-
)
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
io.emitFlow("out");
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
private registerJsonBehavior() {
|
|
1182
|
-
const runner = this;
|
|
1183
|
-
this.behaviors.registerJS("json-parse-run", async ({ token, io }) => {
|
|
1184
|
-
const node = runner.requireNode(token.nodeId);
|
|
1185
|
-
|
|
1186
|
-
try {
|
|
1187
|
-
const input = await io.getInput("in");
|
|
1188
|
-
if (!isTrueSignal(input)) {
|
|
1189
|
-
io.setOutput(
|
|
1190
|
-
"out",
|
|
1191
|
-
createFalseSignal("JSON 节点需要收到真信号后才会解析")
|
|
1192
|
-
);
|
|
1193
|
-
io.emitFlow("out");
|
|
1194
|
-
return;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
const config: JsonNodeConfig = {
|
|
1198
|
-
...DEFAULT_JSON_NODE_CONFIG,
|
|
1199
|
-
...node.jsonConfig,
|
|
1200
|
-
};
|
|
1201
|
-
const value = parseJsonConfig(config);
|
|
1202
|
-
io.setOutput("out", createTrueSignal(value));
|
|
1203
|
-
} catch (error) {
|
|
1204
|
-
io.setOutput(
|
|
1205
|
-
"out",
|
|
1206
|
-
createFalseSignal(
|
|
1207
|
-
error instanceof Error ? error.message : String(error)
|
|
1208
|
-
)
|
|
1209
|
-
);
|
|
1210
|
-
}
|
|
1211
|
-
io.emitFlow("out");
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
private registerClockBehavior() {
|
|
1216
|
-
const runner = this;
|
|
1217
|
-
this.behaviors.registerJS("clock-run", async ({ token, io }) => {
|
|
1218
|
-
const node = runner.requireNode(token.nodeId);
|
|
1219
|
-
const config = normalizeClockConfig({
|
|
1220
|
-
...DEFAULT_CLOCK_NODE_CONFIG,
|
|
1221
|
-
...node.clockConfig,
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
try {
|
|
1225
|
-
const input = await io.getInput("in");
|
|
1226
|
-
if (isFalseSignal(input)) {
|
|
1227
|
-
io.setOutput("out", input);
|
|
1228
|
-
io.emitFlow("out");
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
if (!isTrueSignal(input)) {
|
|
1232
|
-
io.setOutput(
|
|
1233
|
-
"out",
|
|
1234
|
-
createFalseSignal("时钟节点需要收到真信号后才会启动")
|
|
1235
|
-
);
|
|
1236
|
-
io.emitFlow("out");
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
const validation = validateClockScheduleConfig(config);
|
|
1241
|
-
if (!validation.ok) {
|
|
1242
|
-
io.setOutput("out", createFalseSignal(validation.error));
|
|
1243
|
-
io.emitFlow("out");
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
if (runner.isDebugSessionActive()) {
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
runner.startClockSchedule(token.nodeId, config, token.scope);
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
io.setOutput(
|
|
1254
|
-
"out",
|
|
1255
|
-
createFalseSignal(
|
|
1256
|
-
error instanceof Error ? error.message : String(error)
|
|
1257
|
-
)
|
|
1258
|
-
);
|
|
1259
|
-
io.emitFlow("out");
|
|
1260
|
-
}
|
|
1261
|
-
});
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
private registerAndBehavior() {
|
|
1265
|
-
this.behaviors.registerJS("and-run", async ({ io }) => {
|
|
1266
|
-
const inA = await io.getInput("inA");
|
|
1267
|
-
const inB = await io.getInput("inB");
|
|
1268
|
-
|
|
1269
|
-
if (isTrueSignal(inA) && isTrueSignal(inB)) {
|
|
1270
|
-
io.setOutput(
|
|
1271
|
-
"out",
|
|
1272
|
-
createTrueSignal({ inA: inA.value, inB: inB.value })
|
|
1273
|
-
);
|
|
1274
|
-
} else {
|
|
1275
|
-
io.setOutput(
|
|
1276
|
-
"out",
|
|
1277
|
-
createFalseSignal("并运算:两个输入须均为真信号")
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
io.emitFlow("out");
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
private registerViewBindBehavior() {
|
|
1285
|
-
const runner = this;
|
|
1286
|
-
this.behaviors.registerJS("view-bind-run", async ({ token, io }) => {
|
|
1287
|
-
const node = runner.requireNode(token.nodeId);
|
|
1288
|
-
const input = await io.getInput("in");
|
|
1289
|
-
|
|
1290
|
-
if (isTrueSignal(input) && node.viewElementIds?.length) {
|
|
1291
|
-
runner.onViewScopeUpdate?.(node.viewElementIds, input.value);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
if (isFalseSignal(input)) {
|
|
1295
|
-
io.setOutput("out", input);
|
|
1296
|
-
} else if (isTrueSignal(input)) {
|
|
1297
|
-
io.setOutput("out", createTrueSignal(input.value));
|
|
1298
|
-
} else {
|
|
1299
|
-
io.setOutput("out", createTrueSignal(undefined));
|
|
1300
|
-
}
|
|
1301
|
-
io.emitFlow("out");
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
private async runFromNode(
|
|
1306
|
-
nodeId: string,
|
|
1307
|
-
lifecycleSignal?: LifecycleSignal,
|
|
1308
|
-
parentScope?: Scope,
|
|
1309
|
-
activationInput?: unknown,
|
|
1310
|
-
viewEvent?: ViewEventSignal
|
|
1311
|
-
) {
|
|
1312
|
-
const queue: ExecutionToken[] = [
|
|
1313
|
-
{
|
|
1314
|
-
tokenId: createTokenId(),
|
|
1315
|
-
nodeId,
|
|
1316
|
-
nodeType: this.requireNode(nodeId).nodeType,
|
|
1317
|
-
inPort: "in",
|
|
1318
|
-
scope: cloneScope(parentScope),
|
|
1319
|
-
},
|
|
1320
|
-
];
|
|
1321
|
-
|
|
1322
|
-
if (lifecycleSignal) {
|
|
1323
|
-
queue[0]!.scope.vars.set(LIFECYCLE_SIGNAL_KEY, lifecycleSignal);
|
|
1324
|
-
}
|
|
1325
|
-
if (lifecycleSignal?.phase === "blueprintActivated") {
|
|
1326
|
-
queue[0]!.scope.vars.set(
|
|
1327
|
-
BLUEPRINT_ACTIVATION_INPUT_KEY,
|
|
1328
|
-
activationInput
|
|
1329
|
-
);
|
|
1330
|
-
}
|
|
1331
|
-
if (viewEvent) {
|
|
1332
|
-
queue[0]!.scope.vars.set(VIEW_EVENT_SIGNAL_KEY, viewEvent);
|
|
1333
|
-
queue[0]!.scope.vars.set(UI_EVENT_PAYLOAD_KEY, viewEvent);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
const executor = this.createExecutor(queue);
|
|
1337
|
-
|
|
1338
|
-
while (queue.length > 0) {
|
|
1339
|
-
const token = queue.shift()!;
|
|
1340
|
-
if (!this.syncTokenFromGraph(token)) continue;
|
|
1341
|
-
try {
|
|
1342
|
-
await executor.executeToken(token);
|
|
1343
|
-
} catch (error) {
|
|
1344
|
-
this.setNodeOutput(
|
|
1345
|
-
token.nodeId,
|
|
1346
|
-
"out",
|
|
1347
|
-
createFalseSignal(
|
|
1348
|
-
error instanceof Error ? error.message : String(error)
|
|
1349
|
-
)
|
|
1350
|
-
);
|
|
1351
|
-
const nextNodes = this.findSignalTargets(token.nodeId, "out");
|
|
1352
|
-
for (const target of nextNodes) {
|
|
1353
|
-
queue.push({
|
|
1354
|
-
tokenId: createTokenId(),
|
|
1355
|
-
nodeId: target.nodeId,
|
|
1356
|
-
nodeType: target.nodeType,
|
|
1357
|
-
inPort: target.inPort,
|
|
1358
|
-
scope: token.scope,
|
|
1359
|
-
correlationId: token.correlationId ?? token.tokenId,
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
private createExecutor(
|
|
1367
|
-
queue: ExecutionToken[],
|
|
1368
|
-
options?: { emitToActiveDebugQueue?: boolean }
|
|
1369
|
-
) {
|
|
1370
|
-
return new Executor({
|
|
1371
|
-
getNodeDefinition,
|
|
1372
|
-
behaviors: this.behaviors,
|
|
1373
|
-
runBlueprint: async () => {
|
|
1374
|
-
// 由 blueprint-ref-run 行为处理
|
|
1375
|
-
},
|
|
1376
|
-
getInputValue: async (token, port) => {
|
|
1377
|
-
const resolvedPort = port || token.inPort || "in";
|
|
1378
|
-
return this.resolvePortInputValue(token.nodeId, resolvedPort);
|
|
1379
|
-
},
|
|
1380
|
-
setNodeOutput: (token, port, value) => {
|
|
1381
|
-
this.setNodeOutput(token.nodeId, port, value);
|
|
1382
|
-
},
|
|
1383
|
-
emitFlow: (token, outPort) => {
|
|
1384
|
-
const targetQueue =
|
|
1385
|
-
options?.emitToActiveDebugQueue && this.debugQueue
|
|
1386
|
-
? this.debugQueue
|
|
1387
|
-
: queue;
|
|
1388
|
-
const nextNodes = this.findSignalTargets(token.nodeId, outPort);
|
|
1389
|
-
for (const target of nextNodes) {
|
|
1390
|
-
targetQueue.push({
|
|
1391
|
-
tokenId: createTokenId(),
|
|
1392
|
-
nodeId: target.nodeId,
|
|
1393
|
-
nodeType: target.nodeType,
|
|
1394
|
-
inPort: target.inPort,
|
|
1395
|
-
scope: token.scope,
|
|
1396
|
-
correlationId: token.correlationId ?? token.tokenId,
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
},
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
private findSignalEntryNodes() {
|
|
1404
|
-
const hasIncoming = new Set<string>();
|
|
1405
|
-
for (const edge of this.graph.edges) {
|
|
1406
|
-
hasIncoming.add(edge.target);
|
|
1407
|
-
}
|
|
1408
|
-
return this.graph.nodes.filter(
|
|
1409
|
-
(node) =>
|
|
1410
|
-
node.nodeType !== LIFECYCLE_NODE_TYPE &&
|
|
1411
|
-
node.nodeType !== EVENT_NODE_TYPE &&
|
|
1412
|
-
!hasIncoming.has(node.id)
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
private collectOutputs(): BlueprintNodeOutputs {
|
|
1417
|
-
const result: BlueprintNodeOutputs = {};
|
|
1418
|
-
for (const [nodeId, ports] of this.outputs.entries()) {
|
|
1419
|
-
result[nodeId] = Object.fromEntries(ports.entries());
|
|
1420
|
-
}
|
|
1421
|
-
return result;
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
private requireNode(nodeId: string): RunnableGraphNode {
|
|
1425
|
-
const node = this.graph.nodes.find((n) => n.id === nodeId);
|
|
1426
|
-
if (!node) {
|
|
1427
|
-
throw new Error(`Graph node not found: ${nodeId}`);
|
|
1428
|
-
}
|
|
1429
|
-
return node;
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
/** 队列中的 token 可能是在节点改类型(如视图绑定)之前入队的,执行前与最新图对齐 */
|
|
1433
|
-
private syncTokenFromGraph(token: ExecutionToken): boolean {
|
|
1434
|
-
const node = this.graph.nodes.find((n) => n.id === token.nodeId);
|
|
1435
|
-
if (!node) return false;
|
|
1436
|
-
token.nodeType = node.nodeType;
|
|
1437
|
-
return true;
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
private syncDebugQueueWithLatestGraph(): void {
|
|
1441
|
-
if (!this.debugQueue) return;
|
|
1442
|
-
this.debugQueue = this.debugQueue.filter((token) =>
|
|
1443
|
-
this.syncTokenFromGraph(token)
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
private findSignalTargets(nodeId: string, outPort: string) {
|
|
1448
|
-
return this.graph.edges
|
|
1449
|
-
.filter(
|
|
1450
|
-
(edge) =>
|
|
1451
|
-
edge.source === nodeId && (edge.sourceHandle ?? "out") === outPort
|
|
1452
|
-
)
|
|
1453
|
-
.map((edge) => {
|
|
1454
|
-
const target = this.requireNode(edge.target);
|
|
1455
|
-
return {
|
|
1456
|
-
nodeId: target.id,
|
|
1457
|
-
nodeType: target.nodeType,
|
|
1458
|
-
inPort: edge.targetHandle ?? "in",
|
|
1459
|
-
};
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
private findInboundSignalEdges(nodeId: string, port: string) {
|
|
1464
|
-
return this.graph.edges.filter(
|
|
1465
|
-
(edge) =>
|
|
1466
|
-
edge.target === nodeId && (edge.targetHandle ?? "in") === port
|
|
1467
|
-
);
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
private buildFetchIncomingScope(
|
|
1471
|
-
nodeId: string,
|
|
1472
|
-
port: string,
|
|
1473
|
-
fallbackInput: Value | undefined
|
|
1474
|
-
) {
|
|
1475
|
-
return resolveFetchIncomingScope({
|
|
1476
|
-
fetchNodeId: nodeId,
|
|
1477
|
-
inputPort: port,
|
|
1478
|
-
nodes: this.graph.nodes.map((node) => ({
|
|
1479
|
-
id: node.id,
|
|
1480
|
-
label: node.label,
|
|
1481
|
-
nodeType: node.nodeType,
|
|
1482
|
-
role: node.role,
|
|
1483
|
-
})),
|
|
1484
|
-
edges: this.graph.edges,
|
|
1485
|
-
getOutput: (sourceId, sourcePort) =>
|
|
1486
|
-
this.getNodeOutput(sourceId, sourcePort),
|
|
1487
|
-
fallbackInput,
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/** 单端口多连线:任一为真则视为真(或语义);支持 triggerNode 注入的入口信号 */
|
|
1492
|
-
private resolvePortInputValue(nodeId: string, port: string): Value | undefined {
|
|
1493
|
-
const injected = this.injectedInputs.get(`${nodeId}:${port}`);
|
|
1494
|
-
if (injected !== undefined) return injected;
|
|
1495
|
-
|
|
1496
|
-
const inboundEdges = this.findInboundSignalEdges(nodeId, port);
|
|
1497
|
-
if (inboundEdges.length === 0) return undefined;
|
|
1498
|
-
|
|
1499
|
-
const values = inboundEdges.map((edge) =>
|
|
1500
|
-
this.getNodeOutput(edge.source, edge.sourceHandle ?? "out")
|
|
1501
|
-
);
|
|
1502
|
-
return combinePortSignalsOr(values);
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
private findInboundSignalEdge(nodeId: string, port: string) {
|
|
1506
|
-
return this.findInboundSignalEdges(nodeId, port)[0];
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
private getNodeOutput(nodeId: string, port: string): Value {
|
|
1510
|
-
return this.outputs.get(nodeId)?.get(port);
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
private setNodeOutput(nodeId: string, port: string, value: Value) {
|
|
1514
|
-
let nodeOutputs = this.outputs.get(nodeId);
|
|
1515
|
-
if (!nodeOutputs) {
|
|
1516
|
-
nodeOutputs = new Map();
|
|
1517
|
-
this.outputs.set(nodeId, nodeOutputs);
|
|
1518
|
-
}
|
|
1519
|
-
nodeOutputs.set(port, value);
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
export { BLUEPRINT_NODE_TYPE, VIEW_NODE_TYPE };
|