yaml-flow 8.6.1 → 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 +544 -45
- package/examples/board/server-config.json +2 -1
- package/examples/board/test/server-http-test.js +12 -4
- package/examples/board-local/demo-shell-localstorage.html +3 -3
- package/lib/{artifacts-store-lib-CLOtsiav.d.cts → artifacts-store-lib-BR-Samty.d.cts} +1 -1
- package/lib/{artifacts-store-lib-C1rtrkxm.d.ts → artifacts-store-lib-DT7XlWUL.d.ts} +1 -1
- package/lib/artifacts-store-public.d.cts +3 -3
- package/lib/artifacts-store-public.d.ts +3 -3
- package/lib/board-live-cards-mcp.cjs +1 -1
- package/lib/board-live-cards-mcp.d.cts +56 -23
- package/lib/board-live-cards-mcp.d.ts +56 -23
- package/lib/board-live-cards-mcp.js +1 -1
- package/lib/board-live-cards-node.cjs +8 -8
- package/lib/board-live-cards-node.d.cts +39 -21
- package/lib/board-live-cards-node.d.ts +39 -21
- package/lib/board-live-cards-node.js +8 -8
- package/lib/{board-live-cards-public-CBVjm327.d.ts → board-live-cards-public-BMUIPOrc.d.ts} +67 -42
- 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-CPJy-aGW.d.cts → board-live-cards-public-wkNmBIRC.d.cts} +67 -42
- package/lib/board-live-cards-public.cjs +1 -1
- 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 -1
- package/lib/board-live-cards-server-runtime.cjs +1 -1
- 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 -1
- package/lib/board-worker-adapter.cjs +4 -3
- package/lib/board-worker-adapter.d.cts +24 -14
- package/lib/board-worker-adapter.d.ts +24 -14
- package/lib/board-worker-adapter.js +4 -3
- package/lib/card-store-public.d.cts +2 -2
- package/lib/card-store-public.d.ts +2 -2
- package/lib/{chat-storage-lib-CKylihjm.d.cts → chat-storage-lib-BIUbE-fM.d.cts} +1 -1
- package/lib/{chat-storage-lib-Bce-xx6l.d.ts → chat-storage-lib-BlG-sobS.d.ts} +1 -1
- package/lib/chat-store-public.d.cts +3 -3
- package/lib/chat-store-public.d.ts +3 -3
- package/lib/{chunk-CWREBRXS.js → chunk-BQS3EIEK.js} +3 -3
- package/lib/{chunk-LDAP75GN.js → chunk-CIAJNUR4.js} +2 -2
- package/lib/chunk-GJJMEAVN.cjs +2 -0
- package/lib/chunk-H5HBXPOI.cjs +3 -0
- package/lib/chunk-HEEDJEKM.js +2 -0
- package/lib/chunk-KQX6R4PV.cjs +8 -0
- package/lib/chunk-MLVTJASJ.js +2 -0
- package/lib/chunk-N6P2JW4W.js +3 -0
- package/lib/chunk-OEFTOO47.cjs +3 -0
- package/lib/chunk-OSWJKJLB.js +8 -0
- package/lib/chunk-PBCDDO4V.cjs +2 -0
- package/lib/{chunk-I4WH5U5D.cjs → chunk-PMUSJQSR.cjs} +2 -2
- package/lib/chunk-SCWHDI3I.js +2 -0
- package/lib/{chunk-UVE65IPR.cjs → chunk-SFVO2LB2.cjs} +3 -3
- package/lib/{chunk-5DB54ZX2.cjs → chunk-U2N6MCD5.cjs} +2 -2
- package/lib/chunk-VMW4Z6EF.js +3 -0
- package/lib/chunk-WOALA3V5.cjs +2 -0
- package/lib/{chunk-LBMEVV4U.js → chunk-XQRNDX4Q.js} +2 -2
- 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/execution-refs.cjs +1 -1
- package/lib/execution-refs.js +1 -1
- package/lib/index.cjs +2 -2
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/{types-DRl0Hy_p.d.cts → queue-lane-registry-BPKWWgd4.d.cts} +66 -14
- package/lib/{types-BuK2UMxk.d.ts → queue-lane-registry-Be6c0ftj.d.ts} +66 -14
- package/lib/server-runtime/index.cjs +1 -1
- 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 -1
- package/lib/step-machine-public/index.cjs +1 -1
- 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 -1
- package/lib/{storage-interface-Ct-C4tlz.d.cts → storage-interface-BFiD3kyB.d.cts} +11 -1
- package/lib/{storage-interface-Ct-C4tlz.d.ts → storage-interface-BFiD3kyB.d.ts} +11 -1
- package/lib/stores/index.cjs +1 -1
- package/lib/stores/index.d.cts +1 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/stores/kv.d.cts +1 -1
- package/lib/stores/kv.d.ts +1 -1
- package/package.json +6 -1
- package/lib/chunk-6APH25VI.js +0 -2
- package/lib/chunk-KNFFDVLD.cjs +0 -2
- package/lib/chunk-LVNQCE5X.cjs +0 -3
- package/lib/chunk-M7EQRS6W.js +0 -3
- package/lib/chunk-NJJ7WEDT.cjs +0 -2
- package/lib/chunk-P64UKI3L.cjs +0 -8
- package/lib/chunk-Q6VSL327.js +0 -8
- package/lib/chunk-VCCTAUIG.js +0 -2
|
@@ -11,12 +11,18 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
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,
|
|
@@ -24,8 +30,9 @@ import {
|
|
|
24
30
|
invokeExecutionRef,
|
|
25
31
|
parseRef,
|
|
26
32
|
registerInProcessExecutionHandler,
|
|
27
|
-
|
|
33
|
+
startQueueLaneRunners,
|
|
28
34
|
serializeRef,
|
|
35
|
+
serializeExecutionRef,
|
|
29
36
|
} from 'yaml-flow/board-live-cards-node';
|
|
30
37
|
import { registerInProcessBoardWorkerCallback } from 'yaml-flow/board-worker-adapter';
|
|
31
38
|
import {
|
|
@@ -144,6 +151,12 @@ const configuredChatFlowTimeoutMs = normalizeTimeoutMs(serverConfig.chatFlowTime
|
|
|
144
151
|
const configuredInvokeRefTimeoutMs = normalizeTimeoutMs(serverConfig.chatInvokeRefTimeoutMs, 300000);
|
|
145
152
|
const configuredCopilotTimeoutMs = normalizeTimeoutMs(serverConfig.chatCopilotTimeoutMs, 300000);
|
|
146
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
|
+
|
|
147
160
|
function normalizeBoardWorkerTransport(value) {
|
|
148
161
|
const normalized = String(value || '').trim().toLowerCase();
|
|
149
162
|
if (normalized === 'http') return 'http';
|
|
@@ -154,6 +167,9 @@ function normalizeBoardWorkerTransport(value) {
|
|
|
154
167
|
const configuredBoardWorkerTransport = normalizeBoardWorkerTransport(
|
|
155
168
|
process.env.DEMO_TASK_EXECUTOR_TRANSPORT || serverConfig.taskExecutorTransport || 'in-process-loop',
|
|
156
169
|
);
|
|
170
|
+
const configuredRuntimeMode = normalizeRuntimeMode(
|
|
171
|
+
process.env.DEMO_SERVER_MODE || serverConfig.mode || 'cloud',
|
|
172
|
+
);
|
|
157
173
|
|
|
158
174
|
// Resolve top-level config defaults (used as fallbacks for per-board config)
|
|
159
175
|
const configuredTaskExecutorPath = resolveFromConfig(serverConfig.taskExecutorPath);
|
|
@@ -293,25 +309,14 @@ function makeHostedBoardWorkerRef(boardId, taskExecPath, transport, executionExt
|
|
|
293
309
|
throw new Error(`Unsupported board-worker transport for demo host: ${transport}`);
|
|
294
310
|
}
|
|
295
311
|
|
|
296
|
-
function
|
|
297
|
-
if (transport === 'in-process-loop' || transport === 'queue') {
|
|
298
|
-
return {
|
|
299
|
-
meta: 'board-live-cards',
|
|
300
|
-
howToRun: 'in-process-loop',
|
|
301
|
-
whatToRun: serializeRef({ kind: 'in-process-loop', value: `board:${boardId}:board-worker-callback` }),
|
|
302
|
-
};
|
|
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`);
|
|
303
315
|
}
|
|
304
316
|
const normalizedServerUrl = typeof serverUrl === 'string' ? serverUrl.trim().replace(/\/+$/, '') : '';
|
|
305
317
|
const normalizedApiBasePath = typeof boardApiBasePath === 'string' ? boardApiBasePath.trim().replace(/\/+$/, '') : '';
|
|
306
318
|
if (!normalizedServerUrl || !normalizedApiBasePath) return undefined;
|
|
307
|
-
return {
|
|
308
|
-
meta: 'board-live-cards',
|
|
309
|
-
howToRun: 'http:post',
|
|
310
|
-
whatToRun: serializeRef({
|
|
311
|
-
kind: 'http-url',
|
|
312
|
-
value: `${normalizedServerUrl}${normalizedApiBasePath}/callback/board-worker`,
|
|
313
|
-
}),
|
|
314
|
-
};
|
|
319
|
+
return createHttpBoardCallbackTransport(`${normalizedServerUrl}${normalizedApiBasePath}/callback/board-worker`);
|
|
315
320
|
}
|
|
316
321
|
|
|
317
322
|
async function readJsonRequest(req) {
|
|
@@ -381,6 +386,437 @@ function createNamedPipeNotificationTransport() {
|
|
|
381
386
|
};
|
|
382
387
|
}
|
|
383
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
|
+
|
|
384
820
|
// ---------------------------------------------------------------------------
|
|
385
821
|
// Server meta store (multi-board registry)
|
|
386
822
|
// ---------------------------------------------------------------------------
|
|
@@ -397,17 +833,17 @@ const serverMetaStore = createArtifactsStore(serverMetaAdapter.blobStorage('serv
|
|
|
397
833
|
|
|
398
834
|
const apiBasePath = '/api/boards';
|
|
399
835
|
const invocationAdapter = createNodeSpawnInvocationAdapter();
|
|
400
|
-
const notificationTransport =
|
|
836
|
+
const notificationTransport = createNotificationTransport();
|
|
401
837
|
const logger = { info: console.log, warn: console.warn, error: console.error };
|
|
402
838
|
const hostedBoardWorkerDispatchers = new Map();
|
|
403
|
-
const
|
|
839
|
+
const hostedQueueLaneStops = new Map();
|
|
404
840
|
const hostedBoardChatStorages = new Map();
|
|
405
841
|
|
|
406
842
|
// Map config keys to board entries for the factory
|
|
407
843
|
const boardConfigEntries = serverConfig.boards ? Object.entries(serverConfig.boards) : [];
|
|
408
844
|
const boardConfigMap = new Map(boardConfigEntries);
|
|
409
845
|
|
|
410
|
-
function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, executionExtra = {}) {
|
|
846
|
+
function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, executionExtra = {}, runtimeMode = 'sync') {
|
|
411
847
|
fs.mkdirSync(boardDir, { recursive: true });
|
|
412
848
|
const runtimeCardsDir = path.join(path.dirname(boardDir), 'cards');
|
|
413
849
|
const runtimeCardStoreDir = path.join(runtimeCardsDir, 'store');
|
|
@@ -419,16 +855,66 @@ function buildBoardContextConfig(label, boardDir, taskExecPath, chatHandlerFlow,
|
|
|
419
855
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
420
856
|
|
|
421
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
|
+
|
|
422
910
|
const baseRef = parseRef(serializeRef({ kind: 'fs-path', value: boardDir }));
|
|
423
|
-
const boardWorkerTransport = normalizeBoardWorkerTransport(executionExtra.taskExecutorTransport);
|
|
424
|
-
const callbackSelfRef = makeBoardWorkerCallbackSelfRef(executionExtra.serverUrl, executionExtra.apiBasePath, boardWorkerTransport, boardId);
|
|
425
911
|
const boardAdapter = createFsBoardPlatformAdapter(baseRef, {
|
|
426
912
|
notifyChannel,
|
|
427
|
-
...(
|
|
913
|
+
...(callbackTransport ? { callbackTransport } : {}),
|
|
428
914
|
});
|
|
429
915
|
const nonCoreAdapter = createFsBoardNonCorePlatformAdapter(baseRef, {
|
|
430
916
|
notifyChannel,
|
|
431
|
-
...(
|
|
917
|
+
...(callbackTransport ? { callbackTransport } : {}),
|
|
432
918
|
});
|
|
433
919
|
const localSyncTaskExecutorRef = makeLocalTaskExecutorRef(taskExecPath, executionExtra);
|
|
434
920
|
if (localSyncTaskExecutorRef) {
|
|
@@ -536,7 +1022,11 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
536
1022
|
);
|
|
537
1023
|
const infAdapterPath = resolveFromConfig(regular.inferenceAdapterPath) || (entry?.inferenceAdapterPath || configuredInferenceAdapterPath);
|
|
538
1024
|
const stepMachinePath = resolveFromConfig(regular.stepMachineCliPath || cfg?.stepMachineCliPath) || (entry?.stepMachineCliPath || configuredStepMachineCliPath);
|
|
539
|
-
const
|
|
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
|
+
);
|
|
540
1030
|
const chatInvokeRefTimeoutMs = configuredInvokeRefTimeoutMs;
|
|
541
1031
|
const chatCopilotTimeoutMs = configuredCopilotTimeoutMs;
|
|
542
1032
|
|
|
@@ -577,11 +1067,20 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
577
1067
|
...(stepMachinePath ? { stepMachineCliPath: stepMachinePath } : {}),
|
|
578
1068
|
};
|
|
579
1069
|
|
|
580
|
-
const baseCfg = buildBoardContextConfig('base', boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, baseExecutionExtra);
|
|
1070
|
+
const baseCfg = buildBoardContextConfig('base', boardDir, taskExecPath, chatHandlerFlow, infAdapterPath, boardId, baseExecutionExtra, runtimeMode);
|
|
581
1071
|
const boards = [baseCfg];
|
|
582
1072
|
|
|
583
1073
|
demoPrepSetup({ cardsDir, boardDir });
|
|
584
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
|
+
|
|
585
1084
|
const chatStorage = createFsBoardChatStorage(boardDir);
|
|
586
1085
|
hostedBoardChatStorages.set(boardId, chatStorage);
|
|
587
1086
|
|
|
@@ -612,10 +1111,10 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
612
1111
|
if (hostedBoardWorkerDispatch) {
|
|
613
1112
|
hostedBoardWorkerDispatchers.set(boardId, hostedBoardWorkerDispatch);
|
|
614
1113
|
}
|
|
615
|
-
const previousQueueStop =
|
|
1114
|
+
const previousQueueStop = hostedQueueLaneStops.get(boardId);
|
|
616
1115
|
if (previousQueueStop) {
|
|
617
1116
|
previousQueueStop();
|
|
618
|
-
|
|
1117
|
+
hostedQueueLaneStops.delete(boardId);
|
|
619
1118
|
}
|
|
620
1119
|
if (boardWorkerTransport === 'in-process-loop' && hostedBoardWorkerDispatch) {
|
|
621
1120
|
registerInProcessExecutionHandler(`board:${boardId}:board-worker`, async (_ref, args) => {
|
|
@@ -625,7 +1124,7 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
625
1124
|
return { result: 'success', data: { dispatched: true } };
|
|
626
1125
|
});
|
|
627
1126
|
}
|
|
628
|
-
if ((boardWorkerTransport === 'in-process-loop' || boardWorkerTransport === 'queue') && hostedBoardWorkerDispatch) {
|
|
1127
|
+
if ((boardWorkerTransport === 'in-process-loop' || boardWorkerTransport === 'queue' || boardWorkerTransport === 'http') && hostedBoardWorkerDispatch) {
|
|
629
1128
|
registerInProcessBoardWorkerCallback(`board:${boardId}:board-worker-callback`, (payload) => {
|
|
630
1129
|
if (payload.outcome === 'success') {
|
|
631
1130
|
return singleBoardRuntime.reportSourceFetched(payload.token, String(payload.ref || ''));
|
|
@@ -633,25 +1132,25 @@ const runtime = createMultiBoardServerRuntime({
|
|
|
633
1132
|
return singleBoardRuntime.reportSourceFetchFailure(payload.token, String(payload.reason || 'unknown'));
|
|
634
1133
|
});
|
|
635
1134
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
hostedBoardWorkerQueueStops.set(boardId, stopQueueRunner);
|
|
647
|
-
}
|
|
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);
|
|
648
1145
|
|
|
649
1146
|
// Seed card store from source cardsDir if empty
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
+
}
|
|
655
1154
|
}
|
|
656
1155
|
|
|
657
1156
|
return singleBoardRuntime;
|