yaml-flow 5.2.5 → 5.2.8
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/README.md +6 -6
- package/board-livecards-server-runtime.js +260 -35
- package/browser/board-livegraph-engine.js +57 -32
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/card-compute.js +17 -17
- package/browser/live-cards.js +139 -12
- package/browser/live-cards.schema.json +14 -9
- package/dist/board-livegraph-runtime/index.cjs +57 -32
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +1 -1
- package/dist/board-livegraph-runtime/index.d.ts +1 -1
- package/dist/board-livegraph-runtime/index.js +57 -32
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +96 -38
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +13 -8
- package/dist/card-compute/index.d.ts +13 -8
- package/dist/card-compute/index.js +96 -38
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +7200 -201
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +6 -6
- package/dist/cli/board-live-cards-cli.d.ts +6 -6
- package/dist/cli/board-live-cards-cli.js +7199 -201
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +55 -30
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +2 -2
- package/dist/continuous-event-graph/index.d.ts +2 -2
- package/dist/continuous-event-graph/index.js +55 -30
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +121 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +121 -53
- package/dist/index.js.map +1 -1
- package/dist/{live-cards-bridge-CeNxiVcm.d.ts → live-cards-bridge-EQjytzI_.d.ts} +10 -5
- package/dist/{live-cards-bridge-z_rJCSbi.d.cts → live-cards-bridge-x5XREkXm.d.cts} +10 -5
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +2 -2
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +10 -10
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +2 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
- package/examples/example-board/agent-instructions-cardlayout.md +1 -1
- package/examples/example-board/agent-instructions.md +271 -45
- package/examples/example-board/cards/card-concentration.json +8 -5
- package/examples/example-board/cards/card-market-prices.json +14 -9
- package/examples/example-board/cards/card-my-identity.json +28 -0
- package/examples/example-board/cards/card-portfolio-value.json +1 -1
- package/examples/example-board/cards/card-portfolio.json +1 -1
- package/examples/example-board/cards/card-rebalance-impact.json +65 -0
- package/examples/example-board/cards/card-rebalance-sim.json +57 -0
- package/examples/example-board/demo-chat-handler.js +2 -1
- package/examples/example-board/demo-server-config.json +6 -1
- package/examples/example-board/demo-server.js +79 -8
- package/examples/example-board/demo-shell-browser.html +6 -6
- package/examples/example-board/demo-shell-with-server.html +4 -4
- package/examples/example-board/demo-task-executor.js +436 -246
- package/examples/example-board/scripts/copilot_wrapper.bat +157 -0
- package/examples/example-board/scripts/copilot_wrapper_helper.ps1 +190 -0
- package/examples/example-board/scripts/workiq_wrapper.mjs +66 -0
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +5 -5
- package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +3 -3
- package/examples/npm-libs/event-graph/research-pipeline.ts +5 -5
- package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +9 -9
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
- package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
- package/package.json +2 -2
- package/schema/live-cards.schema.json +14 -9
package/README.md
CHANGED
|
@@ -263,14 +263,14 @@ settings:
|
|
|
263
263
|
|
|
264
264
|
tasks:
|
|
265
265
|
fetch_sources:
|
|
266
|
-
provides: [raw-
|
|
266
|
+
provides: [raw-asources]
|
|
267
267
|
|
|
268
268
|
analyse_sentiment:
|
|
269
|
-
requires: [raw-
|
|
269
|
+
requires: [raw-asources]
|
|
270
270
|
provides: [sentiment-result]
|
|
271
271
|
|
|
272
272
|
analyse_entities:
|
|
273
|
-
requires: [raw-
|
|
273
|
+
requires: [raw-asources]
|
|
274
274
|
provides: [entity-result]
|
|
275
275
|
|
|
276
276
|
merge_analysis:
|
|
@@ -384,7 +384,7 @@ When multiple eligible tasks produce the same output token, only one should run
|
|
|
384
384
|
|
|
385
385
|
### Pattern: AI Agent Tool Orchestration (Event Graph)
|
|
386
386
|
|
|
387
|
-
An agent needs to gather evidence from multiple
|
|
387
|
+
An agent needs to gather evidence from multiple source_defs, then synthesize.
|
|
388
388
|
|
|
389
389
|
```yaml
|
|
390
390
|
settings:
|
|
@@ -735,7 +735,7 @@ Templates first (expands references), then variables (fills in `${...}` placehol
|
|
|
735
735
|
```typescript
|
|
736
736
|
import { resolveConfigTemplates, resolveVariables } from 'yaml-flow/config';
|
|
737
737
|
|
|
738
|
-
const raw = loadYaml('pipeline.yaml'); // has configTemplates + ${VAR}
|
|
738
|
+
const raw = loadYaml('pipeline.yaml'); // has configTemplates + ${VAR} projections
|
|
739
739
|
const resolved = resolveVariables(
|
|
740
740
|
resolveConfigTemplates(raw),
|
|
741
741
|
{ ENTITY_ID: 'url-42', TOOLS_DIR: '/opt/tools' },
|
|
@@ -971,7 +971,7 @@ const r3 = validateLiveCardSchema(config);
|
|
|
971
971
|
|---|---|---|
|
|
972
972
|
| `validateGraphSchema` | `schema/event-graph.schema.json` | Tasks, settings, refreshStrategy, retry, circuit_breaker, inference hints |
|
|
973
973
|
| `validateFlowSchema` | `schema/flow.schema.json` | Steps, transitions, retry, terminal states |
|
|
974
|
-
| `validateLiveCardSchema` | `schema/live-cards.schema.json` | Cards,
|
|
974
|
+
| `validateLiveCardSchema` | `schema/live-cards.schema.json` | Cards, source_defs, elements, compute, data bindings |
|
|
975
975
|
|
|
976
976
|
All validators are synchronous, pure functions. They return `{ ok: boolean, errors?: ErrorObject[] }`.
|
|
977
977
|
|
|
@@ -38,6 +38,22 @@ function parseUrl(urlString) {
|
|
|
38
38
|
return new URL(urlString, 'http://localhost');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Merges `extraFields` into the `extra` object inside a `.task-executor` JSON file.
|
|
43
|
+
* No-op if the file doesn't exist or isn't valid JSON.
|
|
44
|
+
*/
|
|
45
|
+
function refreshTaskExecutorExtra(runtimeDir, extraFields) {
|
|
46
|
+
const taskExecutorFile = path.join(runtimeDir, '.task-executor');
|
|
47
|
+
if (!fs.existsSync(taskExecutorFile)) return;
|
|
48
|
+
try {
|
|
49
|
+
const current = JSON.parse(fs.readFileSync(taskExecutorFile, 'utf-8'));
|
|
50
|
+
const merged = { ...current, extra: { ...(current.extra || {}), ...extraFields } };
|
|
51
|
+
fs.writeFileSync(taskExecutorFile, JSON.stringify(merged, null, 2), 'utf-8');
|
|
52
|
+
} catch {
|
|
53
|
+
// Silently ignore — board will still function, extra is best-effort
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
export function createRuntimeRequestDispatcher(runtime) {
|
|
42
58
|
if (!runtime || typeof runtime !== 'object') {
|
|
43
59
|
throw new Error('runtime is required');
|
|
@@ -147,6 +163,18 @@ export function createMultiBoardServerRuntime(options = {}) {
|
|
|
147
163
|
const defaultInferenceAdapterPath = typeof entry.inferenceAdapterPath === 'string'
|
|
148
164
|
? entry.inferenceAdapterPath
|
|
149
165
|
: options.defaultInferenceAdapterPath;
|
|
166
|
+
const gandalfCardsDir = typeof entry.gandalfCardsDir === 'string'
|
|
167
|
+
? entry.gandalfCardsDir
|
|
168
|
+
: (options.defaultGandalfCardsDir || null);
|
|
169
|
+
const gandalfTaskExecutorPath = typeof entry.gandalfTaskExecutorPath === 'string'
|
|
170
|
+
? entry.gandalfTaskExecutorPath
|
|
171
|
+
: (options.defaultGandalfTaskExecutorPath || null);
|
|
172
|
+
const gandalfChatHandlerPath = typeof entry.gandalfChatHandlerPath === 'string'
|
|
173
|
+
? entry.gandalfChatHandlerPath
|
|
174
|
+
: (options.defaultGandalfChatHandlerPath || null);
|
|
175
|
+
const gandalfInferenceAdapterPath = typeof entry.gandalfInferenceAdapterPath === 'string'
|
|
176
|
+
? entry.gandalfInferenceAdapterPath
|
|
177
|
+
: (options.defaultGandalfInferenceAdapterPath || null);
|
|
150
178
|
|
|
151
179
|
const service = createExampleBoardServerRuntime({
|
|
152
180
|
apiBasePath: `${apiBasePath}/${boardId}`,
|
|
@@ -160,7 +188,14 @@ export function createMultiBoardServerRuntime(options = {}) {
|
|
|
160
188
|
defaultStepMachineCliPath,
|
|
161
189
|
defaultChatHandlerPath,
|
|
162
190
|
defaultInferenceAdapterPath,
|
|
191
|
+
gandalfCardsDir,
|
|
192
|
+
gandalfRuntimeDir: path.join(boardRoot, 'gandalf-runtime'),
|
|
193
|
+
gandalfRuntimeOutDir: path.join(boardRoot, 'gandalf-runtime-out'),
|
|
194
|
+
gandalfTaskExecutorPath,
|
|
195
|
+
gandalfChatHandlerPath,
|
|
196
|
+
gandalfInferenceAdapterPath,
|
|
163
197
|
boardLiveCardsCliJs: options.boardLiveCardsCliJs,
|
|
198
|
+
serverUrl: options.serverUrl || null,
|
|
164
199
|
});
|
|
165
200
|
|
|
166
201
|
boardServiceCache.set(boardId, service);
|
|
@@ -346,6 +381,49 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
346
381
|
const boardFile = path.join(boardDir, 'board-graph.json');
|
|
347
382
|
const inventoryFile = path.join(boardDir, 'cards-inventory.jsonl');
|
|
348
383
|
|
|
384
|
+
// Board-cards: parallel runtime dirs for the board-manager board.
|
|
385
|
+
const gandalfCardsDir = options.gandalfCardsDir ? path.resolve(options.gandalfCardsDir) : null;
|
|
386
|
+
const gandalfRuntimeDir = path.resolve(options.gandalfRuntimeDir || path.join(path.dirname(boardDir), 'gandalf-runtime'));
|
|
387
|
+
const gandalfRuntimeOutDir = path.resolve(options.gandalfRuntimeOutDir || path.join(path.dirname(boardDir), 'gandalf-runtime-out'));
|
|
388
|
+
const tmpGandalfCardsDir = path.join(tmpSurfaceDir, 'tmp-gandalf-cards');
|
|
389
|
+
const gandalfInventoryFile = path.join(gandalfRuntimeDir, 'cards-inventory.jsonl');
|
|
390
|
+
const gandalfBoardFile = path.join(gandalfRuntimeDir, 'board-graph.json');
|
|
391
|
+
const gandalfStatusSnapshotFile = path.join(gandalfRuntimeOutDir, 'board-livegraph-status.json');
|
|
392
|
+
|
|
393
|
+
// Explicit gandalf-card executor paths — no fallback to regular-card paths.
|
|
394
|
+
const configuredGandalfTaskExecutorPath = typeof options.gandalfTaskExecutorPath === 'string' && options.gandalfTaskExecutorPath.trim()
|
|
395
|
+
? (path.isAbsolute(options.gandalfTaskExecutorPath) ? options.gandalfTaskExecutorPath : path.resolve(process.cwd(), options.gandalfTaskExecutorPath))
|
|
396
|
+
: null;
|
|
397
|
+
const configuredGandalfChatHandlerPath = typeof options.gandalfChatHandlerPath === 'string' && options.gandalfChatHandlerPath.trim()
|
|
398
|
+
? (path.isAbsolute(options.gandalfChatHandlerPath) ? options.gandalfChatHandlerPath : path.resolve(process.cwd(), options.gandalfChatHandlerPath))
|
|
399
|
+
: null;
|
|
400
|
+
const configuredGandalfInferenceAdapterPath = typeof options.gandalfInferenceAdapterPath === 'string' && options.gandalfInferenceAdapterPath.trim()
|
|
401
|
+
? (path.isAbsolute(options.gandalfInferenceAdapterPath) ? options.gandalfInferenceAdapterPath : path.resolve(process.cwd(), options.gandalfInferenceAdapterPath))
|
|
402
|
+
: null;
|
|
403
|
+
|
|
404
|
+
// Server URL passed down from the hosting server (e.g. demo-server) so executors/handlers
|
|
405
|
+
// can call back to server-side proxy endpoints (e.g. /api/workiq/ask).
|
|
406
|
+
const serverUrl = typeof options.serverUrl === 'string' && options.serverUrl.trim()
|
|
407
|
+
? options.serverUrl.trim().replace(/\/$/, '')
|
|
408
|
+
: null;
|
|
409
|
+
|
|
410
|
+
// Board-card ID cache: O(1) lookup, mtime-refreshed each SSE tick.
|
|
411
|
+
let _gandalfCardIds = new Set();
|
|
412
|
+
let _gandalfInventoryMtime = 0;
|
|
413
|
+
function _refreshGandalfCardCache() {
|
|
414
|
+
if (!fs.existsSync(gandalfInventoryFile)) { _gandalfCardIds = new Set(); return; }
|
|
415
|
+
const mtime = fs.statSync(gandalfInventoryFile).mtimeMs;
|
|
416
|
+
if (mtime === _gandalfInventoryMtime) return;
|
|
417
|
+
_gandalfInventoryMtime = mtime;
|
|
418
|
+
_gandalfCardIds = new Set(
|
|
419
|
+
fs.readFileSync(gandalfInventoryFile, 'utf-8')
|
|
420
|
+
.split('\n').filter(Boolean)
|
|
421
|
+
.map(l => { try { return JSON.parse(l).cardId; } catch { return null; } })
|
|
422
|
+
.filter(Boolean)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function isGandalfCard(cardId) { return _gandalfCardIds.has(cardId); }
|
|
426
|
+
|
|
349
427
|
let didDemoSetup = false;
|
|
350
428
|
|
|
351
429
|
function resolveCliJsPath() {
|
|
@@ -380,7 +458,8 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
380
458
|
|
|
381
459
|
function ensureCardStorageDirs(cardId) {
|
|
382
460
|
const safeCardId = String(cardId || '').replace(/[^a-zA-Z0-9_-]/g, '_') || 'unknown-card';
|
|
383
|
-
const
|
|
461
|
+
const baseDir = isGandalfCard(cardId) ? tmpGandalfCardsDir : tmpCardsDir;
|
|
462
|
+
const cardDir = path.join(baseDir, safeCardId);
|
|
384
463
|
const filesDir = path.join(cardDir, 'files');
|
|
385
464
|
const chatsDir = path.join(cardDir, 'chats');
|
|
386
465
|
fs.mkdirSync(filesDir, { recursive: true });
|
|
@@ -491,41 +570,60 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
491
570
|
.map((l) => JSON.parse(l));
|
|
492
571
|
}
|
|
493
572
|
|
|
573
|
+
function readGandalfInventory() {
|
|
574
|
+
if (!fs.existsSync(gandalfInventoryFile)) return [];
|
|
575
|
+
return fs
|
|
576
|
+
.readFileSync(gandalfInventoryFile, 'utf-8')
|
|
577
|
+
.split('\n')
|
|
578
|
+
.map((l) => l.trim())
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
.map((l) => JSON.parse(l));
|
|
581
|
+
}
|
|
582
|
+
|
|
494
583
|
function readStatusSnapshot() {
|
|
495
|
-
|
|
496
|
-
|
|
584
|
+
const base = fs.existsSync(statusSnapshotFile) ? readJson(statusSnapshotFile) : null;
|
|
585
|
+
const boardSnap = fs.existsSync(gandalfStatusSnapshotFile) ? readJson(gandalfStatusSnapshotFile) : null;
|
|
586
|
+
if (!base && !boardSnap) return null;
|
|
587
|
+
if (!boardSnap) return base;
|
|
588
|
+
if (!base) return boardSnap;
|
|
589
|
+
return { ...base, tasks: { ...(base.tasks || {}), ...(boardSnap.tasks || {}) } };
|
|
497
590
|
}
|
|
498
591
|
|
|
499
592
|
function readCardDefinitions() {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
593
|
+
function readFromInventory(invFile) {
|
|
594
|
+
if (!fs.existsSync(invFile)) return [];
|
|
595
|
+
const inv = fs.readFileSync(invFile, 'utf-8').split('\n').map(l => l.trim()).filter(Boolean).map(l => JSON.parse(l));
|
|
596
|
+
const out = [];
|
|
597
|
+
for (const entry of inv) {
|
|
598
|
+
if (!entry || !entry.cardId || !entry.cardFilePath) continue;
|
|
599
|
+
if (!fs.existsSync(entry.cardFilePath)) continue;
|
|
600
|
+
out.push(readJson(entry.cardFilePath));
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
506
603
|
}
|
|
507
|
-
return
|
|
604
|
+
return [...readFromInventory(inventoryFile), ...readFromInventory(gandalfInventoryFile)];
|
|
508
605
|
}
|
|
509
606
|
|
|
510
607
|
function readCardRuntimeArtifacts() {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
608
|
+
function readFromDir(dir) {
|
|
609
|
+
const cardsOutDir = path.join(dir, 'cards');
|
|
610
|
+
if (!fs.existsSync(cardsOutDir)) return {};
|
|
611
|
+
const out = {};
|
|
612
|
+
for (const entry of fs.readdirSync(cardsOutDir, { withFileTypes: true })) {
|
|
613
|
+
if (!entry.isFile() || !entry.name.endsWith('.computed.json')) continue;
|
|
614
|
+
const cardId = entry.name.slice(0, -'.computed.json'.length);
|
|
615
|
+
out[cardId] = readJson(path.join(cardsOutDir, entry.name));
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
520
618
|
}
|
|
521
|
-
return
|
|
619
|
+
return { ...readFromDir(runtimeOutDir), ...readFromDir(gandalfRuntimeOutDir) };
|
|
522
620
|
}
|
|
523
621
|
|
|
524
622
|
function readSourcePayloads(cardDefinition) {
|
|
525
623
|
const out = {};
|
|
526
|
-
if (!cardDefinition || !Array.isArray(cardDefinition.
|
|
624
|
+
if (!cardDefinition || !Array.isArray(cardDefinition.source_defs)) return out;
|
|
527
625
|
|
|
528
|
-
for (const sourceDef of cardDefinition.
|
|
626
|
+
for (const sourceDef of cardDefinition.source_defs) {
|
|
529
627
|
if (!sourceDef || !sourceDef.bindTo || !sourceDef.outputFile) continue;
|
|
530
628
|
const filePath = path.join(boardDir, cardDefinition.id, sourceDef.outputFile);
|
|
531
629
|
if (!fs.existsSync(filePath)) continue;
|
|
@@ -561,7 +659,8 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
561
659
|
}
|
|
562
660
|
|
|
563
661
|
function readChatSignal(cardId) {
|
|
564
|
-
const
|
|
662
|
+
const baseDir = isGandalfCard(cardId) ? tmpGandalfCardsDir : tmpCardsDir;
|
|
663
|
+
const chatsDir = path.join(baseDir, cardId, 'chats');
|
|
565
664
|
if (!fs.existsSync(chatsDir)) {
|
|
566
665
|
return { count: 0, latest_mtime_ms: 0, processing: false };
|
|
567
666
|
}
|
|
@@ -644,6 +743,16 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
644
743
|
fs.copyFileSync(src, dst);
|
|
645
744
|
}
|
|
646
745
|
|
|
746
|
+
// Copy gandalf-card templates if gandalfCardsDir is configured.
|
|
747
|
+
if (gandalfCardsDir && fs.existsSync(gandalfCardsDir)) {
|
|
748
|
+
fs.rmSync(tmpGandalfCardsDir, { recursive: true, force: true });
|
|
749
|
+
fs.mkdirSync(tmpGandalfCardsDir, { recursive: true });
|
|
750
|
+
for (const entry of fs.readdirSync(gandalfCardsDir, { withFileTypes: true })) {
|
|
751
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.json')) continue;
|
|
752
|
+
fs.copyFileSync(path.join(gandalfCardsDir, entry.name), path.join(tmpGandalfCardsDir, entry.name));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
647
756
|
// Concatenate agent-instructions*.md files into copilot-instructions.md at boardSetupRoot
|
|
648
757
|
const boardSetupRoot = path.dirname(boardDir);
|
|
649
758
|
const agentInstructionFiles = ['agent-instructions.md', 'agent-instructions-cardlayout.md'];
|
|
@@ -662,8 +771,12 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
662
771
|
didDemoSetup = true;
|
|
663
772
|
}
|
|
664
773
|
|
|
774
|
+
function isDemoSetupDone() {
|
|
775
|
+
return didDemoSetup && fs.existsSync(tmpCardsDir);
|
|
776
|
+
}
|
|
777
|
+
|
|
665
778
|
function ensureDemoSetup() {
|
|
666
|
-
if (
|
|
779
|
+
if (isDemoSetupDone()) return;
|
|
667
780
|
demoPrepSetup();
|
|
668
781
|
}
|
|
669
782
|
|
|
@@ -713,6 +826,69 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
713
826
|
return resolved;
|
|
714
827
|
}
|
|
715
828
|
|
|
829
|
+
function resolveGandalfTaskExecutorPath() {
|
|
830
|
+
if (!configuredGandalfTaskExecutorPath) return null;
|
|
831
|
+
if (!fs.existsSync(configuredGandalfTaskExecutorPath)) {
|
|
832
|
+
const err = new Error(`Gandalf task executor script not found: ${configuredGandalfTaskExecutorPath}`);
|
|
833
|
+
err.statusCode = 400;
|
|
834
|
+
throw err;
|
|
835
|
+
}
|
|
836
|
+
return configuredGandalfTaskExecutorPath;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function resolveGandalfChatHandlerPath() {
|
|
840
|
+
if (!configuredGandalfChatHandlerPath) return null;
|
|
841
|
+
if (!fs.existsSync(configuredGandalfChatHandlerPath)) {
|
|
842
|
+
const err = new Error(`Gandalf chat handler script not found: ${configuredGandalfChatHandlerPath}`);
|
|
843
|
+
err.statusCode = 400;
|
|
844
|
+
throw err;
|
|
845
|
+
}
|
|
846
|
+
return configuredGandalfChatHandlerPath;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function resolveGandalfInferenceAdapterPath() {
|
|
850
|
+
if (!configuredGandalfInferenceAdapterPath) return null;
|
|
851
|
+
if (!fs.existsSync(configuredGandalfInferenceAdapterPath)) {
|
|
852
|
+
const err = new Error(`Gandalf inference adapter script not found: ${configuredGandalfInferenceAdapterPath}`);
|
|
853
|
+
err.statusCode = 400;
|
|
854
|
+
throw err;
|
|
855
|
+
}
|
|
856
|
+
return configuredGandalfInferenceAdapterPath;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function initGandalfCards() {
|
|
860
|
+
const taskExecutorPath = resolveGandalfTaskExecutorPath();
|
|
861
|
+
if (!taskExecutorPath) return; // gandalf-cards not configured; skip.
|
|
862
|
+
fs.mkdirSync(gandalfRuntimeDir, { recursive: true });
|
|
863
|
+
const chatHandlerPath = resolveGandalfChatHandlerPath();
|
|
864
|
+
const inferenceAdapterPath = resolveGandalfInferenceAdapterPath();
|
|
865
|
+
const taskExecutorCmd = `${shellQuote(process.execPath)} ${shellQuote(taskExecutorPath)}`;
|
|
866
|
+
const chatHandlerCmd = chatHandlerPath ? `${shellQuote(process.execPath)} ${shellQuote(chatHandlerPath)}` : null;
|
|
867
|
+
const inferenceAdapterCmd = inferenceAdapterPath ? `${shellQuote(process.execPath)} ${shellQuote(inferenceAdapterPath)}` : null;
|
|
868
|
+
const boardSetupRoot = path.dirname(boardDir);
|
|
869
|
+
const taskExecutorExtra = JSON.stringify({
|
|
870
|
+
boardSetupRoot,
|
|
871
|
+
boardId,
|
|
872
|
+
boardRuntimeDir: path.relative(boardSetupRoot, gandalfRuntimeDir),
|
|
873
|
+
runtimeStatusDir: path.relative(boardSetupRoot, gandalfRuntimeOutDir),
|
|
874
|
+
cardsDir: path.relative(boardSetupRoot, tmpGandalfCardsDir),
|
|
875
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
876
|
+
});
|
|
877
|
+
const initArgs = ['init', gandalfRuntimeDir, '--task-executor', taskExecutorCmd, '--task-executor-extra', taskExecutorExtra];
|
|
878
|
+
if (chatHandlerCmd) initArgs.push('--chat-handler', chatHandlerCmd);
|
|
879
|
+
if (inferenceAdapterCmd) initArgs.push('--inference-adapter', inferenceAdapterCmd);
|
|
880
|
+
initArgs.push('--runtime-out', gandalfRuntimeOutDir);
|
|
881
|
+
try {
|
|
882
|
+
runCli(initArgs);
|
|
883
|
+
} catch (err) {
|
|
884
|
+
const msg = String((err && err.message) || err);
|
|
885
|
+
if (!msg.includes('no valid board-graph.json')) throw err;
|
|
886
|
+
clearDirContents(gandalfRuntimeDir);
|
|
887
|
+
fs.mkdirSync(gandalfRuntimeDir, { recursive: true });
|
|
888
|
+
runCli(initArgs);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
716
892
|
function initBoard(taskExecutorPathParam, chatHandlerPathParam, inferenceAdapterPathParam) {
|
|
717
893
|
fs.mkdirSync(boardDir, { recursive: true });
|
|
718
894
|
|
|
@@ -727,7 +903,17 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
727
903
|
? `${shellQuote(process.execPath)} ${shellQuote(inferenceAdapterPath)}`
|
|
728
904
|
: null;
|
|
729
905
|
|
|
730
|
-
const
|
|
906
|
+
const boardSetupRoot = path.dirname(boardDir);
|
|
907
|
+
const taskExecutorExtra = JSON.stringify({
|
|
908
|
+
boardSetupRoot,
|
|
909
|
+
boardId,
|
|
910
|
+
boardRuntimeDir: path.relative(boardSetupRoot, boardDir),
|
|
911
|
+
runtimeStatusDir: path.relative(boardSetupRoot, runtimeOutDir),
|
|
912
|
+
cardsDir: path.relative(boardSetupRoot, tmpCardsDir),
|
|
913
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
const initArgs = ['init', boardDir, '--task-executor', taskExecutorCmd, '--task-executor-extra', taskExecutorExtra];
|
|
731
917
|
if (chatHandlerCmd) initArgs.push('--chat-handler', chatHandlerCmd);
|
|
732
918
|
if (inferenceAdapterCmd) initArgs.push('--inference-adapter', inferenceAdapterCmd);
|
|
733
919
|
initArgs.push('--runtime-out', runtimeOutDir);
|
|
@@ -762,6 +948,31 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
762
948
|
clearDirContents(boardDir);
|
|
763
949
|
initBoard(taskExecutorPathParam, chatHandlerPathParam, inferenceAdapterPathParam);
|
|
764
950
|
}
|
|
951
|
+
|
|
952
|
+
// Always refresh the extra in .task-executor so serverUrl and other runtime fields stay current
|
|
953
|
+
// even when initBoard is skipped (board already initialized from a previous run).
|
|
954
|
+
refreshTaskExecutorExtra(boardDir, {
|
|
955
|
+
boardSetupRoot: path.dirname(boardDir),
|
|
956
|
+
boardId,
|
|
957
|
+
boardRuntimeDir: path.relative(path.dirname(boardDir), boardDir),
|
|
958
|
+
runtimeStatusDir: path.relative(path.dirname(boardDir), runtimeOutDir),
|
|
959
|
+
cardsDir: path.relative(path.dirname(boardDir), tmpCardsDir),
|
|
960
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// Board-cards runtime: init if configured but not yet initialized.
|
|
964
|
+
if (resolveGandalfTaskExecutorPath() && !fs.existsSync(gandalfBoardFile)) {
|
|
965
|
+
initGandalfCards();
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function bootstrapGandalfCards() {
|
|
970
|
+
if (!fs.existsSync(tmpGandalfCardsDir)) return;
|
|
971
|
+
if (!fs.existsSync(gandalfBoardFile)) return; // runtime not initialized; initBoardAndSetup handles it
|
|
972
|
+
const jsonFiles = (fs.readdirSync(tmpGandalfCardsDir)).filter(f => f.endsWith('.json'));
|
|
973
|
+
if (!jsonFiles.length) return;
|
|
974
|
+
runCli(['upsert-card', '--rg', gandalfRuntimeDir, '--card-glob', path.join(tmpGandalfCardsDir, '*.json')]);
|
|
975
|
+
_refreshGandalfCardCache();
|
|
765
976
|
}
|
|
766
977
|
|
|
767
978
|
function bootstrapCards() {
|
|
@@ -772,9 +983,14 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
772
983
|
function bootstrapBoard() {
|
|
773
984
|
initBoardAndSetup();
|
|
774
985
|
bootstrapCards();
|
|
986
|
+
bootstrapGandalfCards();
|
|
775
987
|
}
|
|
776
988
|
|
|
777
989
|
function findCardPath(cardId) {
|
|
990
|
+
if (isGandalfCard(cardId)) {
|
|
991
|
+
const found = readGandalfInventory().find((e) => e.cardId === cardId);
|
|
992
|
+
return found ? found.cardFilePath : null;
|
|
993
|
+
}
|
|
778
994
|
const inv = readInventory();
|
|
779
995
|
const found = inv.find((e) => e.cardId === cardId);
|
|
780
996
|
return found ? found.cardFilePath : null;
|
|
@@ -795,7 +1011,8 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
795
1011
|
fs.writeFileSync(cardPath, JSON.stringify(nextCard, null, 2));
|
|
796
1012
|
|
|
797
1013
|
if (syncBoard) {
|
|
798
|
-
|
|
1014
|
+
const rg = isGandalfCard(cardId) ? gandalfRuntimeDir : boardDir;
|
|
1015
|
+
runCli(['upsert-card', '--rg', rg, '--card', cardPath, '--restart']);
|
|
799
1016
|
}
|
|
800
1017
|
}
|
|
801
1018
|
|
|
@@ -992,18 +1209,20 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
992
1209
|
}
|
|
993
1210
|
|
|
994
1211
|
// Fire-and-forget invocation of .chat-handler after a user chat message is persisted.
|
|
995
|
-
//
|
|
1212
|
+
// The handler file lives in the appropriate runtime dir (.chat-handler).
|
|
996
1213
|
// Called with: --boardId <id> --cardId <id> --extraEncJson <base64json>
|
|
997
1214
|
// extraEncJson decodes to:
|
|
998
1215
|
// boardSetupRoot — absolute path to board root (parent of runtime/, surface/, runtime-out/)
|
|
999
|
-
// boardRuntimeDir — relative: 'runtime'
|
|
1216
|
+
// boardRuntimeDir — relative: 'runtime' (or 'gandalf-runtime' for gandalf cards)
|
|
1000
1217
|
// runtimeStatusDir— relative: 'runtime-out'
|
|
1001
|
-
// cardsDir — relative: 'surface/tmp-cards'
|
|
1218
|
+
// cardsDir — relative: 'surface/tmp-cards' (or 'surface/tmp-gandalf-cards')
|
|
1002
1219
|
// chatDir — relative (from cardsDir): e.g. 'card-portfolio/chats'
|
|
1003
1220
|
// lastChatFile — filename of the just-written user message, e.g. '001_user.txt'
|
|
1004
1221
|
// Handler failures are logged and silently ignored — chat-send response is never affected.
|
|
1005
1222
|
function invokeChatHandler(cardId, chatsDir, lastChatFile) {
|
|
1006
|
-
const
|
|
1223
|
+
const isGandalf = isGandalfCard(cardId);
|
|
1224
|
+
const runtimeDir = isGandalf ? gandalfRuntimeDir : boardDir;
|
|
1225
|
+
const handlerFile = path.join(runtimeDir, '.chat-handler');
|
|
1007
1226
|
if (!fs.existsSync(handlerFile)) return;
|
|
1008
1227
|
const handlerCmd = fs.readFileSync(handlerFile, 'utf-8').trim();
|
|
1009
1228
|
if (!handlerCmd) return;
|
|
@@ -1012,11 +1231,12 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
1012
1231
|
try { fs.mkdirSync(chatsDir, { recursive: true }); fs.writeFileSync(processingFile, '', 'utf-8'); } catch {}
|
|
1013
1232
|
const extra = Buffer.from(JSON.stringify({
|
|
1014
1233
|
boardSetupRoot,
|
|
1015
|
-
boardRuntimeDir: path.relative(boardSetupRoot, boardDir),
|
|
1016
|
-
runtimeStatusDir: path.relative(boardSetupRoot, runtimeOutDir),
|
|
1017
|
-
cardsDir: path.relative(boardSetupRoot, tmpCardsDir),
|
|
1234
|
+
boardRuntimeDir: path.relative(boardSetupRoot, isGandalf ? gandalfRuntimeDir : boardDir),
|
|
1235
|
+
runtimeStatusDir: path.relative(boardSetupRoot, isGandalf ? gandalfRuntimeOutDir : runtimeOutDir),
|
|
1236
|
+
cardsDir: path.relative(boardSetupRoot, isGandalf ? tmpGandalfCardsDir : tmpCardsDir),
|
|
1018
1237
|
chatDir: chatsDir,
|
|
1019
1238
|
lastChatFile,
|
|
1239
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
1020
1240
|
})).toString('base64');
|
|
1021
1241
|
try {
|
|
1022
1242
|
const proc = spawn(handlerCmd, [
|
|
@@ -1180,6 +1400,10 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
1180
1400
|
const poll = setInterval(() => {
|
|
1181
1401
|
try {
|
|
1182
1402
|
runCli(['process-accumulated-events', '--rg', boardDir]);
|
|
1403
|
+
if (fs.existsSync(gandalfBoardFile)) {
|
|
1404
|
+
runCli(['process-accumulated-events', '--rg', gandalfRuntimeDir]);
|
|
1405
|
+
}
|
|
1406
|
+
_refreshGandalfCardCache();
|
|
1183
1407
|
|
|
1184
1408
|
const nextPayload = buildPublishedRuntimePayload();
|
|
1185
1409
|
const nextHash = stablePayloadString(nextPayload);
|
|
@@ -1318,8 +1542,8 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
1318
1542
|
const idx = parseInt(cardFileDownloadMatch[2], 10);
|
|
1319
1543
|
const expectedStoredName = url.searchParams.get('sn');
|
|
1320
1544
|
|
|
1321
|
-
const cardPath =
|
|
1322
|
-
if (!fs.existsSync(cardPath)) {
|
|
1545
|
+
const cardPath = findCardPath(cardId);
|
|
1546
|
+
if (!cardPath || !fs.existsSync(cardPath)) {
|
|
1323
1547
|
json(res, 404, { error: 'Card not found' });
|
|
1324
1548
|
return true;
|
|
1325
1549
|
}
|
|
@@ -1395,6 +1619,7 @@ export function createExampleBoardServerRuntime(options = {}) {
|
|
|
1395
1619
|
runCli,
|
|
1396
1620
|
demoPrepSetup,
|
|
1397
1621
|
ensureDemoSetup,
|
|
1622
|
+
isDemoSetupDone,
|
|
1398
1623
|
buildPublishedRuntimePayload,
|
|
1399
1624
|
handleRuntimeApi,
|
|
1400
1625
|
clearChatRecords,
|