yaml-flow 8.5.3 → 8.6.2
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/browser/asset-integrity.json +3 -3
- package/examples/board/demo-shell-with-server.html +2 -2
- package/examples/board/doc.html +2 -2
- package/examples/board/server/board-server.js +773 -13
- package/examples/board/server/board-worker/task-executor.js +166 -51
- package/examples/board/server/chat-flow/copilot-chat/assistant.js +25 -12
- package/examples/board/server/chat-flow/copilot-chat/probe.js +7 -0
- package/examples/board/server/chat-flow/copilot-chat/shared.js +97 -0
- package/examples/board/server/chat-flow/flow-steps.json +109 -51
- package/examples/board/server-config.json +2 -0
- package/examples/board/test/server-http-test.js +878 -67
- package/examples/board-local/demo-shell-localstorage.html +3 -3
- package/lib/{artifacts-store-lib-CVgtQrNZ.d.cts → artifacts-store-lib-BR-Samty.d.cts} +1 -1
- package/lib/{artifacts-store-lib-D-k-E8Vy.d.ts → artifacts-store-lib-DT7XlWUL.d.ts} +1 -1
- package/lib/artifacts-store-public.cjs +1 -1
- package/lib/artifacts-store-public.d.cts +3 -3
- package/lib/artifacts-store-public.d.ts +3 -3
- package/lib/artifacts-store-public.js +1 -1
- package/lib/batch/index.cjs +1 -1
- package/lib/batch/index.js +1 -1
- package/lib/board-live-cards-mcp.cjs +1 -1
- package/lib/board-live-cards-mcp.d.cts +87 -34
- package/lib/board-live-cards-mcp.d.ts +87 -34
- package/lib/board-live-cards-mcp.js +1 -1
- package/lib/board-live-cards-node.cjs +8 -16
- package/lib/board-live-cards-node.d.cts +52 -14
- package/lib/board-live-cards-node.d.ts +52 -14
- package/lib/board-live-cards-node.js +8 -16
- package/lib/{board-live-cards-public-BGS22cMb.d.ts → board-live-cards-public-BMUIPOrc.d.ts} +90 -30
- package/lib/board-live-cards-public-async-DKZqbJVU.d.ts +256 -0
- package/lib/board-live-cards-public-async-dMWNbWq6.d.cts +256 -0
- package/lib/{board-live-cards-public-B13InXhC.d.cts → board-live-cards-public-wkNmBIRC.d.cts} +90 -30
- package/lib/board-live-cards-public.cjs +1 -2
- package/lib/board-live-cards-public.d.cts +2 -2
- package/lib/board-live-cards-public.d.ts +2 -2
- package/lib/board-live-cards-public.js +1 -2
- package/lib/board-live-cards-server-runtime.cjs +1 -7
- package/lib/board-live-cards-server-runtime.d.cts +7 -6
- package/lib/board-live-cards-server-runtime.d.ts +7 -6
- package/lib/board-live-cards-server-runtime.js +1 -7
- package/lib/board-livegraph-runtime/index.cjs +1 -2
- package/lib/board-livegraph-runtime/index.js +1 -2
- package/lib/board-worker-adapter.cjs +22 -7
- package/lib/board-worker-adapter.d.cts +28 -3
- package/lib/board-worker-adapter.d.ts +28 -3
- package/lib/board-worker-adapter.js +22 -7
- package/lib/card-compute/index.cjs +1 -9
- package/lib/card-compute/index.js +1 -9
- package/lib/card-store-public.cjs +1 -1
- package/lib/card-store-public.d.cts +2 -2
- package/lib/card-store-public.d.ts +2 -2
- package/lib/card-store-public.js +1 -1
- package/lib/card-validation.cjs +1 -9
- package/lib/card-validation.js +1 -9
- package/lib/{chat-storage-lib-0imhRX3l.d.cts → chat-storage-lib-BIUbE-fM.d.cts} +1 -1
- package/lib/{chat-storage-lib-CJn7a6OH.d.ts → chat-storage-lib-BlG-sobS.d.ts} +1 -1
- package/lib/chat-store-public.cjs +1 -1
- package/lib/chat-store-public.d.cts +3 -3
- package/lib/chat-store-public.d.ts +3 -3
- package/lib/chat-store-public.js +1 -1
- package/lib/chunk-2MZUYY65.cjs +2 -0
- package/lib/chunk-5EA2ESS4.cjs +16 -0
- package/lib/chunk-76ON3V7R.js +2 -0
- package/lib/chunk-7BKNHFNH.js +2 -0
- package/lib/chunk-BQS3EIEK.js +3 -0
- package/lib/chunk-CIAJNUR4.js +2 -0
- package/lib/chunk-DAXACY63.js +2 -0
- package/lib/chunk-FW4363Y4.js +2 -0
- package/lib/chunk-FZ2SBU5M.js +3 -0
- package/lib/chunk-G4XXRHL2.cjs +3 -0
- package/lib/chunk-GJJMEAVN.cjs +2 -0
- package/lib/chunk-GNFE24S7.cjs +2 -0
- package/lib/chunk-GYQXDNNI.cjs +2 -0
- package/lib/chunk-H5HBXPOI.cjs +3 -0
- package/lib/chunk-H5KD3JPY.cjs +2 -0
- package/lib/chunk-HEEDJEKM.js +2 -0
- package/lib/chunk-HLJH7LGW.js +16 -0
- package/lib/chunk-IXZG74EW.cjs +2 -0
- package/lib/chunk-JAL25FGA.cjs +2 -0
- package/lib/chunk-JM5EKT57.js +2 -0
- package/lib/chunk-JMDHDY6M.js +2 -0
- package/lib/chunk-KBELAKIY.js +2 -0
- package/lib/chunk-KHJABJ45.cjs +3 -0
- package/lib/chunk-KLRUISRY.cjs +2 -0
- package/lib/chunk-KQX6R4PV.cjs +8 -0
- package/lib/chunk-LODXIALE.cjs +2 -0
- package/lib/chunk-MLVTJASJ.js +2 -0
- package/lib/chunk-MNEOJWPS.js +10 -0
- package/lib/chunk-N6P2JW4W.js +3 -0
- package/lib/chunk-NMZ6XNLB.cjs +3 -0
- package/lib/chunk-OEFTOO47.cjs +3 -0
- package/lib/chunk-OPNGCSXJ.js +2 -0
- package/lib/chunk-OSWJKJLB.js +8 -0
- package/lib/chunk-P7ZCDICS.cjs +2 -0
- package/lib/chunk-PBCDDO4V.cjs +2 -0
- package/lib/chunk-PMUSJQSR.cjs +2 -0
- package/lib/chunk-Q6H7NINN.cjs +5 -0
- package/lib/chunk-QWBNDVUA.js +5 -0
- package/lib/chunk-S6DRP2HX.cjs +2 -0
- package/lib/chunk-SCWHDI3I.js +2 -0
- package/lib/chunk-SFVO2LB2.cjs +3 -0
- package/lib/chunk-U2N6MCD5.cjs +2 -0
- package/lib/chunk-UJ7ZTV4J.cjs +10 -0
- package/lib/chunk-VGT3TRQG.js +3 -0
- package/lib/chunk-VLBB3D6B.js +3 -0
- package/lib/chunk-VMW4Z6EF.js +3 -0
- package/lib/chunk-WDPOGXTY.js +2 -0
- package/lib/chunk-WOALA3V5.cjs +2 -0
- package/lib/chunk-X3LC4LII.js +2 -0
- package/lib/chunk-XQRNDX4Q.js +2 -0
- package/lib/chunk-YGKDQLYP.js +2 -0
- package/lib/chunk-YMEIPKLW.cjs +2 -0
- package/lib/cloud-storage.cjs +2 -0
- package/lib/cloud-storage.d.cts +177 -0
- package/lib/cloud-storage.d.ts +177 -0
- package/lib/cloud-storage.js +2 -0
- package/lib/config/index.cjs +1 -1
- package/lib/config/index.js +1 -1
- package/lib/continuous-event-graph/index.cjs +1 -2
- package/lib/continuous-event-graph/index.js +1 -2
- package/lib/event-graph/index.cjs +1 -22
- package/lib/event-graph/index.js +1 -22
- package/lib/execution-refs.cjs +1 -2
- package/lib/execution-refs.d.cts +3 -2
- package/lib/execution-refs.d.ts +3 -2
- package/lib/execution-refs.js +1 -2
- package/lib/index.cjs +2 -24
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -24
- package/lib/{types-CIgsh56O.d.cts → queue-lane-registry-BPKWWgd4.d.cts} +66 -14
- package/lib/{types-30R357js.d.ts → queue-lane-registry-Be6c0ftj.d.ts} +66 -14
- package/lib/server-runtime/index.cjs +1 -7
- package/lib/server-runtime/index.d.cts +18 -7
- package/lib/server-runtime/index.d.ts +18 -7
- package/lib/server-runtime/index.js +1 -7
- package/lib/step-machine/index.cjs +1 -11
- package/lib/step-machine/index.js +1 -11
- package/lib/step-machine-public/index.cjs +1 -4
- package/lib/step-machine-public/index.d.cts +1 -1
- package/lib/step-machine-public/index.d.ts +1 -1
- package/lib/step-machine-public/index.js +1 -4
- package/lib/{storage-interface-B2WD9D5n.d.cts → storage-interface-BFiD3kyB.d.cts} +38 -1
- package/lib/{storage-interface-B2WD9D5n.d.ts → storage-interface-BFiD3kyB.d.ts} +38 -1
- package/lib/stores/index.cjs +1 -2
- package/lib/stores/index.d.cts +1 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -2
- package/lib/stores/kv.cjs +1 -2
- package/lib/stores/kv.d.cts +1 -1
- package/lib/stores/kv.d.ts +1 -1
- package/lib/stores/kv.js +1 -2
- package/lib/stores/memory.cjs +1 -1
- package/lib/stores/memory.js +1 -1
- package/package.json +7 -16
- package/cli/board-live-cards-lib-COi4bSpk.d.ts +0 -322
- package/cli/browser-api/board-live-cards-browser-adapter.d.ts +0 -36
- package/cli/browser-api/board-live-cards-browser-adapter.js +0 -4
- package/cli/browser-api/card-store-browser-api.d.ts +0 -25
- package/cli/browser-api/card-store-browser-api.js +0 -2
- package/cli/browser-api/jsonata-sync.cjs +0 -7623
- package/cli/bundled/artifacts-store-cli.mjs +0 -12
- package/cli/bundled/batch-runner-cli.mjs +0 -3
- package/cli/bundled/board-live-cards-cli.mjs +0 -29
- package/cli/bundled/card-store-cli.mjs +0 -154
- package/cli/bundled/chat-store-cli.mjs +0 -16
- package/cli/bundled/jsonata-sync.cjs +0 -7623
- package/cli/bundled/step-machine-cli.mjs +0 -150
- package/cli/execution-interface-BCIhu1gO.d.ts +0 -442
- package/cli/types-H3EMBPY2.d.ts +0 -398
- package/examples/board/server/README-mcp-api.md +0 -690
- package/examples/board/test/server-http-mcp-test.js +0 -1280
- package/lib/board-livegraph-runtime/jsonata-sync.cjs +0 -7623
- package/lib/card-compute/jsonata-sync.cjs +0 -7623
- package/lib/continuous-event-graph/jsonata-sync.cjs +0 -7623
- package/lib/server-runtime/jsonata-sync.cjs +0 -7623
|
@@ -6,25 +6,35 @@ import path from 'node:path';
|
|
|
6
6
|
import net from 'node:net';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
createMultiBoardServerRuntime,
|
|
13
13
|
createSingleBoardServerRuntime,
|
|
14
|
+
createHostedBoardQueueLaneRegistry,
|
|
14
15
|
} from 'yaml-flow/board-live-cards-server-runtime';
|
|
16
|
+
import {
|
|
17
|
+
createHostedAsyncBoardPlatformAdapter,
|
|
18
|
+
} from 'yaml-flow/cloud-storage';
|
|
15
19
|
|
|
16
20
|
import {
|
|
17
21
|
buildLocalBaseSpec,
|
|
22
|
+
createHttpBoardCallbackTransport,
|
|
18
23
|
createFsBoardPlatformAdapter,
|
|
19
24
|
createFsBoardNonCorePlatformAdapter,
|
|
25
|
+
createInProcessBoardCallbackTransport,
|
|
20
26
|
createFsBoardChatStorage,
|
|
21
27
|
createNodeSpawnInvocationAdapter,
|
|
22
28
|
createArtifactsStore,
|
|
23
29
|
evaluateArgsMassaging,
|
|
24
30
|
invokeExecutionRef,
|
|
25
31
|
parseRef,
|
|
32
|
+
registerInProcessExecutionHandler,
|
|
33
|
+
startQueueLaneRunners,
|
|
26
34
|
serializeRef,
|
|
35
|
+
serializeExecutionRef,
|
|
27
36
|
} from 'yaml-flow/board-live-cards-node';
|
|
37
|
+
import { registerInProcessBoardWorkerCallback } from 'yaml-flow/board-worker-adapter';
|
|
28
38
|
import {
|
|
29
39
|
createStepMachineChatFlowRunner,
|
|
30
40
|
} from 'yaml-flow/step-machine-public';
|
|
@@ -141,6 +151,26 @@ const configuredChatFlowTimeoutMs = normalizeTimeoutMs(serverConfig.chatFlowTime
|
|
|
141
151
|
const configuredInvokeRefTimeoutMs = normalizeTimeoutMs(serverConfig.chatInvokeRefTimeoutMs, 300000);
|
|
142
152
|
const configuredCopilotTimeoutMs = normalizeTimeoutMs(serverConfig.chatCopilotTimeoutMs, 300000);
|
|
143
153
|
|
|
154
|
+
function normalizeRuntimeMode(value) {
|
|
155
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
156
|
+
if (normalized === 'sync' || normalized === 'fs') return 'sync';
|
|
157
|
+
return 'cloud';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeBoardWorkerTransport(value) {
|
|
161
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
162
|
+
if (normalized === 'http') return 'http';
|
|
163
|
+
if (normalized === 'queue') return 'queue';
|
|
164
|
+
return 'in-process-loop';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const configuredBoardWorkerTransport = normalizeBoardWorkerTransport(
|
|
168
|
+
process.env.DEMO_TASK_EXECUTOR_TRANSPORT || serverConfig.taskExecutorTransport || 'in-process-loop',
|
|
169
|
+
);
|
|
170
|
+
const configuredRuntimeMode = normalizeRuntimeMode(
|
|
171
|
+
process.env.DEMO_SERVER_MODE || serverConfig.mode || 'cloud',
|
|
172
|
+
);
|
|
173
|
+
|
|
144
174
|
// Resolve top-level config defaults (used as fallbacks for per-board config)
|
|
145
175
|
const configuredTaskExecutorPath = resolveFromConfig(serverConfig.taskExecutorPath);
|
|
146
176
|
const configuredChatHandlerPath = resolveFromConfig(serverConfig.chatHandlerPath);
|
|
@@ -167,6 +197,7 @@ const PORT = Number(process.env.DEMO_SERVER_PORT || serverConfig.port || 7799);
|
|
|
167
197
|
const cardsPatternArgIndex = cliArgs.indexOf('--cards-pattern');
|
|
168
198
|
const cliCardsPattern = cardsPatternArgIndex !== -1 ? cliArgs[cardsPatternArgIndex + 1] : null;
|
|
169
199
|
const selectedCardsPattern = (process.env.DEMO_CARDS_PATTERN || cliCardsPattern || '').trim() || null;
|
|
200
|
+
const enableTestReq = /^(1|true|yes|on)$/i.test((process.env.BOARD_SERVER_ENABLE_TEST_REQ || '').trim());
|
|
170
201
|
|
|
171
202
|
const CORS_HEADERS = {
|
|
172
203
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -229,6 +260,96 @@ function makeExecutionRef(scriptPath, extra) {
|
|
|
229
260
|
};
|
|
230
261
|
}
|
|
231
262
|
|
|
263
|
+
function makeLocalTaskExecutorRef(scriptPath, extra) {
|
|
264
|
+
if (!scriptPath) return undefined;
|
|
265
|
+
const resolved = path.isAbsolute(scriptPath) ? scriptPath : path.resolve(process.cwd(), scriptPath);
|
|
266
|
+
return {
|
|
267
|
+
meta: 'task-executor',
|
|
268
|
+
howToRun: 'local-node',
|
|
269
|
+
whatToRun: serializeRef({ kind: 'fs-path', value: resolved }),
|
|
270
|
+
...(extra !== undefined ? { extra } : {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isHostedTaskExecutorRef(ref) {
|
|
275
|
+
return ref?.howToRun === 'queue-storage'
|
|
276
|
+
|| ref?.howToRun === 'in-process-loop'
|
|
277
|
+
|| ref?.howToRun === 'http:post'
|
|
278
|
+
|| ref?.howToRun === 'http:get';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function makeHostedBoardWorkerRef(boardId, taskExecPath, transport, executionExtra) {
|
|
282
|
+
if (!taskExecPath) return undefined;
|
|
283
|
+
if (transport === 'in-process-loop') {
|
|
284
|
+
return {
|
|
285
|
+
meta: 'task-executor',
|
|
286
|
+
howToRun: 'in-process-loop',
|
|
287
|
+
whatToRun: serializeRef({ kind: 'in-process-loop', value: `board:${boardId}:board-worker` }),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (transport === 'http') {
|
|
291
|
+
return {
|
|
292
|
+
meta: 'task-executor',
|
|
293
|
+
howToRun: 'http:post',
|
|
294
|
+
whatToRun: serializeRef({
|
|
295
|
+
kind: 'http-url',
|
|
296
|
+
value: `${String(executionExtra.serverUrl || '').replace(/\/+$/, '')}/api/board-worker`,
|
|
297
|
+
}),
|
|
298
|
+
extra: { boardId },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (transport === 'queue') {
|
|
302
|
+
return {
|
|
303
|
+
meta: 'task-executor',
|
|
304
|
+
howToRun: 'queue-storage',
|
|
305
|
+
whatToRun: serializeRef({ kind: 'queue-storage', value: `board:${boardId}:board-worker` }),
|
|
306
|
+
extra: { boardId },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
throw new Error(`Unsupported board-worker transport for demo host: ${transport}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function makeBoardWorkerCallbackTransport(serverUrl, boardApiBasePath, transport, boardId) {
|
|
313
|
+
if (transport === 'in-process-loop' || transport === 'queue' || transport === 'http') {
|
|
314
|
+
return createInProcessBoardCallbackTransport(`board:${boardId}:board-worker-callback`);
|
|
315
|
+
}
|
|
316
|
+
const normalizedServerUrl = typeof serverUrl === 'string' ? serverUrl.trim().replace(/\/+$/, '') : '';
|
|
317
|
+
const normalizedApiBasePath = typeof boardApiBasePath === 'string' ? boardApiBasePath.trim().replace(/\/+$/, '') : '';
|
|
318
|
+
if (!normalizedServerUrl || !normalizedApiBasePath) return undefined;
|
|
319
|
+
return createHttpBoardCallbackTransport(`${normalizedServerUrl}${normalizedApiBasePath}/callback/board-worker`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function readJsonRequest(req) {
|
|
323
|
+
const parts = [];
|
|
324
|
+
for await (const chunk of req) parts.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
325
|
+
const raw = Buffer.concat(parts).toString('utf-8').trim();
|
|
326
|
+
return raw ? JSON.parse(raw) : {};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const boardWorkerModuleCache = new Map();
|
|
330
|
+
|
|
331
|
+
async function loadBoardWorkerModule(taskExecPath) {
|
|
332
|
+
const resolved = path.isAbsolute(taskExecPath) ? taskExecPath : path.resolve(BOARD_ROOT, taskExecPath);
|
|
333
|
+
if (!boardWorkerModuleCache.has(resolved)) {
|
|
334
|
+
boardWorkerModuleCache.set(resolved, import(pathToFileURL(resolved).href));
|
|
335
|
+
}
|
|
336
|
+
return boardWorkerModuleCache.get(resolved);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createHostedBoardWorkerDispatcher(boardId, taskExecPath) {
|
|
340
|
+
if (!taskExecPath) return null;
|
|
341
|
+
return async (request) => {
|
|
342
|
+
const mod = await loadBoardWorkerModule(taskExecPath);
|
|
343
|
+
if (typeof mod.executeBoardWorkerRequest === 'function') {
|
|
344
|
+
return await mod.executeBoardWorkerRequest(request);
|
|
345
|
+
}
|
|
346
|
+
if (typeof mod.executeTaskExecutorRequest === 'function') {
|
|
347
|
+
return await mod.executeTaskExecutorRequest(request);
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`Hosted board worker for board ${boardId} must export executeBoardWorkerRequest(request)`);
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
232
353
|
function createNamedPipeNotificationTransport() {
|
|
233
354
|
return {
|
|
234
355
|
async subscribe(ref, onEvent) {
|
|
@@ -265,6 +386,437 @@ function createNamedPipeNotificationTransport() {
|
|
|
265
386
|
};
|
|
266
387
|
}
|
|
267
388
|
|
|
389
|
+
function createInMemoryNotificationTransport() {
|
|
390
|
+
const subscribers = new Map();
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
publish(channel, notifications) {
|
|
394
|
+
const channelSubscribers = subscribers.get(channel);
|
|
395
|
+
if (!channelSubscribers?.size) return;
|
|
396
|
+
const event = notifications.length === 1
|
|
397
|
+
? notifications[0]
|
|
398
|
+
: { kind: 'notification-batch', notifications };
|
|
399
|
+
for (const onEvent of channelSubscribers) {
|
|
400
|
+
try { onEvent(event); } catch { /* */ }
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
async subscribe(ref, onEvent) {
|
|
405
|
+
if (ref.kind !== 'in-memory-notify') return () => {};
|
|
406
|
+
const channel = String(ref.value || '');
|
|
407
|
+
const channelSubscribers = subscribers.get(channel) || new Set();
|
|
408
|
+
channelSubscribers.add(onEvent);
|
|
409
|
+
subscribers.set(channel, channelSubscribers);
|
|
410
|
+
return () => {
|
|
411
|
+
channelSubscribers.delete(onEvent);
|
|
412
|
+
if (!channelSubscribers.size) subscribers.delete(channel);
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function createNotificationTransport() {
|
|
419
|
+
const namedPipeTransport = createNamedPipeNotificationTransport();
|
|
420
|
+
const inMemoryTransport = createInMemoryNotificationTransport();
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
publish: inMemoryTransport.publish,
|
|
424
|
+
async subscribe(ref, onEvent) {
|
|
425
|
+
if (ref.kind === 'in-memory-notify') return inMemoryTransport.subscribe(ref, onEvent);
|
|
426
|
+
return namedPipeTransport.subscribe(ref, onEvent);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
class MemoryAsyncKVStorage {
|
|
432
|
+
constructor() {
|
|
433
|
+
this.values = new Map();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
readSync(key) {
|
|
437
|
+
return this.values.has(key) ? this.values.get(key) : null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
writeSync(key, value) {
|
|
441
|
+
this.values.set(key, value);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
deleteSync(key) {
|
|
445
|
+
this.values.delete(key);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async read(key) {
|
|
449
|
+
return this.readSync(key);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async write(key, value) {
|
|
453
|
+
this.writeSync(key, value);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async delete(key) {
|
|
457
|
+
this.deleteSync(key);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async listKeys(prefix = '') {
|
|
461
|
+
return Array.from(this.values.keys()).filter((key) => key.startsWith(prefix)).sort();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
class MemoryAsyncBlobStorage {
|
|
466
|
+
constructor(kind, keyRefFactory = null) {
|
|
467
|
+
this.kind = kind;
|
|
468
|
+
this.keyRefFactory = keyRefFactory;
|
|
469
|
+
this.textValues = new Map();
|
|
470
|
+
this.byteValues = new Map();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
keyRef(key) {
|
|
474
|
+
if (this.keyRefFactory) return this.keyRefFactory(key);
|
|
475
|
+
return { kind: this.kind, value: key };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async read(key) {
|
|
479
|
+
if (this.textValues.has(key)) return this.textValues.get(key);
|
|
480
|
+
const bytes = this.byteValues.get(key);
|
|
481
|
+
return bytes ? Buffer.from(bytes).toString('utf-8') : null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async write(key, value) {
|
|
485
|
+
this.textValues.set(key, value);
|
|
486
|
+
this.byteValues.delete(key);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async readBytes(key) {
|
|
490
|
+
if (this.byteValues.has(key)) return this.byteValues.get(key);
|
|
491
|
+
if (this.textValues.has(key)) return Buffer.from(this.textValues.get(key), 'utf-8');
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async writeBytes(key, value) {
|
|
496
|
+
this.byteValues.set(key, Buffer.from(value));
|
|
497
|
+
this.textValues.delete(key);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async remove(key) {
|
|
501
|
+
this.textValues.delete(key);
|
|
502
|
+
this.byteValues.delete(key);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async exists(key) {
|
|
506
|
+
return this.textValues.has(key) || this.byteValues.has(key);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async listKeys(prefix = '') {
|
|
510
|
+
const keys = new Set([
|
|
511
|
+
...Array.from(this.textValues.keys()),
|
|
512
|
+
...Array.from(this.byteValues.keys()),
|
|
513
|
+
]);
|
|
514
|
+
return Array.from(keys).filter((key) => key.startsWith(prefix)).sort();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
class MemoryAsyncQueueStorage {
|
|
519
|
+
constructor() {
|
|
520
|
+
this.queueItems = new Map();
|
|
521
|
+
this.deadQueueItems = new Map();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
createId() {
|
|
525
|
+
return globalThis.crypto?.randomUUID?.() || genShortId();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async enqueue(body) {
|
|
529
|
+
const item = {
|
|
530
|
+
id: this.createId(),
|
|
531
|
+
body,
|
|
532
|
+
enqueuedAt: new Date().toISOString(),
|
|
533
|
+
attempt: 0,
|
|
534
|
+
};
|
|
535
|
+
this.queueItems.set(item.id, item);
|
|
536
|
+
return item;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async enqueueIfAbsent(body, dedupKey) {
|
|
540
|
+
for (const existing of this.queueItems.values()) {
|
|
541
|
+
if (existing.dedupKey === dedupKey) return null;
|
|
542
|
+
}
|
|
543
|
+
const item = {
|
|
544
|
+
id: this.createId(),
|
|
545
|
+
body,
|
|
546
|
+
enqueuedAt: new Date().toISOString(),
|
|
547
|
+
attempt: 0,
|
|
548
|
+
dedupKey,
|
|
549
|
+
};
|
|
550
|
+
this.queueItems.set(item.id, item);
|
|
551
|
+
return { id: item.id, body: item.body, enqueuedAt: item.enqueuedAt, attempt: item.attempt };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async lease(opts = {}) {
|
|
555
|
+
const max = Math.max(1, Math.floor(opts.max ?? 1));
|
|
556
|
+
const visibilityMs = Math.max(1, Math.floor(opts.visibilityMs ?? 60_000));
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
for (const item of this.queueItems.values()) {
|
|
559
|
+
if (item.leaseExpiresAt && Date.parse(item.leaseExpiresAt) <= now) {
|
|
560
|
+
delete item.leaseToken;
|
|
561
|
+
delete item.leaseExpiresAt;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const leased = [];
|
|
565
|
+
for (const item of this.queueItems.values()) {
|
|
566
|
+
if (leased.length >= max) break;
|
|
567
|
+
if (item.leaseToken) continue;
|
|
568
|
+
item.attempt += 1;
|
|
569
|
+
item.leaseToken = this.createId();
|
|
570
|
+
item.leaseExpiresAt = new Date(Date.now() + visibilityMs).toISOString();
|
|
571
|
+
leased.push({
|
|
572
|
+
id: item.id,
|
|
573
|
+
body: item.body,
|
|
574
|
+
enqueuedAt: item.enqueuedAt,
|
|
575
|
+
attempt: item.attempt,
|
|
576
|
+
leaseToken: item.leaseToken,
|
|
577
|
+
leaseExpiresAt: item.leaseExpiresAt,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return leased;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async ack(messageId, leaseToken) {
|
|
584
|
+
const item = this.queueItems.get(messageId);
|
|
585
|
+
if (!item || item.leaseToken !== leaseToken) return false;
|
|
586
|
+
this.queueItems.delete(messageId);
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async nack(messageId, leaseToken, opts = {}) {
|
|
591
|
+
const item = this.queueItems.get(messageId);
|
|
592
|
+
if (!item || item.leaseToken !== leaseToken) return false;
|
|
593
|
+
delete item.leaseToken;
|
|
594
|
+
delete item.leaseExpiresAt;
|
|
595
|
+
if (opts.dead) {
|
|
596
|
+
this.queueItems.delete(messageId);
|
|
597
|
+
this.deadQueueItems.set(messageId, { ...item, reason: opts.reason });
|
|
598
|
+
}
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async peekActive() {
|
|
603
|
+
return Array.from(this.queueItems.values())
|
|
604
|
+
.filter((item) => !item.leaseToken)
|
|
605
|
+
.map((item) => ({ id: item.id, body: item.body, enqueuedAt: item.enqueuedAt, attempt: item.attempt }));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async peekDeadLetter() {
|
|
609
|
+
return Array.from(this.deadQueueItems.values())
|
|
610
|
+
.map((item) => ({ ...item, body: item.body }));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function createMemoryAsyncScratchStorage() {
|
|
615
|
+
const store = new MemoryAsyncBlobStorage('cloud-scratch-key');
|
|
616
|
+
let seq = 0;
|
|
617
|
+
return {
|
|
618
|
+
...store,
|
|
619
|
+
async getUniqueKey(prefix = 'scratch', suffix = '.json') {
|
|
620
|
+
seq += 1;
|
|
621
|
+
return `${prefix}-${seq}${suffix}`;
|
|
622
|
+
},
|
|
623
|
+
async create(value, prefix, suffix) {
|
|
624
|
+
const key = await this.getUniqueKey(prefix, suffix);
|
|
625
|
+
await store.write(key, value);
|
|
626
|
+
return key;
|
|
627
|
+
},
|
|
628
|
+
config: {
|
|
629
|
+
get: () => null,
|
|
630
|
+
set: () => {},
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function createMemoryArchiveFactory() {
|
|
636
|
+
const blobStores = new Map();
|
|
637
|
+
const journalStreams = new Map();
|
|
638
|
+
let seq = 0;
|
|
639
|
+
return {
|
|
640
|
+
stream(name) {
|
|
641
|
+
if (!journalStreams.has(name)) journalStreams.set(name, []);
|
|
642
|
+
const entries = journalStreams.get(name);
|
|
643
|
+
return {
|
|
644
|
+
async append(payload) {
|
|
645
|
+
seq += 1;
|
|
646
|
+
const entry = { id: `j-${seq}`, payload };
|
|
647
|
+
entries.push(entry);
|
|
648
|
+
return entry;
|
|
649
|
+
},
|
|
650
|
+
async readAll() {
|
|
651
|
+
return entries.slice();
|
|
652
|
+
},
|
|
653
|
+
async readAfter(cursor) {
|
|
654
|
+
const idx = cursor ? entries.findIndex((entry) => entry.id === cursor) : -1;
|
|
655
|
+
const items = idx >= 0 ? entries.slice(idx + 1) : entries.slice();
|
|
656
|
+
return {
|
|
657
|
+
entries: items,
|
|
658
|
+
newCursor: items.length ? items[items.length - 1].id : cursor,
|
|
659
|
+
};
|
|
660
|
+
},
|
|
661
|
+
async clear() {
|
|
662
|
+
entries.splice(0, entries.length);
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
blob(name) {
|
|
667
|
+
if (!blobStores.has(name)) blobStores.set(name, new MemoryAsyncBlobStorage('cloud-archive-key'));
|
|
668
|
+
return blobStores.get(name);
|
|
669
|
+
},
|
|
670
|
+
async listStreams(prefix = '') {
|
|
671
|
+
return Array.from(journalStreams.keys()).filter((key) => key.startsWith(prefix)).sort();
|
|
672
|
+
},
|
|
673
|
+
async listBlobs(prefix = '') {
|
|
674
|
+
return Array.from(blobStores.keys()).filter((key) => key.startsWith(prefix)).sort();
|
|
675
|
+
},
|
|
676
|
+
config: {
|
|
677
|
+
get: () => null,
|
|
678
|
+
set: () => {},
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function createImmediateAsyncLock() {
|
|
684
|
+
let held = false;
|
|
685
|
+
return {
|
|
686
|
+
async tryAcquire() {
|
|
687
|
+
if (held) return null;
|
|
688
|
+
held = true;
|
|
689
|
+
return async () => { held = false; };
|
|
690
|
+
},
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function stableHash(value) {
|
|
695
|
+
const json = JSON.stringify(value);
|
|
696
|
+
let hash = 0;
|
|
697
|
+
for (let i = 0; i < json.length; i += 1) {
|
|
698
|
+
hash = ((hash << 5) - hash + json.charCodeAt(i)) | 0;
|
|
699
|
+
}
|
|
700
|
+
return `h${Math.abs(hash)}`;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function genShortId() {
|
|
704
|
+
return `${Date.now().toString(16)}${Math.random().toString(16).slice(2, 18)}`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function normalizeHostedBoardWorkerTransport(runtimeMode, requestedTransport) {
|
|
708
|
+
if (runtimeMode === 'cloud') return 'http';
|
|
709
|
+
return requestedTransport;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function seedCloudCardStore(cardStoreKv, cards) {
|
|
713
|
+
const index = {};
|
|
714
|
+
const now = new Date().toISOString();
|
|
715
|
+
for (const card of cards) {
|
|
716
|
+
if (!card || typeof card !== 'object' || !card.id) continue;
|
|
717
|
+
const key = String(card.id);
|
|
718
|
+
cardStoreKv.writeSync(key, card);
|
|
719
|
+
index[card.id] = {
|
|
720
|
+
key,
|
|
721
|
+
checksum: stableHash(card),
|
|
722
|
+
updatedAt: now,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
cardStoreKv.writeSync('_index', index);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const cloudBoardBundles = new Map();
|
|
729
|
+
|
|
730
|
+
function getCloudBoardBundle(boardId, notifyChannel, boardDir = null) {
|
|
731
|
+
if (cloudBoardBundles.has(boardId)) return cloudBoardBundles.get(boardId);
|
|
732
|
+
|
|
733
|
+
const kvNamespaces = new Map();
|
|
734
|
+
const kvRefs = new Map();
|
|
735
|
+
const blobNamespaces = new Map();
|
|
736
|
+
const blobKindToNamespace = new Map([
|
|
737
|
+
['cloud-blob-key', ''],
|
|
738
|
+
['cloud-source-key', 'sources'],
|
|
739
|
+
['cloud-archive-key', 'archive'],
|
|
740
|
+
['cloud-scratch-key', 'scratch'],
|
|
741
|
+
]);
|
|
742
|
+
const scratchStore = createMemoryAsyncScratchStorage();
|
|
743
|
+
const archiveFactory = createMemoryArchiveFactory();
|
|
744
|
+
const journalStorage = archiveFactory.stream('board-journal');
|
|
745
|
+
const boardWorkerQueueStorage = new MemoryAsyncQueueStorage();
|
|
746
|
+
const chatAgentQueueStorage = new MemoryAsyncQueueStorage();
|
|
747
|
+
const processAccumulatedQueueStorage = new MemoryAsyncQueueStorage();
|
|
748
|
+
const stagedSourcesDir = boardDir ? path.join(path.dirname(boardDir), 'runtime-out', '.cloud-staged-sources') : null;
|
|
749
|
+
if (stagedSourcesDir) fs.mkdirSync(stagedSourcesDir, { recursive: true });
|
|
750
|
+
|
|
751
|
+
const getKvNamespace = (namespace) => {
|
|
752
|
+
const key = String(namespace || '');
|
|
753
|
+
if (!kvNamespaces.has(key)) kvNamespaces.set(key, new MemoryAsyncKVStorage());
|
|
754
|
+
return kvNamespaces.get(key);
|
|
755
|
+
};
|
|
756
|
+
const getKvRef = (ref) => {
|
|
757
|
+
const key = String(ref || '');
|
|
758
|
+
if (!kvRefs.has(key)) kvRefs.set(key, new MemoryAsyncKVStorage());
|
|
759
|
+
return kvRefs.get(key);
|
|
760
|
+
};
|
|
761
|
+
const getBlobNamespace = (namespace) => {
|
|
762
|
+
const key = String(namespace || '');
|
|
763
|
+
if (key === 'scratch') return scratchStore;
|
|
764
|
+
if (!blobNamespaces.has(key)) {
|
|
765
|
+
const kind = key === 'sources' ? 'cloud-source-key' : 'cloud-blob-key';
|
|
766
|
+
const keyRefFactory = key === 'sources' && stagedSourcesDir
|
|
767
|
+
? (blobKey) => ({ kind: 'fs-path', value: path.join(stagedSourcesDir, ...String(blobKey).split('/')) })
|
|
768
|
+
: null;
|
|
769
|
+
blobNamespaces.set(key, new MemoryAsyncBlobStorage(kind, keyRefFactory));
|
|
770
|
+
}
|
|
771
|
+
return blobNamespaces.get(key);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const bundle = {
|
|
775
|
+
getKvNamespace,
|
|
776
|
+
getKvRef,
|
|
777
|
+
getBlobNamespace,
|
|
778
|
+
adapter: null,
|
|
779
|
+
notifyChannel,
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
bundle.adapter = createHostedAsyncBoardPlatformAdapter({
|
|
783
|
+
boardId,
|
|
784
|
+
kvStorage: (namespace) => getKvNamespace(namespace),
|
|
785
|
+
kvStorageForRef: (ref) => getKvRef(ref),
|
|
786
|
+
blobStorage: (namespace) => getBlobNamespace(namespace),
|
|
787
|
+
scratchStorage: () => scratchStore,
|
|
788
|
+
scratchStorageForRef: () => scratchStore,
|
|
789
|
+
archiveFactory: () => archiveFactory,
|
|
790
|
+
archiveFactoryForRef: () => archiveFactory,
|
|
791
|
+
journalStorage: () => journalStorage,
|
|
792
|
+
queueStorage: boardWorkerQueueStorage,
|
|
793
|
+
chatAgentQueueStorage,
|
|
794
|
+
processAccumulatedQueueStorage,
|
|
795
|
+
lock: createImmediateAsyncLock(),
|
|
796
|
+
callbackTransport: undefined,
|
|
797
|
+
resolveBlob: async (ref) => {
|
|
798
|
+
if (ref.kind === 'fs-path') {
|
|
799
|
+
return fs.promises.readFile(ref.value, 'utf-8');
|
|
800
|
+
}
|
|
801
|
+
const namespace = blobKindToNamespace.get(ref.kind);
|
|
802
|
+
if (namespace !== undefined) {
|
|
803
|
+
const value = await getBlobNamespace(namespace).read(ref.value);
|
|
804
|
+
if (value != null) return value;
|
|
805
|
+
}
|
|
806
|
+
throw new Error(`Blob not found for ref ${ref.kind}:${ref.value}`);
|
|
807
|
+
},
|
|
808
|
+
hashFn: stableHash,
|
|
809
|
+
genId: genShortId,
|
|
810
|
+
supportsDirectSourceOutput: (ref) => ref?.howToRun === 'http:post',
|
|
811
|
+
publishBoardChangeNotifications: async (notifications) => {
|
|
812
|
+
notificationTransport.publish(notifyChannel, notifications);
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
cloudBoardBundles.set(boardId, bundle);
|
|
817
|
+
return bundle;
|
|
818
|
+
}
|
|
819
|
+
|
|
268
820
|
// ---------------------------------------------------------------------------
|
|
269
821
|
// Server meta store (multi-board registry)
|
|
270
822
|
// ---------------------------------------------------------------------------
|
|
@@ -281,14 +833,17 @@ const serverMetaStore = createArtifactsStore(serverMetaAdapter.blobStorage('serv
|
|
|
281
833
|
|
|
282
834
|
const apiBasePath = '/api/boards';
|
|
283
835
|
const invocationAdapter = createNodeSpawnInvocationAdapter();
|
|
284
|
-
const notificationTransport =
|
|
836
|
+
const notificationTransport = createNotificationTransport();
|
|
285
837
|
const logger = { info: console.log, warn: console.warn, error: console.error };
|
|
838
|
+
const hostedBoardWorkerDispatchers = new Map();
|
|
839
|
+
const hostedQueueLaneStops = new Map();
|
|
840
|
+
const hostedBoardChatStorages = new Map();
|
|
286
841
|
|
|
287
842
|
// Map config keys to board entries for the factory
|
|
288
843
|
const boardConfigEntries = serverConfig.boards ? Object.entries(serverConfig.boards) : [];
|
|
289
844
|
const boardConfigMap = new Map(boardConfigEntries);
|
|
290
845
|
|
|
291
|
-
function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, executionExtra = {}) {
|
|
846
|
+
function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, executionExtra = {}, runtimeMode = 'sync') {
|
|
292
847
|
fs.mkdirSync(boardDir, { recursive: true });
|
|
293
848
|
const runtimeCardsDir = path.join(path.dirname(boardDir), 'cards');
|
|
294
849
|
const runtimeCardStoreDir = path.join(runtimeCardsDir, 'store');
|
|
@@ -300,9 +855,75 @@ function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow,
|
|
|
300
855
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
301
856
|
|
|
302
857
|
const notifyChannel = `yaml-flow-server-${label}-${boardId}-${process.pid}`;
|
|
858
|
+
const boardWorkerTransport = normalizeHostedBoardWorkerTransport(
|
|
859
|
+
runtimeMode,
|
|
860
|
+
normalizeBoardWorkerTransport(executionExtra.taskExecutorTransport),
|
|
861
|
+
);
|
|
862
|
+
const callbackTransport = makeBoardWorkerCallbackTransport(executionExtra.serverUrl, executionExtra.apiBasePath, boardWorkerTransport, boardId);
|
|
863
|
+
|
|
864
|
+
if (runtimeMode === 'cloud') {
|
|
865
|
+
const cloudBundle = getCloudBoardBundle(boardId, notifyChannel, boardDir);
|
|
866
|
+
cloudBundle.adapter.callbackTransport = callbackTransport;
|
|
867
|
+
const cloudNonCoreAdapter = createFsBoardNonCorePlatformAdapter(
|
|
868
|
+
parseRef(serializeRef({ kind: 'fs-path', value: boardDir })),
|
|
869
|
+
{
|
|
870
|
+
notifyChannel,
|
|
871
|
+
...(callbackTransport ? { callbackTransport } : {}),
|
|
872
|
+
},
|
|
873
|
+
);
|
|
874
|
+
const cloudHostedTaskExecutorRef = makeHostedBoardWorkerRef(boardId, taskExecPath, boardWorkerTransport, executionExtra);
|
|
875
|
+
const cloudLocalSyncTaskExecutorRef = makeLocalTaskExecutorRef(taskExecPath, executionExtra);
|
|
876
|
+
if (cloudLocalSyncTaskExecutorRef) {
|
|
877
|
+
const invokeExecutor = cloudNonCoreAdapter.invokeExecutor.bind(cloudNonCoreAdapter);
|
|
878
|
+
cloudNonCoreAdapter.invokeExecutor = (ref, subcommand, execOpts) => {
|
|
879
|
+
const syncRef = isHostedTaskExecutorRef(ref) ? cloudLocalSyncTaskExecutorRef : ref;
|
|
880
|
+
return invokeExecutor(syncRef, subcommand, execOpts);
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
cloudNonCoreAdapter.requestProcessAccumulated = () => {};
|
|
884
|
+
try {
|
|
885
|
+
const seedTeRef = cloudLocalSyncTaskExecutorRef ?? cloudHostedTaskExecutorRef;
|
|
886
|
+
if (seedTeRef) {
|
|
887
|
+
cloudNonCoreAdapter.kvStorage('config').write('task-executor', serializeExecutionRef(seedTeRef));
|
|
888
|
+
}
|
|
889
|
+
} catch (e) {
|
|
890
|
+
logger.warn(`[cloud:${boardId}] failed to seed non-core task-executor config: ${e?.message || e}`);
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
label,
|
|
894
|
+
boardAdapter: cloudBundle.adapter,
|
|
895
|
+
nonCoreAdapter: cloudNonCoreAdapter,
|
|
896
|
+
artifactsAdapter: cloudBundle.adapter,
|
|
897
|
+
baseRef: { kind: 'cloud-board', value: `board:${boardId}` },
|
|
898
|
+
cardStoreRef: `cloud:${boardId}:cards`,
|
|
899
|
+
outputsStoreRef: `cloud:${boardId}:runtime-out`,
|
|
900
|
+
artifactsStoreRef: `cloud:${boardId}:artifacts`,
|
|
901
|
+
scratchStoreRef: `cloud:${boardId}:scratch`,
|
|
902
|
+
archiveStoreRef: `cloud:${boardId}:archive`,
|
|
903
|
+
notifyRef: { kind: 'in-memory-notify', value: notifyChannel },
|
|
904
|
+
taskExecutorRef: cloudHostedTaskExecutorRef,
|
|
905
|
+
chatHandlerFlow,
|
|
906
|
+
inferenceAdapterRef: makeExecutionRef(infAdapterPath),
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
303
910
|
const baseRef = parseRef(serializeRef({ kind: 'fs-path', value: boardDir }));
|
|
304
|
-
const boardAdapter = createFsBoardPlatformAdapter(baseRef, {
|
|
305
|
-
|
|
911
|
+
const boardAdapter = createFsBoardPlatformAdapter(baseRef, {
|
|
912
|
+
notifyChannel,
|
|
913
|
+
...(callbackTransport ? { callbackTransport } : {}),
|
|
914
|
+
});
|
|
915
|
+
const nonCoreAdapter = createFsBoardNonCorePlatformAdapter(baseRef, {
|
|
916
|
+
notifyChannel,
|
|
917
|
+
...(callbackTransport ? { callbackTransport } : {}),
|
|
918
|
+
});
|
|
919
|
+
const localSyncTaskExecutorRef = makeLocalTaskExecutorRef(taskExecPath, executionExtra);
|
|
920
|
+
if (localSyncTaskExecutorRef) {
|
|
921
|
+
const invokeExecutor = nonCoreAdapter.invokeExecutor.bind(nonCoreAdapter);
|
|
922
|
+
nonCoreAdapter.invokeExecutor = (ref, subcommand, execOpts) => {
|
|
923
|
+
const syncRef = isHostedTaskExecutorRef(ref) ? localSyncTaskExecutorRef : ref;
|
|
924
|
+
return invokeExecutor(syncRef, subcommand, execOpts);
|
|
925
|
+
};
|
|
926
|
+
}
|
|
306
927
|
boardAdapter.requestProcessAccumulated = () => {};
|
|
307
928
|
nonCoreAdapter.requestProcessAccumulated = () => {};
|
|
308
929
|
|
|
@@ -325,7 +946,7 @@ function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow,
|
|
|
325
946
|
scratchStoreRef,
|
|
326
947
|
archiveStoreRef,
|
|
327
948
|
notifyRef: { kind: 'named-pipe', value: namedPipePath(notifyChannel) },
|
|
328
|
-
taskExecutorRef:
|
|
949
|
+
taskExecutorRef: makeHostedBoardWorkerRef(boardId, taskExecPath, boardWorkerTransport, executionExtra),
|
|
329
950
|
chatHandlerFlow,
|
|
330
951
|
inferenceAdapterRef: makeExecutionRef(infAdapterPath),
|
|
331
952
|
};
|
|
@@ -401,6 +1022,11 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
401
1022
|
);
|
|
402
1023
|
const infAdapterPath = resolveFromConfig(regular.inferenceAdapterPath) || (entry?.inferenceAdapterPath || configuredInferenceAdapterPath);
|
|
403
1024
|
const stepMachinePath = resolveFromConfig(regular.stepMachineCliPath || cfg?.stepMachineCliPath) || (entry?.stepMachineCliPath || configuredStepMachineCliPath);
|
|
1025
|
+
const runtimeMode = normalizeRuntimeMode(regular.mode || entry?.mode || cfg?.mode || configuredRuntimeMode);
|
|
1026
|
+
const boardWorkerTransport = normalizeHostedBoardWorkerTransport(
|
|
1027
|
+
runtimeMode,
|
|
1028
|
+
normalizeBoardWorkerTransport(regular.taskExecutorTransport || entry?.taskExecutorTransport || configuredBoardWorkerTransport),
|
|
1029
|
+
);
|
|
404
1030
|
const chatInvokeRefTimeoutMs = configuredInvokeRefTimeoutMs;
|
|
405
1031
|
const chatCopilotTimeoutMs = configuredCopilotTimeoutMs;
|
|
406
1032
|
|
|
@@ -436,16 +1062,27 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
436
1062
|
chatFlowRoot,
|
|
437
1063
|
apiBasePath: `${apiBasePath}/${boardId}`,
|
|
438
1064
|
serverUrl: `http://127.0.0.1:${PORT}`,
|
|
1065
|
+
taskExecutorTransport: boardWorkerTransport,
|
|
439
1066
|
chatCopilotTimeoutMs,
|
|
440
1067
|
...(stepMachinePath ? { stepMachineCliPath: stepMachinePath } : {}),
|
|
441
1068
|
};
|
|
442
1069
|
|
|
443
|
-
const baseCfg = buildBoardContextConfig('base', boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, baseExecutionExtra);
|
|
1070
|
+
const baseCfg = buildBoardContextConfig('base', boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, baseExecutionExtra, runtimeMode);
|
|
444
1071
|
const boards = [baseCfg];
|
|
445
1072
|
|
|
446
1073
|
demoPrepSetup({ cardsDir, boardDir });
|
|
447
1074
|
|
|
1075
|
+
if (runtimeMode === 'cloud' && cardsDir) {
|
|
1076
|
+
const cards = createFsCardSource(cardsDir, selectedCardsPattern).listCards();
|
|
1077
|
+
if (cards.length) {
|
|
1078
|
+
const cloudBundle = getCloudBoardBundle(boardId, baseCfg.notifyRef.value);
|
|
1079
|
+
|
|
1080
|
+
seedCloudCardStore(cloudBundle.getKvRef(baseCfg.cardStoreRef), cards);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
448
1084
|
const chatStorage = createFsBoardChatStorage(boardDir);
|
|
1085
|
+
hostedBoardChatStorages.set(boardId, chatStorage);
|
|
449
1086
|
|
|
450
1087
|
const singleBoardRuntime = createSingleBoardServerRuntime({
|
|
451
1088
|
apiBasePath: `${apiBasePath}/${boardId}`,
|
|
@@ -470,12 +1107,50 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
470
1107
|
},
|
|
471
1108
|
});
|
|
472
1109
|
|
|
1110
|
+
const hostedBoardWorkerDispatch = createHostedBoardWorkerDispatcher(boardId, taskExecPath);
|
|
1111
|
+
if (hostedBoardWorkerDispatch) {
|
|
1112
|
+
hostedBoardWorkerDispatchers.set(boardId, hostedBoardWorkerDispatch);
|
|
1113
|
+
}
|
|
1114
|
+
const previousQueueStop = hostedQueueLaneStops.get(boardId);
|
|
1115
|
+
if (previousQueueStop) {
|
|
1116
|
+
previousQueueStop();
|
|
1117
|
+
hostedQueueLaneStops.delete(boardId);
|
|
1118
|
+
}
|
|
1119
|
+
if (boardWorkerTransport === 'in-process-loop' && hostedBoardWorkerDispatch) {
|
|
1120
|
+
registerInProcessExecutionHandler(`board:${boardId}:board-worker`, async (_ref, args) => {
|
|
1121
|
+
void hostedBoardWorkerDispatch(args).catch((err) => {
|
|
1122
|
+
logger.error(`[board-server] in-process board-worker failed for ${boardId}: ${String(err && err.message || err)}`);
|
|
1123
|
+
});
|
|
1124
|
+
return { result: 'success', data: { dispatched: true } };
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
if ((boardWorkerTransport === 'in-process-loop' || boardWorkerTransport === 'queue' || boardWorkerTransport === 'http') && hostedBoardWorkerDispatch) {
|
|
1128
|
+
registerInProcessBoardWorkerCallback(`board:${boardId}:board-worker-callback`, (payload) => {
|
|
1129
|
+
if (payload.outcome === 'success') {
|
|
1130
|
+
return singleBoardRuntime.reportSourceFetched(payload.token, String(payload.ref || ''));
|
|
1131
|
+
}
|
|
1132
|
+
return singleBoardRuntime.reportSourceFetchFailure(payload.token, String(payload.reason || 'unknown'));
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
const stopQueueRunner = startQueueLaneRunners(createHostedBoardQueueLaneRegistry({
|
|
1136
|
+
boardId,
|
|
1137
|
+
runtime: singleBoardRuntime,
|
|
1138
|
+
boardAdapter: baseCfg.boardAdapter,
|
|
1139
|
+
logger,
|
|
1140
|
+
...(boardWorkerTransport === 'queue' && hostedBoardWorkerDispatch
|
|
1141
|
+
? { executeTaskExecutorRequest: hostedBoardWorkerDispatch }
|
|
1142
|
+
: {}),
|
|
1143
|
+
}));
|
|
1144
|
+
hostedQueueLaneStops.set(boardId, stopQueueRunner);
|
|
1145
|
+
|
|
473
1146
|
// Seed card store from source cardsDir if empty
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1147
|
+
if (runtimeMode === 'sync') {
|
|
1148
|
+
const existing = singleBoardRuntime.cardStore.get({});
|
|
1149
|
+
const isEmpty = existing.status !== 'success' || !existing.data?.cards?.length;
|
|
1150
|
+
if (isEmpty && cardsDir) {
|
|
1151
|
+
const cards = createFsCardSource(cardsDir, selectedCardsPattern).listCards();
|
|
1152
|
+
if (cards.length) singleBoardRuntime.cardStore.set({ body: cards });
|
|
1153
|
+
}
|
|
479
1154
|
}
|
|
480
1155
|
|
|
481
1156
|
return singleBoardRuntime;
|
|
@@ -509,7 +1184,7 @@ function jsonReply(res, status, payload) {
|
|
|
509
1184
|
res.end(body);
|
|
510
1185
|
}
|
|
511
1186
|
|
|
512
|
-
const server = http.createServer((req, res) => {
|
|
1187
|
+
const server = http.createServer(async (req, res) => {
|
|
513
1188
|
const method = req.method || 'GET';
|
|
514
1189
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
515
1190
|
const pathname = url.pathname;
|
|
@@ -520,6 +1195,87 @@ const server = http.createServer((req, res) => {
|
|
|
520
1195
|
return;
|
|
521
1196
|
}
|
|
522
1197
|
|
|
1198
|
+
const testSystemChatMatch = pathname.match(/^\/test-req\/boards\/([^/]+)\/chat\/system-message$/);
|
|
1199
|
+
if (method === 'POST' && testSystemChatMatch) {
|
|
1200
|
+
if (!enableTestReq) {
|
|
1201
|
+
jsonReply(res, 404, { error: 'Not found' });
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
try {
|
|
1206
|
+
const boardId = decodeURIComponent(testSystemChatMatch[1]);
|
|
1207
|
+
runtime.requireBoardService(boardId);
|
|
1208
|
+
const chatStorage = hostedBoardChatStorages.get(boardId);
|
|
1209
|
+
if (!chatStorage) {
|
|
1210
|
+
jsonReply(res, 409, { error: `No hosted chat storage configured for board: ${boardId}` });
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const body = await readJsonRequest(req);
|
|
1215
|
+
const cardId = typeof body?.cardId === 'string' ? body.cardId.trim() : '';
|
|
1216
|
+
const text = typeof body?.text === 'string' ? body.text : '';
|
|
1217
|
+
const turn = typeof body?.turn === 'string' ? body.turn : '';
|
|
1218
|
+
const files = Array.isArray(body?.files) ? body.files : [];
|
|
1219
|
+
|
|
1220
|
+
if (!cardId) {
|
|
1221
|
+
jsonReply(res, 400, { error: 'cardId is required' });
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof body?.text !== 'string') {
|
|
1225
|
+
jsonReply(res, 400, { error: 'text is required' });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const id = chatStorage.append(cardId, 'system', text, files, turn);
|
|
1230
|
+
jsonReply(res, 200, {
|
|
1231
|
+
status: 'success',
|
|
1232
|
+
data: {
|
|
1233
|
+
id,
|
|
1234
|
+
boardId,
|
|
1235
|
+
cardId,
|
|
1236
|
+
role: 'system',
|
|
1237
|
+
text,
|
|
1238
|
+
turn,
|
|
1239
|
+
files,
|
|
1240
|
+
},
|
|
1241
|
+
});
|
|
1242
|
+
return;
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
jsonReply(res, 404, { error: String(err && err.message || err) });
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (method === 'POST' && pathname === '/api/board-worker') {
|
|
1250
|
+
try {
|
|
1251
|
+
const body = await readJsonRequest(req);
|
|
1252
|
+
const boardId = typeof body?.extra?.boardId === 'string' ? body.extra.boardId.trim() : '';
|
|
1253
|
+
if (!boardId) {
|
|
1254
|
+
jsonReply(res, 400, { error: 'boardId is required in request.extra.boardId' });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
runtime.requireBoardService(boardId);
|
|
1258
|
+
const dispatcher = hostedBoardWorkerDispatchers.get(boardId);
|
|
1259
|
+
if (!dispatcher) {
|
|
1260
|
+
jsonReply(res, 409, { error: `No hosted board-worker configured for board: ${boardId}` });
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (body?.source_def) {
|
|
1264
|
+
void dispatcher(body).catch((err) => {
|
|
1265
|
+
logger.error(`[board-server] hosted board-worker failed for ${boardId}: ${String(err && err.message || err)}`);
|
|
1266
|
+
});
|
|
1267
|
+
jsonReply(res, 202, { status: 'success', dispatched: true });
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const workerResult = await dispatcher(body);
|
|
1271
|
+
jsonReply(res, 200, workerResult ?? { status: 'success', data: {} });
|
|
1272
|
+
return;
|
|
1273
|
+
} catch (err) {
|
|
1274
|
+
jsonReply(res, 404, { error: String(err && err.message || err) });
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
523
1279
|
// All other /api/boards routes are handled by the platform-free runtime
|
|
524
1280
|
runtime.handleApi(req, res, url).then((handled) => {
|
|
525
1281
|
if (!handled) {
|
|
@@ -546,4 +1302,8 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
546
1302
|
console.log(` GET ${apiBasePath}/:boardId/cards/:id/chats`);
|
|
547
1303
|
console.log(` POST ${apiBasePath}/:boardId/cards/:id/chats/subscribe-sse`);
|
|
548
1304
|
console.log(` POST ${apiBasePath}/:boardId/cards/:id/chats/unsubscribe-sse`);
|
|
1305
|
+
console.log(' POST /api/board-worker');
|
|
1306
|
+
if (enableTestReq) {
|
|
1307
|
+
console.log(' POST /test-req/boards/:boardId/chat/system-message');
|
|
1308
|
+
}
|
|
549
1309
|
});
|