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.
Files changed (176) hide show
  1. package/browser/asset-integrity.json +3 -3
  2. package/examples/board/demo-shell-with-server.html +2 -2
  3. package/examples/board/doc.html +2 -2
  4. package/examples/board/server/board-server.js +773 -13
  5. package/examples/board/server/board-worker/task-executor.js +166 -51
  6. package/examples/board/server/chat-flow/copilot-chat/assistant.js +25 -12
  7. package/examples/board/server/chat-flow/copilot-chat/probe.js +7 -0
  8. package/examples/board/server/chat-flow/copilot-chat/shared.js +97 -0
  9. package/examples/board/server/chat-flow/flow-steps.json +109 -51
  10. package/examples/board/server-config.json +2 -0
  11. package/examples/board/test/server-http-test.js +878 -67
  12. package/examples/board-local/demo-shell-localstorage.html +3 -3
  13. package/lib/{artifacts-store-lib-CVgtQrNZ.d.cts → artifacts-store-lib-BR-Samty.d.cts} +1 -1
  14. package/lib/{artifacts-store-lib-D-k-E8Vy.d.ts → artifacts-store-lib-DT7XlWUL.d.ts} +1 -1
  15. package/lib/artifacts-store-public.cjs +1 -1
  16. package/lib/artifacts-store-public.d.cts +3 -3
  17. package/lib/artifacts-store-public.d.ts +3 -3
  18. package/lib/artifacts-store-public.js +1 -1
  19. package/lib/batch/index.cjs +1 -1
  20. package/lib/batch/index.js +1 -1
  21. package/lib/board-live-cards-mcp.cjs +1 -1
  22. package/lib/board-live-cards-mcp.d.cts +87 -34
  23. package/lib/board-live-cards-mcp.d.ts +87 -34
  24. package/lib/board-live-cards-mcp.js +1 -1
  25. package/lib/board-live-cards-node.cjs +8 -16
  26. package/lib/board-live-cards-node.d.cts +52 -14
  27. package/lib/board-live-cards-node.d.ts +52 -14
  28. package/lib/board-live-cards-node.js +8 -16
  29. package/lib/{board-live-cards-public-BGS22cMb.d.ts → board-live-cards-public-BMUIPOrc.d.ts} +90 -30
  30. package/lib/board-live-cards-public-async-DKZqbJVU.d.ts +256 -0
  31. package/lib/board-live-cards-public-async-dMWNbWq6.d.cts +256 -0
  32. package/lib/{board-live-cards-public-B13InXhC.d.cts → board-live-cards-public-wkNmBIRC.d.cts} +90 -30
  33. package/lib/board-live-cards-public.cjs +1 -2
  34. package/lib/board-live-cards-public.d.cts +2 -2
  35. package/lib/board-live-cards-public.d.ts +2 -2
  36. package/lib/board-live-cards-public.js +1 -2
  37. package/lib/board-live-cards-server-runtime.cjs +1 -7
  38. package/lib/board-live-cards-server-runtime.d.cts +7 -6
  39. package/lib/board-live-cards-server-runtime.d.ts +7 -6
  40. package/lib/board-live-cards-server-runtime.js +1 -7
  41. package/lib/board-livegraph-runtime/index.cjs +1 -2
  42. package/lib/board-livegraph-runtime/index.js +1 -2
  43. package/lib/board-worker-adapter.cjs +22 -7
  44. package/lib/board-worker-adapter.d.cts +28 -3
  45. package/lib/board-worker-adapter.d.ts +28 -3
  46. package/lib/board-worker-adapter.js +22 -7
  47. package/lib/card-compute/index.cjs +1 -9
  48. package/lib/card-compute/index.js +1 -9
  49. package/lib/card-store-public.cjs +1 -1
  50. package/lib/card-store-public.d.cts +2 -2
  51. package/lib/card-store-public.d.ts +2 -2
  52. package/lib/card-store-public.js +1 -1
  53. package/lib/card-validation.cjs +1 -9
  54. package/lib/card-validation.js +1 -9
  55. package/lib/{chat-storage-lib-0imhRX3l.d.cts → chat-storage-lib-BIUbE-fM.d.cts} +1 -1
  56. package/lib/{chat-storage-lib-CJn7a6OH.d.ts → chat-storage-lib-BlG-sobS.d.ts} +1 -1
  57. package/lib/chat-store-public.cjs +1 -1
  58. package/lib/chat-store-public.d.cts +3 -3
  59. package/lib/chat-store-public.d.ts +3 -3
  60. package/lib/chat-store-public.js +1 -1
  61. package/lib/chunk-2MZUYY65.cjs +2 -0
  62. package/lib/chunk-5EA2ESS4.cjs +16 -0
  63. package/lib/chunk-76ON3V7R.js +2 -0
  64. package/lib/chunk-7BKNHFNH.js +2 -0
  65. package/lib/chunk-BQS3EIEK.js +3 -0
  66. package/lib/chunk-CIAJNUR4.js +2 -0
  67. package/lib/chunk-DAXACY63.js +2 -0
  68. package/lib/chunk-FW4363Y4.js +2 -0
  69. package/lib/chunk-FZ2SBU5M.js +3 -0
  70. package/lib/chunk-G4XXRHL2.cjs +3 -0
  71. package/lib/chunk-GJJMEAVN.cjs +2 -0
  72. package/lib/chunk-GNFE24S7.cjs +2 -0
  73. package/lib/chunk-GYQXDNNI.cjs +2 -0
  74. package/lib/chunk-H5HBXPOI.cjs +3 -0
  75. package/lib/chunk-H5KD3JPY.cjs +2 -0
  76. package/lib/chunk-HEEDJEKM.js +2 -0
  77. package/lib/chunk-HLJH7LGW.js +16 -0
  78. package/lib/chunk-IXZG74EW.cjs +2 -0
  79. package/lib/chunk-JAL25FGA.cjs +2 -0
  80. package/lib/chunk-JM5EKT57.js +2 -0
  81. package/lib/chunk-JMDHDY6M.js +2 -0
  82. package/lib/chunk-KBELAKIY.js +2 -0
  83. package/lib/chunk-KHJABJ45.cjs +3 -0
  84. package/lib/chunk-KLRUISRY.cjs +2 -0
  85. package/lib/chunk-KQX6R4PV.cjs +8 -0
  86. package/lib/chunk-LODXIALE.cjs +2 -0
  87. package/lib/chunk-MLVTJASJ.js +2 -0
  88. package/lib/chunk-MNEOJWPS.js +10 -0
  89. package/lib/chunk-N6P2JW4W.js +3 -0
  90. package/lib/chunk-NMZ6XNLB.cjs +3 -0
  91. package/lib/chunk-OEFTOO47.cjs +3 -0
  92. package/lib/chunk-OPNGCSXJ.js +2 -0
  93. package/lib/chunk-OSWJKJLB.js +8 -0
  94. package/lib/chunk-P7ZCDICS.cjs +2 -0
  95. package/lib/chunk-PBCDDO4V.cjs +2 -0
  96. package/lib/chunk-PMUSJQSR.cjs +2 -0
  97. package/lib/chunk-Q6H7NINN.cjs +5 -0
  98. package/lib/chunk-QWBNDVUA.js +5 -0
  99. package/lib/chunk-S6DRP2HX.cjs +2 -0
  100. package/lib/chunk-SCWHDI3I.js +2 -0
  101. package/lib/chunk-SFVO2LB2.cjs +3 -0
  102. package/lib/chunk-U2N6MCD5.cjs +2 -0
  103. package/lib/chunk-UJ7ZTV4J.cjs +10 -0
  104. package/lib/chunk-VGT3TRQG.js +3 -0
  105. package/lib/chunk-VLBB3D6B.js +3 -0
  106. package/lib/chunk-VMW4Z6EF.js +3 -0
  107. package/lib/chunk-WDPOGXTY.js +2 -0
  108. package/lib/chunk-WOALA3V5.cjs +2 -0
  109. package/lib/chunk-X3LC4LII.js +2 -0
  110. package/lib/chunk-XQRNDX4Q.js +2 -0
  111. package/lib/chunk-YGKDQLYP.js +2 -0
  112. package/lib/chunk-YMEIPKLW.cjs +2 -0
  113. package/lib/cloud-storage.cjs +2 -0
  114. package/lib/cloud-storage.d.cts +177 -0
  115. package/lib/cloud-storage.d.ts +177 -0
  116. package/lib/cloud-storage.js +2 -0
  117. package/lib/config/index.cjs +1 -1
  118. package/lib/config/index.js +1 -1
  119. package/lib/continuous-event-graph/index.cjs +1 -2
  120. package/lib/continuous-event-graph/index.js +1 -2
  121. package/lib/event-graph/index.cjs +1 -22
  122. package/lib/event-graph/index.js +1 -22
  123. package/lib/execution-refs.cjs +1 -2
  124. package/lib/execution-refs.d.cts +3 -2
  125. package/lib/execution-refs.d.ts +3 -2
  126. package/lib/execution-refs.js +1 -2
  127. package/lib/index.cjs +2 -24
  128. package/lib/index.d.cts +1 -1
  129. package/lib/index.d.ts +1 -1
  130. package/lib/index.js +2 -24
  131. package/lib/{types-CIgsh56O.d.cts → queue-lane-registry-BPKWWgd4.d.cts} +66 -14
  132. package/lib/{types-30R357js.d.ts → queue-lane-registry-Be6c0ftj.d.ts} +66 -14
  133. package/lib/server-runtime/index.cjs +1 -7
  134. package/lib/server-runtime/index.d.cts +18 -7
  135. package/lib/server-runtime/index.d.ts +18 -7
  136. package/lib/server-runtime/index.js +1 -7
  137. package/lib/step-machine/index.cjs +1 -11
  138. package/lib/step-machine/index.js +1 -11
  139. package/lib/step-machine-public/index.cjs +1 -4
  140. package/lib/step-machine-public/index.d.cts +1 -1
  141. package/lib/step-machine-public/index.d.ts +1 -1
  142. package/lib/step-machine-public/index.js +1 -4
  143. package/lib/{storage-interface-B2WD9D5n.d.cts → storage-interface-BFiD3kyB.d.cts} +38 -1
  144. package/lib/{storage-interface-B2WD9D5n.d.ts → storage-interface-BFiD3kyB.d.ts} +38 -1
  145. package/lib/stores/index.cjs +1 -2
  146. package/lib/stores/index.d.cts +1 -1
  147. package/lib/stores/index.d.ts +1 -1
  148. package/lib/stores/index.js +1 -2
  149. package/lib/stores/kv.cjs +1 -2
  150. package/lib/stores/kv.d.cts +1 -1
  151. package/lib/stores/kv.d.ts +1 -1
  152. package/lib/stores/kv.js +1 -2
  153. package/lib/stores/memory.cjs +1 -1
  154. package/lib/stores/memory.js +1 -1
  155. package/package.json +7 -16
  156. package/cli/board-live-cards-lib-COi4bSpk.d.ts +0 -322
  157. package/cli/browser-api/board-live-cards-browser-adapter.d.ts +0 -36
  158. package/cli/browser-api/board-live-cards-browser-adapter.js +0 -4
  159. package/cli/browser-api/card-store-browser-api.d.ts +0 -25
  160. package/cli/browser-api/card-store-browser-api.js +0 -2
  161. package/cli/browser-api/jsonata-sync.cjs +0 -7623
  162. package/cli/bundled/artifacts-store-cli.mjs +0 -12
  163. package/cli/bundled/batch-runner-cli.mjs +0 -3
  164. package/cli/bundled/board-live-cards-cli.mjs +0 -29
  165. package/cli/bundled/card-store-cli.mjs +0 -154
  166. package/cli/bundled/chat-store-cli.mjs +0 -16
  167. package/cli/bundled/jsonata-sync.cjs +0 -7623
  168. package/cli/bundled/step-machine-cli.mjs +0 -150
  169. package/cli/execution-interface-BCIhu1gO.d.ts +0 -442
  170. package/cli/types-H3EMBPY2.d.ts +0 -398
  171. package/examples/board/server/README-mcp-api.md +0 -690
  172. package/examples/board/test/server-http-mcp-test.js +0 -1280
  173. package/lib/board-livegraph-runtime/jsonata-sync.cjs +0 -7623
  174. package/lib/card-compute/jsonata-sync.cjs +0 -7623
  175. package/lib/continuous-event-graph/jsonata-sync.cjs +0 -7623
  176. 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 = createNamedPipeNotificationTransport();
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, { notifyChannel });
305
- const nonCoreAdapter = createFsBoardNonCorePlatformAdapter(baseRef, { notifyChannel });
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: makeExecutionRef(taskExecPath, executionExtra),
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
- const existing = singleBoardRuntime.cardStore.get({});
475
- const isEmpty = existing.status !== 'success' || !existing.data?.cards?.length;
476
- if (isEmpty && cardsDir) {
477
- const cards = createFsCardSource(cardsDir, selectedCardsPattern).listCards();
478
- if (cards.length) singleBoardRuntime.cardStore.set({ body: cards });
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
  });